FreeNAS

נפתח בדיסקליימר הרגיל:
כן, לא כתבתי מליון שנה, מה תעשו לי?

ב2009 בניתי מכונת אכסון ביתית מבוססת ZFS שהריצה את OpenSolaris.
התצורה של המערכת היא משהו דמוי RAID10:
שלושה זוגות דיסקים, כל זוג הוא מירור, וכולם ביחד מחוברים בRAID 0 ליחידה לוגית אחת.
מכיוון שקניתי דיסקים פשוטים ומעפנים, ציפיתי לאבד כמה במשך השנים וכך היה.
קונפיגורציה כזו יכולה לאבד עד שלושה דיסקים מתוך השישה, אבל רק לא מאבדים שני דיסקים מאותו זוג.
כל פעם שאבד דיסק, קניתי זוג חלופי וניצלתי את ההזדמנות כדי להגדיל את שטח האכסון.

חוץ מזה, מערכת ההפעלה התיישנה: Sun נרכשה על ידי אורקל (ימ"ש), שהפסיקה לפתח את OpenSolaris. הקהילה ניסתה להמשיך ושחררה את OpenIndiana, אבל מתישהו הם ויתרו.
חוץ מזה, אף פעם לא ממש הרגשתי בבית בסולאריס, כל דבר הוא קשה ומעצבן (חוץ מZFS).

לפני חודש חודשיים שמתי לב במקרה ששרת הקבצים מתלונן על בעיות באחד הדיסקים, ושהPOOL עבר לרמה של DEGRADED.
נו טוב, קורה.
קניתי זוג דיסקים בנוהל, 2TB כל אחד, של HGST – חברה שלא שמעתי את שמה עד היום, אבל שחברה הוסטינג שחררה מידע סטטיסטי שמראה שהם הכי אמינים.
הדיסקים עלו כ$65 כל אחד, שזה מחיר מצחיק למדי בשביל 2TB, אבל אני בטח לא מעודכן.

אחרי שהחלפתי את הדיסק הראשון, ZFS התחיל להתלונן שיש עוד דיסק דפוק, למרבה הפליאה בן הזוג של הדיסק הראשון שהתקלקל.
למזלי קניתי שניים, אבל מה – ZFS לא רצה לתת לי לנתק את הדיסק השני הדפוק כי אין מספיק עותקים של הנתונים (מה שיביא את כל הPOOL למצב לא שמיש).
די מוזר, ואחרי נסיונות חוזרים ונשנים לשכנע אותו בדרכי נועם הבנתי שאני לא הולך להצליח, מה שאומר שלראשונה אחרי שבע שנים עם שרת האכסון הזה, איבדתי נתונים.
לא לגמרי, כי למרות המצב המוזר עדיין יכלתי להעתיק קבצים כרגיל, אבל לא ממש היה לי איפה לשים את רוב הסרטים והסדרות אז מחקתי לא מעט.

בקיצור, אם כבר אני הולך לאבד נתונים, הגיע הזמן להפטר מהענתיקה ולהחליף את הOpenSolaris במשהו יותר מודרני.
בימים שבהם שמעתי פודקסטים (עברתי לספרי שמע זה מכבר), שמעתי בFLOSS WEEKLY על FreeNAS.
FreeNAS היא מערכת מבוססת FreeBSD, שנבנתה ממש מסביב לשימוש שאני עושה בZFS:
התלבטתי אם לנסות לינוקס, שהיום כבר תומך בצורה זו או אחרת בZFS, או לנסות את FreeNAS, שהוא בעצם ZFS למפגרים.
בחרתי בFreeNAS, ואני לא מצטער.

קודם כל, האתחול הרבה יותר פשוט, בקונפיגורציה הישנה איתחלתי מהדיסקים עצמם, מה שאומר שמחיצת השורש ישבה בעצמה על אחד המירורים. זה די מסובך להתקין ככה ולא ממש התחשק לי להתעסק עם זה שוב. הפתרון של FreeNAS הוא פשוט להתקין על USB Thumb drive ולאתחל ממנו.
מערכת ההפעלה יושבת עליו, הקונפיגורציה והנתונים יושבים על הדיסקים האמיתיים. הדבר הזה נותן גמישות, במקרה שהולכת מערכת ההפעלה להתקין אותה מחדש זה פשוט מאוד ולא מאבדים מידע.

מה שעוד מאוד נחמד, זה שאת כל הניהול השוטף ניתן לעשות ממשק וובי חזק.
הוא אפילו מאפשר כניסה בSHELL מתוך הדפדפן.
התקנתי ותוך כלום זמן הגדרתי הכל, כולל שיתוף קבצים עם חלונות, MAC, ולינוקס (ספציפית השיתוף עם חלונות אף פעם לא ממש עבד לי עם הOpenSolaris ופה פשוט עבד בקלות דרך הממשק).
הפעם בחרתי בקונפיגורציה של RAIDZ2. מה שאומר שאני יכול לאבד כל שני דיסקים. על הדרך זה גם הגדיל את שטח האחסון הזמין.

capture

 

הדבר היחיד שהיה קשה קצת היה לגרום לZync לעבוד שם.
עקרונית הוא לא באמת נחוץ כי אפשר להגדיר את מה שהוא עושה דרך ממשק המשתמש. אבל זה יהיה די מסורבל במיוחד אם רוצים לגבות כמה שרתים, לקחת סנפשוטים של הגיבוי מתי שבוודאות הRsync כבר לא רץ, ולהגדיר ערמה של ספריות שלא יגובו עבור כל שרת שמגובה.
החבר'ה בפורומים של FreeNAS לא ממש עזרו, ואלו בIRC היו אפילו עויינים לרעיון שאני מעדיף להשתמש במשהו לשורת פקודה במקום בממשק המשתמש בשביל זה.
בסוף הסתדרתי, האיך בדיוק שווה פוסט שלם אם זה מעניין מישהו.

בשורה התחתונה אני מרוצה, FreeBSD הוא בערך פי מליון יותר ידידותי מSolaris, ואני שמח שנפטרתי מעונשו של זה.

עד הפעם הבאה.

AWS

מסיבות היסטוריות, תמיד העדפתי את החומרה שלי אמיתית ולא וירטואלית.
בחודש האחרון יצא לי להתנסות בעבודה עם AWS – הלו הוא שרותי הווב של אמזון (Amazon Web Services) שכולל בין היתר את:

  • EC2 – הענן האלסטי של אמזון, שמאפשר להקצות מכונות וירטואליות על פי דרישה, שמריצות מה שבא לכם.
  • S3 – שרות אחסון הנתונים הוותיק של אמזון
  • RDS – שרותי בסיס נתונים, למעשה מכונת MySQL בניהול של אמזון, שכולל גם שרותי גיבוי, Read replica, סנאפ-שוטים של הנתונים ועוד.

מכונה וירטואלית בEC2 כוללת שטח אכסון מקומי (הרדיסקים לצורך העניין).
בעבר – מערכת הקבצים של המכונה ישבה על הדיסקים האלו. הImage של מערכות בשיטה הזו נקרא Instance store image, ואוכסנו בS3 – שרות איכסון הנתונים של אמזון.

אחת התכונות של הדיסקים המקומיים היא שכשהמכונה מוקצה מקבלים דיסקים ריקים, וכשהמכונה משוחררת (Terminated) המידע על הדיסקים האלו אובד לתמיד.
הדבר הזה גרם לקשיים אמיתיים להריץ בסיס נתונים על EC2, עד שאמזון פיתחו את הEBS (Elastic block store):
הEBS דומה במהות שלו למערכת איכסון מרכזית שמקצה דיסקים למכונות קצה, ולכן ביצועי הקריאה/כתיבה שלו תלויים בעומס עליו. במילים אחרות – אתם עשויים לסבול מביצועי קריאה/כתיבה ירודים כי משתמשים אחרים מתפרעים.
אם חשובים לכם ביצועי הIO, ואתם מוכנים לספוג אובדן של הנתונים אם המכונה משוחררת, שווה לההשתמש בדיסקים המקומיים, תקבלו ביצועיי IO צפויים יותר ובנוסף לא יהיה לכם תשלום לפי נפח הIO שאתם מבצעים (בניגוד לשימוש בEBS).
אפשר כמובן להגדיר אותם בתצורת  RAID-0 כדי לשפר את הביצועים

היתרון המרכזי ביותר לVolume של EBS הוא שמחזור החיים שלו נפרד מזה של המכונה אליה הוא מחובר: אם המכונה שאליה הוא מחובר משוחררת, הוא ממשיך להתקיים בנפרד ואפשר לחבר אותו למכונה אחרת (אבל רק לאחת בו זמנית כמובן).

EBS שמאפשר הקצאה של "דיסק" בצורה דינמית, וחיבור שלו למכונה בצורה דינאמית. בנוסף אפשר כמובן לשמור את מערכת הקבצים (root) של המכונה שם. Image כזה נקרא EBS Image, והוא עדיף ברוב הבחינות על הImage הוותיק יותר.
גם מכונות מבוססות EBS כוללות דיסקים מקומיים שתכלו לנצל אם תרצו (מה שאומר לפעמים שצריך לפרמט אותם).

אחד היתרונות המהותיים ביותר של ספקי ענן על פני ספק שרתים קלאסי הוא הזמן הנדרש כדי לקבל מכונה חדשה:

בספק קלאסי, מדובר בדרך כלל בכמה ימים לכל הפחות, שבהם הוא יזמין חומרה חדשה לפי דרישתכם, יתקין עליה מערכת הפעלה ויספק את המכונה.
בEC2, התהליך לוקח דקות, ואתם לא צריכים לשלוח אימייל לאף אחד, ניתן להקצות את המכונה מממשק הניהול של AWS, או בשימוש בSDK בשפה המועדפת עליכם.
מכיוון שהתהליך הוא כל כך אוטומטי, יש כמה דברים שאין לכם עליהם שליטה:
קודם כל, מאפייני החומרה מוגבלים למספר מצומצם של סוגי מכונות, ואתם צריכים לבחור את סוג המתאים ביותר.
בנוסף, המכונה תקבל כתובת IP פנימית וחיצונית שונה בכל שתקצו אותה, ושם המכונה יהיה מגעיל במיוחד (איזה חרא שמבוסס על כתובת הIP הפנימית).
אם אתם מגיעים כמוני מחוות שרתים קלאסית, זה יהיה מעצבן במיוחד. הצורך לשלוט על השם של מכונה הוא טריויאלי וחיוני במקרים רבים.

כשמקצים מכונה בEC2, חוץ מהפרטים הרגילים של סוג המכונה, איפה היא תהיה, על איזה אימג' היא תתבסס וכו' – אפשר גם להעביר מידע כללי כלשהו למכונה, שיהיה זמין לה על פי דרישה.
ספציפית אפשר להעביר למכונה את שם ההוסט שאנו רוצים שהיא תקבל, ואולי גם כל מני פרטים שיעזרו למכונה להגדיר את עצמה לפי הצורך בעליה הראשונה.
אם משתמשים בVPC – הלו הוא ענן וירטואלי פרטי, מקבלים כמה יתרונות, קודם כל אפשר לשלוט על תצורת הרשת של המכונות לחלוטין, מה שאומר שתוכלו לבחור כתובות IP, סאבנטים, שרת DNS וכו'.
כדי לשלוט גם על שם ההוסט של המכונה, המכונה מעדכנת DNS פנימי בשם ובכתובת הIP שלה (הDNS הוא מכונה וירטואלית בעצמו בתוך הVPC שלכם). מכיוון שכל המכונות שלכם מוגדרות להשתמש בDNS הזה, שם ההוסט החדש יהיה מוכר לכל המכונות האחרות אוטומטית ברגע שהמכונה עולה ומעדכנת את הDNS.

כל המכונות הוירטואליות שלי מבוססות על אותו IMAGE, למרות שהן מסוגים שונים ומשונים, יש לי שם:

  • מכונות DNS
  • מכונות Memcached
  • מכונות WEB
  • מכונות Gearman
  • מכונת HAProxy
  • ועוד

אז איך יתכן שכל המכונות מבוססות על אותו IMAGE?
כאמור, כשאני מקצה מכונה אני מעביר לה את ה"סוג" שלה בUSER-DATA.
כשהמכונה עולה, היא מעבירה את הסוג שלה לשרת תצורה מרכזי מסוג Puppet, שאומר למכונה מה לעשות כדי להפוך למה שהיא אמורה להפוך.
אפשר למלא כמה פוסטים רק על Puppet (ואפילו כתבתי עליו כבר פעם), אבל הפעם אני לא ארחיב.

כאשר יש לכם מערכת אלסטית של שרתים, שבה שרתים יכולים לבוא וללכת לפי דרישה – נוצרת בעיה של ניהול. למשל, אם תרצו להפעיל מחדש את כל שרתי האפ'אצי במכונות הWEB – איך תעשו את זה? בתור התחלה, איך תדעו בכלל איזה שרתים הם מכונות WEB ומה הכתובות שלהם?
אם תרצו לברר מה הגרסא של חבילה מסויימת בכל השרתים, איך תדעו מה כל השרתים?
פתרון אחד הוא  לנהל בסיס נתונים מרכזי שישמור מידע על השרתים. הפתרון הזה עובד, אבל הוא בעייתי : צריך לתחזק אותו, ויש לשים לב במיוחד למה קורה כשמכונה משוחררת.

פתרון אלטרנטיבי הוא להשתמש במערכת Marionette collective:
קונספטואלית, mcollective  היא נטולת שרת מרכזי.
יש שרת הודעות כללי (Message queue) מסוג ActiveMQ שמשמש לתקשורת בין המכונות. פלאגינים מיוחדים בשם Agents רצים על המכונות, ומאפשרים ביצוע פעולות שונות ומשונות עליהם.
הפעולות יכולות להיות מסוננות לפי "עובדות" שהמכונות יודעות על עצמן, במילים אחרות – מכונת WEB, שיודעת היא מכונת WEB, לא תבצע פעולה שמיועדת למכונת Gearman.
ההתקנה של MCollective היא לא מאוד מסובכת, ומה שיפה זה שהActiveMQ עובד, כל מכונה שעולה אוטומטית זמינה דרך הMCollective (כמובן – היא צריכה להריץ את השרת של MCollective, אבל כל זה חלק מהIMAGE הבסיסי.
שווה מאוד לראות את הסרטונים באתר של Mcollective כדי להבין במה מדובר.

אם לסכם, אני מרוצה מהמערכת שקמתי באמזון, ובגדול מהגמישות שנובעת מהתשתיות של אמזון.
יש לי עדיין לדאוג לכל העניין של איזורי הזמינות, גיבויים שוטפים וכו'.

אם תהיה דרישה, אולי אני אכתוב עוד כמה פוסטים שמפרטים לגבי חלקים ספציפיים מהתצורה שלי בAWS.

החלפת דיסק תקול בZFS

מכונת הOpenSolaris שלי, מכילה שישה דיסקים:
שני דיסקים של 1TB, שניים של 1.5TB ושניים של 2TB.
מאגר האחסון (pool) העיקרי בנוי בעצם משלושה זוגות של דיסקים (או חלקי דיסקים), כך שאם אחד מהם מתקלקל המערכת יכולה להמשיך לתפקד.
החסרונות של הגישה הזו הם שאני מאבד חצי מהנפח, ושאם שני דיסקים מאותו יחידת ראי נופלים איבדתי הכל.
היתרונות הם ביצועי קריאה כפולים, וגמישות בשינויי תצורה. קל להוסיף דיסקים או לשדרג דיסקים קיימים לגדולים יותר.

ההסתברות לכשלון של דיסק מסויים גדלה ביחס ישיר למספר הדיסקים, במהלך השנתיים בערך שיש לי את המערכת הזו, שלושה דיסקים כשלו (!) ובכל זאת עד היום לא איבדתי מידע.
בכשלון האחרון הבחנתי לפני כמספר שבועות : לא הרגשתי בעיות, אבל ראיתי במקרה שZFS מדווח על שגיאה בדיסק, מה שהביא את המאגר כולו למצב degraded, שהוא בהחלט לא מצב שתרצו להשאר בו יותר זמן ממה שחייבים (כי דיסק נוסף שכושל עלול לדפוק את כל הנתונים).
אז נאלצתי להחליף את הדיסק.
הקטע הכי קשה הוא להבין איזה מהדיסקים הפיזיים צריך לנתק.
בפעם האחרונה דאגתי לסמן את הדיסקים במדבקות עם השם שלהם במערכת ההפעלה (למשל c8d0 עבור דיסק 1 בקונטרולר 8) אבל משום מה המדבקות היו קצת לא מדוייקות.
בכל מקרה, אחרי קצת ניסוי וטעיה, מצאתי איזה מהדיסקים הוא c8d1 (הדיסק שדווח כלא תקין).
הרצתי את הפקודה הבאה כדי להוציא אותו מהמאגר, וניתקתי אותו פיזית.
[code]
zpool detach storage c8d1
[/code]
חיברתי למחשב אחר כדי לבדוק עם SpinRite (אין לי מסך מחובר לשרת הOpenSolaris).
SpinRite לא הצליח לתקן אבל ראיתי שSMART מדווח שיש לו המון שגיאות חיפוש (seek) וקריאה.

שלחתי למעבדה וקיבלתי דיסק אחר תוך כמה שבועות.
התקנתי את הדיסק במחשב, והרצתי את הפקודה הבאה כדי לחבר אותו חזרה:
[code]
zpool attach storage c9d1 c8d1
[/code]
הפקודה די הפוכה לפקודת הניתוק הקודמת, רק שהיא מציינת לאיזה דיסק קיים להוסיף את החדש.
אחרי שהוספתי, זה המצב של המאגר storage :
אפשר לראות שמתבצעת פעולת resilver, שבעצם דואגת לסנכרן הנתונים של הדיסק החדש עם הדיסק הוותיק באותה יחידת ראי.

root@iron:~# zpool status storage
pool: storage
state: ONLINE
status: One or more devices is currently being resilvered. The pool will
continue to function, possibly in a degraded state.
action: Wait for the resilver to complete.
scrub: resilver in progress for 0h14m, 0.60% done, 38h34m to go
config:

NAME STATE READ WRITE CKSUM
storage ONLINE 0 0 0
mirror-0 ONLINE 0 0 0
c8d0p2 ONLINE 0 0 0
c9d0p2 ONLINE 0 0 0
mirror-1 ONLINE 0 0 0
c9d1 ONLINE 0 0 0
c8d1 ONLINE 0 0 0 6.70G resilvered
mirror-2 ONLINE 0 0 0
c11d0 ONLINE 0 0 0
c10d0 ONLINE 0 0 0

errors: No known data errors

סקלה

בפוסט הקודם דיברתי על הצורך שלי בצפיה של סרטוני יוטיוב על האייפד כשאני לא מחובר, ועל הפתרון שלי – שמבוסס על כמה סקריפטים שקושרים ביחד את הלוגיקה הבאה:

  1. הבא מהAPI של יוטיוב פיד RSS רלוונטי
  2. חלץ ממנו URLים של סרטונים
  3. הורד את הסרטונים עצמם מיוטיוב עם youtube-dl
  4. קודד את הסרטונים לפורמט של אייפד עם HandBreak
  5. צור פיד RSS שמאפשר להוריד את הקבצים המקודדים

ברגע שהתהליך הזה קיים, מה שנשאר זה לרשום את הפיד החדש בITunes, ומאותו רגע הוא מוריד אוטומטית את הסרטונים לצפייה אוף ליין.

לאחרונה שמעתי יותר ויותר על סקלה, סקלה היא שפה יחסית חדשה (הופיעה ב2003).
אז מה התכונות של סקלה?

  • שפה סטטית, הקומפיילר יגיד לכם כשיש לכם שגיאות טיפוס (בניגוד לשפות דינמיות כמו PHP, פייתון וכו')
  • מבוססת על הJVM, סקלה מתקמפלת לבייט-קוד
  • מאפשרת גישה מלאה ונוחה לתשתיות וספריות של ג'אווה. אם יש לכם קוד ג'אווה תוכלו להשתמש בו בקלות מסקלה
  • הקוד בסקלה הוא הרבה יותר קומפקטי מג'אווה
  • תומכת בתכנות פונקציונלי, פונקציות הן אזרחיות מהשורה הראשונה, אפשר להעביר פונקציות כפרמטר וליצור פונקציות אנונימות בצורה מאוד אלגנטית.
  • תומכת במודל הActors, שהוא מודל שבו "שחקנים" שונים מתקשרים אחד עם השני על ידי שליחת הודעות, ולא חולקים שום State. המודל הזה חוסך לא מעט בעיות שנובעות ממיקבול גבוה
  • תמיכה בסקריפטים, אפשר להריץ קוד כסקריפט בלי לקמפל אותו מראש (למרות שכמובן אפשר לקמפל).

יש עוד כמה תכונות מעניינות לסקלה, אבל אני לא באמת מכיר את השפה לעומק עדיין.

החלטתי ללמוד סקלה, וכדי לעשות את זה כתבתי מחדש את הסקריפטים הקודמים כתוכנית סקלה אחת  וגם שיפרתי אותם על הדרך לתמוך בכמה ערוצי יוטיוב.
עשיתי את זה עם vim, ישירות על השרת – ובלי לקמפל, ממש כמו כתיבה של סקריפט.
אני בטוח שדוגמאות הקוד שלי יכולות להיות יותר אלגנטיות, אבל זה מה שיש לבינתיים 🙂
אז זה הmain של הסקריפט:


object YoutubeRSS
{
val downloader = new Downloader
def main(args: Array[String])
{
deleteOldFiles
regenerateFeeds
Source.fromFile("channels").getLines.foreach(x =>; process(x trim) )
downloader ! Stop
}
//...
}

התוכנית מפעילה כמה פונקציות ואז קוראת שורות מקובץ,  ועל כל שורה מפעילה את הפונקציה process.
לבסוף היא שולחת הודעת Stop לActor של הDownloader.
אפשר לשים מיד לב לכמה דברים:

  • כשמצהירים על object, זה בעצם סינגלטון.
  • אין נקודה-פסיק בסוף שורה, בסקלה זה אופציונלי.
  • לא חייבים להשתמש בסוגריים כשקוראים לפונקציה בלי פרמטרים (למעשה, אם יש לפונקציה פרמטר בודד – אפשר בדרך כלל לוותר על הסוגריים גם אז)
  • בסקלה כמעט כל דבר חוקי כשם של פונקציה, סימן קריאה היא פונקציה די נפוצה בהרבה ספריות.

הנה עוד קצת מהתוכנית:


object Conf
{
val YOUTUBE_CHANNEL_URL = "http://gdata.youtube.com/feeds/api/users/%s/uploads"
val YOUTUBE_PROFILE_URL = "http://gdata.youtube.com/feeds/api/users/%s"
val BIN_DIR = "/home/omry/youtube-rss-2/bin"
val DOWNLOAD_DIR = "/home/omry/youtube-rss-2/download"
val RSS_WEB_DIR="/home/omry/www/youtube-rss.firefang.net"
val RSS_BASE_URL="http://youtube-rss.firefang.net"
val DELETE_OLDER=14
}

object YoutubeRSS
{
//...
def process(channel : String)
{
println("Processing channel " + channel)
updateFeed(channel)
val url = Conf.YOUTUBE_CHANNEL_URL.format(channel)
val rss = Source.fromURL(url).mkString
val xml = XML.loadString(rss)
val entries = xml\"entry";
entries foreach(download(_,channel))
println("Done processing " + channel)
}
}

הקונפיגורציה כרגע היא ישירות בקוד בתוך אובייקט Conf.
שימו לב לאלגנטיות של הבאת הנתונים מהURL ובעיקר של פרסור הXML,
השורה הפשוטה

val entries = xml\"entry";

מחזירה רשימה של entries מתוך הXML (שהוא פיד רסס).

עוד קצת קוד מהפונקציה שמכינה את קובץ הRSS הסופי:


new File(dir).listFiles.
sortBy(_.lastModified).reverse.
filter(_.getName().endsWith(".mp4")).
map(file => file.substring(dir.length + 1)).
foreach(file =>
{
val f = new File(dir,file)
val entry = new SyndEntryImpl()
//..

הקוד פה מקבל (עם API של ג'אווה) את רשימת הקבצים בספריה, ממיין אותן לפי תאריך שינוי, הופך את הסדר, מפעיל פילטר שמשאיר רק קבצי mp4, מפעיל פוקציית map שמורידה את הספריה משם הקובץ ולבסוף מפעיל קוד כל סטרינג.

קצת על הפעלת תהליכים חיצוניים:
מי שניסה להפעיל פרוסס חיצוני מג'אווה בוודאי זוכר שזו לא חוויה מרנינה, צריך שני Threadים שיקראו את הפלט של התהליך (אחד לפלט סטנדרטי ואחד לשגיאה סטנדרטי) ובאופן כללי זה די מסורבל.
בסקלה (2.9 ומעלה) זה הרבה יותר פשוט:

// Run process
"ls -l"!
// Run process and get output into string:
val output = "ls -l"!!
// Run process from a list of arguments
val fname = List("youtube-dl", "--get-filename","-o","download/"+channel+"/%(uploader)s-%(stitle)s.%(ext)s",link.toString)!!

הרבה יותר נחמד מבג'אווה.

אפשר להוריד את הכל מפה.

בשורה התחתונה, בינתיים אני אוהב את סקלה, ונראה לי שאני אתחיל להשתמש בה באופן יותר קבוע.

איך להוריד סרטי יוטיוב לצפיה אופליין באייפד/אייפון

כן, אני יודע שמזמן לא היה פה פוסט, תתבעו אותי ;).

אחד הדברים שאף פעם לא עובד כמו שצריך באייפון ובאייפד (ובאייפוד) זה YouTube. כלומר – יש ישום של אפל לצורך העניין, אבל החוויה לא מרנינה:
בדרך כלל לא תצליחו לראות סרטון בלי גמגומים, גם אם אתם משתמשים ברשת אלחוטית מהירה עם חיבור אינטרנט שמן וטוב.

לאחרונה אני צופה בדי הרבה משחקי סטארקרפט ביוטיוב, והפריע לי חוסר היכולת לראות את המשחקים על האייפד.
זה הביא לי רעיון:
מה אם נוריד מראש את הסרטונים, וניתן אותם לאייפד בצורת פיד RSS – ממש כמו שצופים בפוקסט וידאו – ככה שנוכל לראות אותם בלי להיות תלויים בחסדי מהירות התקשורת ליוטיוב?

אז בתור התחלה, הצצה למטרה של כל הפוסט הזה:
הנה פיד הרסס של הערוצים שאני מנוי עלהם ביוטיוב (Subscriptions).
את הפיד הזה הוספתי לאייטונז (כפודקאסט), ומאותו רגע הוא מוריד את הסרטונים ומסנכרן אותם לאייפד.

אז איך כל זה קורה?

הכל מתחיל בAPI הנתונים של יוטיוב, שמאפשר קבלת פידי RSS שונים ומשונים, כשהשימושי שבהם מבחינתי הוא פיד שמאפשר קבלת הסרטונים החדשים בערוצים שמשתמש מסויים מנוי עליהם:

http://gdata.youtube.com/feeds/base/users/omryy/newsubscriptionvideos

שימו לב ששם המשתמש שלי הוא חלק מהURL.
קל למדי לחלץ מפה את רשימת הURLים לסרטונים שמופיעים בפיד, למשל עם הסקריפט הזה:
[CODE]
GET "http://gdata.youtube.com/feeds/base/users/omryy/newsubscriptionvideos" | grep http://www.youtube.com/watch?v=[a-zA-Z0-9_\-]* -o | sort -u
[/CODE]
הסקריפט, בפשטות – מוריד את הפיד, מחפש בתוכו URLים שנראים כמו לינקים לסרטון ביוטיוב, ומבטל כפילויות שמופיעות מסיבה לא חשובה בפיד.

הצעד הבא הוא להוריד את הסרטים עצמם (הלינקים בפיד הם לינקים לצפיה ישירות ביוטיוב).
לשם כך נשתמש בכלי החביב במיוחד, youtube-dl,

הפקודה הבאה תוריד את הסרטון מהURL הנתון, ותשמור אותו בפורמט נחמד וברור שכולל את השם של מי שהעלה, וכן שם קובץ של הכותרת של הסרטים.
youtube-dl -w YOUTUBE_URL -o "%\(uploader\)s-%\(stitle\)s.%\(ext\)s"
אפשר לחבר את הפקודה הזו לפקודה שמחלצת את הURLים מהפיד תוך שימוש בxargs, אבל בחרתי להשתמש בGNU Parallel שמאפשר הפעלה של פקודה מסויימת כמה פעמים במקביל (בדומה מאוד לxargs, רק במקביל).

אז הפקודה השלמה להורדת הסרטונים שמעניינים אותי היא:
[CODE]
#!/bin/bash
source conf
pushd $YT_DOWNLOAD_DIR
GET "http://gdata.youtube.com/feeds/base/users/omryy/newsubscriptionvideos" | grep http://www.youtube.com/watch?v=[a-zA-Z0-9_\-]* -o | sort -u | $BIN_DIR/parallel -j $CONCURRENT_DOWNLOADS $BIN_DIR/youtube-dl -w {} -o "%\(uploader\)s-%\(stitle\)s.%\(ext\)s"
popd
[/CODE]

הסקריפט הזה (והבאים אחריו) משתמש בקובץ ההגדרות הבא:
[CODE]
BIN_DIR=/home/omry/youtube-rss/bin
YT_DOWNLOAD_DIR=/home/omry/youtube-rss/download
CONCURRENT_DOWNLOADS=20
RSS_WEB_DIR=/home/omry/www/youtube-rss.firefang.net
RSS_BASE_URL=http://youtube-rss.firefang.net

# Delete older than X days
DELETE_OLDER=14
[/CODE]
עד פה, הכל טוב.
הפקודה הזו תוריד רק קבצים חדשים שלא הורדו כבר (הדגל -w).

כשניסיתי להעלות לאייפד את הקבצים האלו, שמתי לב שהוא החליט לדלג על חלק גדול מהם.
מסתבר שכל הקבצים שדולגו היו ברזולוציה של HD מלא (1920X1080).
אוקיי, אז זה הדבר הבא שצריך לתקן.
בהתחלה חשבתי להשתמש בffmpeg – שהיא הפתרון המקובל לקידוד קבצי וידאו.
הבעיה היא שצריך לקמפל גרסא שלה שתומכת בH264 (הקידוד שמתאים למכשירי אפל), ושבאופן כללי די קשה לגרום לה לעשות מה שאתם רוצים.
אחרי המשך חפירות מצאתי את Handbreak שהיא תוכנה (בקוד פתוח כמו כל שאר הדברים בפוסט הזה) שתפקידה בחיים הוא לקודד סרטונים לאייפוד, אייפון אייפד ושאר חברים בצורה מאוד פשוטה.
Handbreak מגיעה עם ממשק GTK שלא ממש עניין אותי, ועם שורת פקודה שמאוד עניינה אותי.
אגב, היא משתמשת בlibavcodec מבית היוצר של מפתחי ffmpeg.
לעניינינו, הפקודה לקידוד סרטון לפורמט אייפד היא :
[CODE]
HandBrakeCLI -Z iPad -i input_file -o output_file.mp4
[/CODE]
לא יכול להיות יותר פשוט מזה.

התהליך עצמו יכול לקחת די הרבה זמן, תלוי באורך הסרט ובעוצמת העיבוד של המחשב שלכם.
Handbreak מספיק מוצלחת כדי להשתמש בכל הליבות שלכם (אפשר לומר לה להשתמש במספר מסויים אם רוצים) ולרוץ בעדיפות נמוכה כדי לא לחנוק תהליכים אחרים.

השתמשתי בתסריט bash קטן כדי לעבור על הקבצים שירדו, ולקודד לספריה חדשה קבצים עם אותו שם אם הם כבר לא נמצאים שם.
[CODE]
$ cat encode_new.sh
#!/bin/bash
source conf
for file in `ls -1 $YT_DOWNLOAD_DIR/*.mp4 $YT_DOWNLOAD_DIR/*.flv $YT_DOWNLOAD_DIR/*.video`
do
base=`basename ${file%.*}`
ipad=${RSS_WEB_DIR}/${base}.mp4
if [ -f $ipad ]
then
echo "Skipping $ipad"
else
$BIN_DIR/HandBrakeCLI -Z iPad -i $file -o $ipad
touch -c -r $file $ipad
./update_feed.sh
fi
done
[/CODE]

אחרי כל קובץ שמקודד, הסקיפט update_feed.sh נקרא.
סקריפט זה משתמש בתוכנית ג'אווה קטנה שכתבתי שמכינה קובץ RSS על בסיס ספריה עם סרטוני וידאו.
לבסוף, נשאר רק למחוק קבצים ישנים יותר מ14 יום כדי למנוע התפוצצות, ולהריץ את כל העסק פעם ביום או משהו עם CRON.

[CODE]
$ cat delete_old.sh
#!/bin/bash
source conf
find -mtime +$DELETE_OLDER -exec echo rm $YT_DOWNLOAD_DIR/{} $RSS_WEB_DIR/{} \;
[/CODE]

אפשר להוריד את כל העסק מפה, שימו לב שזה כולל את HandBreak בגרסאת AMD64 ללינוקס, אבל אפשר להוריד מהאתר שלהם גרסאות אחרות.

לסיום, הנה סרטון סטרקראפט מצחיק:

ג'אווה וHTTPS

אחד הכאבים המפתיעים והלא צפויים שנתקלתי בהם לאחרונה עם ג'אווה היה כשניסיתי לתקשר עם שרת HTTPS שנחתם בחתימה של StartSSL.
מסתבר שג'אווה מגיעה כמעט בלי חתימות של ספקי חתימות (verisign שם, אבל הרבה מאוד אחרים לא).
חפירות באינטרנט הובילו לכל מני פתרונות שלא עבדו, אולי כי החתימה שלי היא Wildcard certificate (*.site.com).
פתרון אפשרי הוא ליבא את חתימת השורש של StartSSL לתוך הJVM, אבל זו פעולה ידנית שכל משתמש צריך לעשות ובכל מקרה היא לא עבדה לי, אולי בגלל סוג החתימה.
כל זה מעצבן למדי, הדפדפן סומך על האתר אבל ג'אווה לא מסכימה להתחבר:
נסיון להשתמש בURL הרגיל של ג'אווה כדי להתחבר בHTTPS בדרך כלל מוביל לשגיאה הנפלאה הבאה (אלא אם מדובר בחתימה שחתם השורש שלה ידוע לJVM):

Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:294)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:200)
at sun.security.validator.Validator.validate(Validator.java:218)
at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:126)
at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:209)
at com.sun.net.ssl.internal.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:249)
at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1053)
... 16 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:174)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:238)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:289)

פתרון:
מעבר לHTTPClient של אפאצ'י ושימוש בקוד הבא (דוגמא לGET ולPOST)
התיעוד של הספריה לא משהו, יש כמה גרסאות לא תואמות שלה והתיעוד מתייחס לגרסא ישנה.
הקוד הבא עובד עם גרסא 4.0.3 של HTTPClient ו4.1.0 של HTTPCore (זו תלות נדרשת לHTTPClient, אפשר להוריד מאותו אתר).
קחו בחשבון שהפתרון הזה מאפשר תקיפת MAN IN THE MIDDLE כדי לזייף את החתימה, אבל מבחינה פרקטית עדיף משהו שעובד אבל קצת פגיע מאשר משהו שלא עובד בכלל. (וכמובן ששימוש בHTTPS הוא עדיף על שימוש בHTTP נקי בכל מקרה).

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.util.EntityUtils;

public class HTTPClientSSLExample
{
public static void main(String[] args) throws MalformedURLException, IOException
{
byte[] bytes = getURLBytes_httpclient("https://www.startssl.com/", 30000, 30000);
System.out.println(new String(bytes));
}

public static byte[] getURLBytes_httpclient(String url, int connectionTimeout, int readTimeout) throws IOException
{
long now = System.currentTimeMillis();

DefaultHttpClient httpclient = getHttpClient(url);

httpclient.getParams().setIntParameter(CoreConnectionPNames.SO_TIMEOUT, readTimeout);
httpclient.getParams().setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, connectionTimeout);

HttpGet get = new HttpGet(url);
HttpResponse response = httpclient.execute(get);
int res = response.getStatusLine().getStatusCode();
if (res == 200)
{
HttpEntity entity = response.getEntity();
int len = (int) entity.getContentLength();
InputStream in = entity.getContent();
ByteArrayOutputStream bout = new ByteArrayOutputStream(len > 0 ? len : 1000);
pump(in, bout);
return bout.toByteArray();
}
else
{
String bs = "";
try
{
HttpEntity entity = response.getEntity();
bs = entity == null ? null : EntityUtils.toString(entity);
}
catch (IOException e)
{
bs += " || Exception while trying to read data from stream : " + e.getMessage();
}

throw new IOException("Server returned HTTP " + res + " after " + (System.currentTimeMillis() - now) + " ms, URL : " + url + " data: " + bs);
}
}

public static ByteArrayInputStream openInputStream_httpclient(String url, int connectionTimeout, int readTimeout, byte[] postdata) throws IOException
{
if (postdata != null)
{
DefaultHttpClient httpClient = getHttpClient(url);
HttpPost post = new HttpPost(url);
InputStreamEntity reqEntity = new InputStreamEntity(new ByteArrayInputStream(postdata), postdata.length);
reqEntity.setContentType("binary/octet-stream");
post.setEntity(reqEntity);
HttpResponse response = httpClient.execute(post);
int res = response.getStatusLine().getStatusCode();
if (res == 200)
{
HttpEntity entity = response.getEntity();
int len = (int) entity.getContentLength();
InputStream in = entity.getContent();
ByteArrayOutputStream bout = new ByteArrayOutputStream(len > 0 ? len : 1000);
pump(in, bout);
return new ByteArrayInputStream(bout.toByteArray());
}
else
{
throw new IOException("Http response code " + res);
}
}
else
{
byte[] bytes = getURLBytes_httpclient(url, connectionTimeout, readTimeout);
return new ByteArrayInputStream(bytes);
}
}

private static DefaultHttpClient getHttpClient(String url1) throws IOException
{
DefaultHttpClient httpclient = new DefaultHttpClient();

try
{
SSLContext ctx = SSLContext.getInstance("TLS");
X509TrustManager tm = new X509TrustManager()
{

public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException
{
}

public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException
{
}

public X509Certificate[] getAcceptedIssuers()
{
return null;
}
};
ctx.init(null, new TrustManager[]
{
tm
}, null);
SSLSocketFactory ssf = new SSLSocketFactory(ctx);
ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
ClientConnectionManager ccm = httpclient.getConnectionManager();
SchemeRegistry sr = ccm.getSchemeRegistry();
sr.register(new Scheme("https", ssf, 443));
httpclient = new DefaultHttpClient(ccm, httpclient.getParams());
}
catch (Exception ex)
{
ex.printStackTrace();
return null;
}

// This block handles urls with user:password@server block
URL u = new URL(url1);
String userInfo = u.getUserInfo();
if (userInfo != null)
{
String user;
String password;
int i = userInfo != null ? userInfo.indexOf(':') : -1;
if (i == -1)
{
user = userInfo.substring(0);
password = "";
}
else
{
user = userInfo.substring(0, i);
password = userInfo.substring(i + 1);
}
httpclient.getCredentialsProvider().setCredentials(new AuthScope(u.getHost(), u.getPort()), new UsernamePasswordCredentials(user, password));
}
return httpclient;
}

/**
* Writes the bytes read from the given input stream into the given output
* stream until the end of the input stream is reached. Returns the amount
* of bytes actually read/written.
*/
public static int pump(InputStream in, OutputStream out) throws IOException
{
byte[] buf = new byte[4096];
int count;
int amountRead = 0;

while ((count = in.read(buf)) != -1)
{
out.write(buf, 0, count);
amountRead += count;
}

return amountRead;
}
}

הדוור מצלצל פעמיים, ואז כותב את הסיסמא על תיבת הדואר

אחת התוכנות הפופולאריות ביותר לניהול רשימות תפוצה היא mailman.
היום קיבלתי תזכורת מאחת מרשימות התפוצה אליהן אני רשום שאני רשום אליה, ובתזכורת היתה הסיסמא איתה נרשמתי.

מי שקרא את הפוסט על התורכים, זוכר מה זה אומר:

מיילמן שומר את הסיסמא כטקסט נקי.

חיפוש קצר בגוגל הוביל לזה: באג במיילמן מ2006 (!)  שלאחרונה זכה לקצת התייחסות, הכרה מצד מפתחי מיילמן שזו בעיה שתתוקן בגרסא הבאה.
לא ממש מוסיף כבוד להילת האבטחה המוגברת של מוצרי קוד פתוח, באג טיוויאלי וחשוב כזה זוכה להתעלמות במשך ארבע שנים.
הבאג הוא כל  כך ישן שגם שכבר תצא הגרסא הבאה רוב המשתמשים לא יטרחו לשדרג כי הם אפילו לא יזכרו שהם התקינו את מיילמן.

בכל מקרה, המשמעות של זה היא שהסיסמאות שנתתם לרשימות התפוצה בסכנת חשיפה גבוהה. הדבר הזה מדגיש את החשיבות של פתרון כמו LastPass או KeePass שיאפשרו שימוש בסיסמאות אקראיות לכל שרות.

הקלות הבלתי נסבלת של הפרת הפטנט

מפתח הולנדי חובב פיתח בזמנו הפנוי במהלך סוף שבוע תוכנה דמוית שזאם למציאת מוסיקה שמבוססת על ניתוח תדר של הקול, יצירת חתימה פשוטה של חלונות בזמן וחיפוש של התאמות.
הוא פירסם את זה בבלוג שלו, וזכה לאיומים מרומזים מהחברה שפיתחה את שזאם בטענות שהוא מפר את הפטנט שלהם ובדרישה שלא יפרסם קוד ואפילו – תחזיקו – ימחק את הפוסט כי הוא "זמין למשתמשים בין לאומיים ועלול לשמש להפרת הפטנט".
ספציפית, אני מניח שהם מתכוונים שהוא זמין לאמריקאים (יש להם פטנט בארצות הברית).

הבנתם?
הבחור פיתח אב טיפוס בסוף שבוע, והבהיל את החברה כל כך עד שהם החליטו להפחיד אותו ולמנוע ממנו לפרסם את הקוד.

דעתכם?

לינוקס בתוך לינוקס, chroot

קרה לכם פעם שניסית להפעיל תוכנת לינוקס שקומפלה במערכת 32 ביט במערכת 64 ביט?
מעבדים מודרניים מסוגלים להריץ קוד 32 ביט וקוד 64 ביט בלי בעיה, אבל התוכנית דורשת ספריות 32 ביט שפשוט לא זמינות במערכת המארחת. גם אם ניסיתם להתקין את הספריות הרלוונטיות, התוכנה סרבה 'למצוא' את הספריות, כי היא מחפשת גרסאת 32 ביט והתקנת גירסאת 64 ביט.
יש טריקים של התקנה של ספריות 32 במערכת 64 ביט, למשל חבילת ia-32, אבל רק חלק קטן מהספריות מגיעות באריזה כזו.

דבר אחד לעשות זה לנסות לקמפל את התוכנה ב64 ביט, אבל לפעמים זה לא אפשרי – בין אם כי הקוד של התוכנה לא זמין או כי מסיבות אלו או אחרות היא לא מתקמפלת ב64 ביט או אולי מתקמפלת אבל לא עובדת).

אז מה אפשר לעשות?
יש בלינוקס פקודה בשם chroot, עם דף man קצר במיוחד.

[code]
NAME
chroot – run command or interactive shell with special root directory

SYNOPSIS
chroot NEWROOT [COMMAND [ARG]…]
chroot OPTION
[/code]

הפקודה משתמשת בקריאת המערכת chroot, שמשנה את ספרית השורש של התהליך שקורא לה לספריה חדשה.
אחרי chroot, מערכת הקבצים הרגילה מוסתרת וכל מה שהתהליך רואה זה את הקבצים והספריות שבתוך הספריה שניתנה לפקודה כפרמטר.
אחד השימושים הנפוצים (והמפוקפקים למדי) לchroot הוא להשתמש בה כדי לכלוא משתמש / תהליך בספריה מסויימת כדי למנוע ממנו גישה לשאר המערכת.
לא בשימוש הזה אני רוצה להתמקד.

נחזור לדוגמא של תוכנית 32 במערכת 64 ביט.
אפשר ליצור מערכת 32 ביט בספריה מסויימת, עם כל הספריות הדרושות – כולן ב32 ביט ולהריץ את התוכנית הסוררת – והיא תעבוד בלי בעיה.
יש פקודה נפלאה בדביאן בשם debootstrap, שלמעשה מסוגלת ליצור לנו מערכת דביאן מינימלית בארכיטרקטורה שנרצה (i386 במקרה הזה) ומהגרסא שנרצה – נניח stable.

[code lang="bash"]
$ mkdir stable-i386
$ sudo debootstrap –arch i386 stable stable-i386/
I: Retrieving Release
I: Retrieving Packages
I: Validating Packages

I: Base system installed successfully.
[/code]
בסוף התהליך יהיה לכם דביאן קטן ורזה למדי בתוך אותה ספריה (בסדר גודל של 200 מגה-בייט).
כדי שהוא יעבוד באמת כדאי לתת לו גישה לספריות dev וproc, ואת זה נעשה בעזרת פקודת mount עם פרמטר bind:
[code lang="bash"]
$ sudo mount –bind /dev stable-i386/dev
$ sudo mount –bind /proc stable-i386/proc
[/code]
אפשר כמובן להוסיף את הפקודות לfstab כדי שהן יורצו אוטומטית בעליה של המחשב.
[code lang="bash"]
/dev /stable-i386/dev none bind 0 2
/proc /stable-i386/proc none bind 0 2
[/code]

יש מי שעושים bind גם לספרית הבית שלהם, כדי שאותם קבצים יהיו זמינים (זהירות עם זה, אם אתם מוחקים את הstable-i386 אחרי שעשיתם את זה הוא יעיף לכם גם את ספריית הבית).
בכל מקרה, אחרי כל זה אפשר פשוט להריץ chroot כדי להכנס "למערכת" החדשה:
שימו לב שקצת קשה לדעת שאנחנו אכן בפנים, אפשר לבדוק שהקבצים מסביב הם לא מה שאתם רגילים לראות.
בדביאן יש טריק חביב, צרו קובץ debian_chroot בתוך etc, שימו בתוכו את השם של הchroot ומהלוגין הבא השם יופיע במקום בולט ונוח:

[code]
omry@falcon:~$ sudo chroot stable-i386/
root@falcon:/#
root@falcon:/# echo "i386 stable" > /etc/debian_chroot
root@falcon:/# bash
(i386 stable)root@falcon:/#
[/code]

בתוך הchroot אפשר להתפרע ולהריץ מה שרוצים, למשל apt-get כדי להתקין חבילות כאוות נפשכם.

אחד הדברים המעניינים ביותר הוא שכל העניין הזה מאפשר להריץ הפצת לינוקס אחת בתוך הפצת לינוקס אחרת.
לאחרונה נאלצתי לעבוד עם Centos 64bit, שהיא הפצה מבוססת רד-האט ועובדת עם חבילות rpm.
המשימה היתה להריץ תוכנה שנבנתה ונבדקה בדביאן בגרסאת 32 ביט, וההתקנה שלה הכילה כעשרה קבצי deb שיצרתי בעצמי מספריות בגרסאות שונות ומשונות (ולפעמים כוללת שינויים שלי).
במקום ליצור קבצי rpm מקבילים, פשוט התקנתי chroot של דביאן 32 ביט (אם מדברים על שתי ציפורים במכה אחת), ופשוט התקנתי את הdebים בפנים כאילו זה דביאן למהדרין.
נתקלתי בבאג בודד שנבע מכך שהקרנל של אותה גרסאת Centos היה ישן יותר מזה של הדביאן ואחת מקריאות המערכת (mmap) השתנתה בין הגרסאות בצורה שגרמה לקוד שעבד בגרסא החדשה של הקרנל לקרוס בגרסא הישנה, אבל שינוי קטן בתוכנה שלי פתר את הבאג.

אגב:
הפוסט הזה לא יהיה שלם בלי להזכיר את schroot שמאפשר גם למשתמשים שאינם root להשתמש בchroot, ובגדול הוא הרבה יותר נוח מאשר שימוש ישיר בchroot.

PXE, ISCSI ושאר ירקות

לפני כשנה ארגנתי לי מכונת אחסון ביתית שמריצה OpenSolaris (להלן Iron, או איירון).
איירון מכיל היום שישה דיסקים בגדלים שונים (בתצורת MIRROR, ככה שאני מקבל חצי מנפח האחסון נטו).
בהתחלה השתמשתי במכונה בתור כונן גדול לאחסון סרטים ומוסיקה (גדול מאוד, נטו 4TB וכאמור כל ביט כתוב על שני דיסקים), אבל עוד מההתחלה היו לי מחשבות על אחסון של ספריית הבית שלי על המכונה הזו כדי להרוויח מהירות שרידות וגיבויים נוחים ויעילים (הכל בחסות ZFS).
מכיוון שהדיסקים החדשים יותר שלי נמצאים באיירון המחשב הראשי שלי זכה לקבל שני דיסקים ישנים למדי, של 200GB ו500GB.
על הדיסק של ה200GB מותקנת חלונות 7 שמשמשת אותי למשחקים, ועל הדיסק של ה500 מותקן דביאן לינוקס (Squeeze) שמשמש לכל השאר.
לאחרונה התחילו לצוץ רמזים שהמצב של הדיסקים האלו די ירוד – bad sectors (מגזרים רעים!), שגיאות קריאה וסריקה (seek) אקראיות, תקיעות אקראיות וכו'.
הצעד הטבעי במצב כזה הוא לקנות דיסקים חלופיים, אבל זה די בזבוז לקנות דיסקים חדשים כשיש כל כך הרבה מקום איכותי מהיר ופנוי על איירון.
אז התחלתי לחשוב על האפשרות של איתחול דרך הרשת לשתי מערכות ההפעלה על המחשב הראשי.
מחשבים מודרניים תומכים באיתחל רשת דרך PXE (פיקסי).

מילה מקדימה:
כמעט את כל הבדיקות והמשחקים להלן ביצעתי תוך שימוש בVirtualBox.
ווירצ'ואל בוקס תומך היטב באיתחול פיקסי. כדי לעשות את זה צריך לשנות את הגדרות המכונה הוירטואלית לאיתחול מהרשת.
בנוסף, אם אתם משתמשים כמוני בשרת DHCP ברשת האמיתית, חשוב לשנות את הגדרות כרטיס הרשת של המכונה הוירטואלית למצב Bridged (ברירת המחדל היא NAT) אחרת זה לא יעבוד.

כדי לאפשר למחשבים לבצע איתחול דרך פיקסי צריך את הרכיבים הבאים:

  • שרת DHCP – שמעביר אוטומטית למחשב הגדרות רשת ופרמטרים לפיקסי.
  • שרת  TFTP – שמכיל קבצים שמשמשים את תהליך האתחול. TFTP זה קיצור פרוטוקול טריויאלי להעברת קבצים, והוא מאוד פרימיטיבי ואיטי – אבל מספיק בשביל להתניע את התהליך.

שרת הDHCP צריך לתמוך בכל מני אופציות מתקדמות, לכן לא סביר שהDHCP שמגיע עם הנתב האלחוטי הביתי שלכם יתאים לצורך העניין.
בגדול, הDHCP צריך להכיל שני פרמטרים:

  • TFTPsrvN :  שמכיל את השם או כתובת הIP של המחשב שמריץ את שרת הTFTP
  • BootFile : שמכיל את השם של קובץ ההפעלה הראשי שמתחיל את תהליך האתחול, ונמצא בתוך ספרית השורש של שרת הTFTP.

התהליך הוא כזה:
המחשב מבצע איתחול ומחפש ברשת שרת DHCP שיספר לו מה שרת הTFTP ואיזה קובץ למשוך ממנו, ברגע שהוא מקבל הוא מתחבר לשרת הTFTP, מושך את קובץ ההפעלה ומפעיל אותו.
הקובץ הזה מכיל טוען בוט מיוחד. יש כמה אפשרויות ולמעשה אני עדיין מתנסה בעניין.

מכיוון שאני מתכוון להשתמש במכונת הOpenSolaris לשמירת הקבצים, זה טבעי שאני אשתמש בה גם בשביל שרתי הDHCP והTFTP.
שרת הDHCP הנפוץ ביותר הוא ISC DHCP ואולי כדאי לבחור בו כי רוב הדוגמאות שתמצאו ברשת לגבי הגדרת PXE יתאימו לשרת הזה.
אני החלטתי להשתמש בשרת הDHCP שמגיע עם OpenSolaris, יש הוראות התקנה וקינפוג בסיסיות כאן.
מה שחסר זה שם זה איך מגדירים את TFTsrvN ואת BootFile:
ככה אני עשיתי את זה:
[code lang="bash"]
pfexec dhtadm -M -m `hostname` -e BootFile=pxelinux.0
pfexec dhtadm -M -m `hostname` -e TFTPsrvN=10.0.0.4
[/code]
אפשר גם להשתמש בכלי הגרפי dhcpmgr שיפתח ממשק משתמש שיאפשר שינוים בהגדרות שרת הDHCP.

טועני בוט

לדעתי הטוען בוט הפופולרי יותר הוא pxelinux שהוא למעשה גרסא מיוחדת של syslinux.
כדי לעבוד עם פיקסילינוקס, צריך לשים את pxelinux.0 בספריית השורש של שרת הtftp (בדרך כלל /tftpboot).
כשפיקסילינוקס עולה, הוא מחפש קבצי הגדרות שמתאימות למחשב לפי סדר הגיוני למדי : קודם קובץ בשם של כתובת הMAC של המחשב, ואז בשם של כתובת הIP, ואז חלק יותר קטן מכתובת הIP וכו' ולבסוף קובץ השם default שיטען אם לא נמצא קובץ הגדרות יותר ספציפי.

פיקסילינוקס תומך במעין תפריט בוט פשוט, למשל:
[code]

LABEL sbm
KERNEL memdisk
APPEND initrd=sbm.bin

LABEL local
LOCALBOOT 0
[/code]

עם התפריט הבא, פיקסילינוקס יציג לנו propmt שבו נוכל להקליד local כדי להמשיך לבוט מקומי או sbm כדי לטעון את  Smart boot manager (אפשר למצוא את sbm.bin פה.

דבר אחד שחסר לפיקסילינוקס הוא תמיכה בboot מכונן iscsi.
iscsi הוא פרוטוקול תקשורת שמאפשר שימוש בדיסק מרוחק כאילו הוא דיסק סקאזי מקומי.

מסתבר שפרוייקט אחר בשם gpxe (הידוע גם כ Etherboot) תומך בבוט מiscsi, ואפילו בבוט מhttp, ftp ועוד.
מסתבר עוד, שיש מעין הכלאה של pxelinux ושל gpxe שמאפשרת את היכולות של pxelinux וגם את היכולות של gpxe, ההכלאה נקראת gpxelinux (איזו הפתעה) והיא כלולה בהורדה של pxelinux.
כדי להשתמש בgpxelinux, צריך לקבוע בשרת הDHCP שקובץ הבוט הוא gpxelinux.0, שאר העבודה בדיוק כמו עם פיקסילינוקס.

על ISCSI

לISCSI יש טרמינולוגיה די מעצבנת:

  • Logical unit : המקבילה לדיסק או מחיצה
  • LUN : המספר של הלוג'יקל יוניט
  • Target : מעין מזהה שמגדיר גישה לאחד או יותר יחידות לוגיות, נראה ככה למשל : iqn.1986-03.com.sun:02:2775db99-0772-6e28-bb0e-bcbe420894ee
  • Initiator : הלקוח, קוד ששולח בקשות IO בפרוטוקול iscsi.

אפשר להרים שרת iscsi גם עם לינוקס כמובן, אבל אני משתמש באופן סולאריס בתור מערכת האחסון שלי.

לאופן-סולאריס יש ארכיטקטורה חדשה לiscsi שנקראת Comstar, קומסטר תומך גם בעוד פרוטוקולים כמו ערוץ אופטי (FC) אבל זה לא מעניין אותנו.

יש פה פוסט די טוב שמתאר שימוש בסיסי בקומסטר כדי ליצור iscsi target.
מה שחסר זה שצריך בהתחלה להתקין את stmf ולעשות ריבוט אם זה לא עובד (כן, ריבוט – יש על זה באג פתוח).
[code]
pkg install SUNWstmf
[/code]
שימו לב שבדוגמא שם הוא מתאר איך ליצור מיפוי שבו כל היחידות הלוגיות זמינות דרך כל הtargetים. אם אתם רוצים משהו יותר מורכב מזה תקראו את התיעוד, ספציפית את הדף הזה.

ברגע שהגדרתם target בהצלחה, נשאר לבדוק אותו.
זה די פשוט גם בחלונות 7 או וויסטה וגם בלינוקס. חפשו קצת בגוגל.

לגבי התקנות ואיתחול של של מערכות הפעלה ספציפיות על iscsi, יש תיעוד לרוב בוויקי של Etherboot .
עדיין לא הצלחתי להגיע לבוט מלא של חלונות 7 או דביאן דרך הרשת, אבל אני מקווה להצליח בקרוב.

צפו לפוסט עדכון לגבי איך ומה עושים לגבי כל אחת מהמערכות כדי להגיע לאיתחול מISCSI. (אלא אם אני אכשל ואז צפו לשקט חשוד).

אם הפוסט הזה עשה לכם תיאבון לנסות, קחו בחשבון שתצטרכו ללמוד הרבה דברים, ואולי תיתקעו בדרך.
זה לא פשוט, וזה לוקח די הרבה זמן להבין את הכל.

פוסטים קשורים:
מכונת אחסון ביתית על אופן-סולאריס
אופן סולאריס: תצורה, שיתוף קבצים וגיבויים
שידרוג נפח אכסון ZFS על גבי OpenSolaris