ป้ายกำกับ

วันอาทิตย์ที่ 14 กันยายน พ.ศ. 2557

Visual Studio Image Library

เมื่อก่อนตอนสมัยที่ผมข้ามจาก Visual Studio 6 มาใช้ Visual Studio 2008 นั้น ผมเพิ่งจะได้รู้ว่า ไมโครซอฟต์ได้แถมคลังรูปภาพไว้ใช้สำหรับทำปุ่มต่างๆในสไตล์ของไมโครซอฟต์มาให้ เรียกว่า Visual Studio Image Library ซึ่งความจริง Image Library นี้มีมาตั้งแต่ Visual Studio รุ่น 2005 โดยไฟล์จะอยู่ในโฟลเดอร์ ดังตาราง

Visual Studio 2005C:\Program Files\Microsoft Visual Studio 8\Common7\VS2005ImageLibrary\VS2005ImageLibrary.zip
Visual Studio 2008C:\Microsoft Visual Studio 9.0\Common7\VS2008ImageLibrary\1033\VS2008ImageLibrary.zip
Visual Studio 2010C:\Microsoft Visual Studio 10.0\Common7\VS2010ImageLibrary\1033\VS2010ImageLibrary.zip

ตอนนั้นน่าจะมีภาพอยู่ประมาณ 1,000 ภาพ แต่ตั้งแต่ Visual Studio 2012 เป็นต้นมา ไมโครซอฟต์ไม่ได้รวมชุด Visual Studio Image Library ไว้ใน Released Package ด้วย ซึ่งผมก็ไม่ได้สนใจหามาเพราะมีไฟล์เก่าของ VS2008 อยู่ จนกระทั่งวันนี้ ผมได้ติดตั้ง Visual Studio 2013 และต้องการใช้ Image Library ขึ้นมา ก็เลยค้นหาดู พบว่า ไมโครซอฟต์มีให้ดาวโหลด อยู่ที่
http://www.microsoft.com/en-us/download/details.aspx?id=35825
เห็นเขียนไว้ในรายละเอียดว่ามีภาพถึง 5,000 ภาพ เช่น


 ใครสนใจก็ลองดาวโหลดมาใช้ดู แต่อย่าลืมอ่านเงื่อนไขการใช้งานด้วยนะครับ

อ้างอิง

  1. Microsoft Corporation, How to Get High-Quality Icons Using Visual Studio Image Library

วันอังคารที่ 19 สิงหาคม พ.ศ. 2557

Restore Files from deja-dup

เวลาใช้ระบบ Backup ของ Ubuntu นั้น GUI ที่มีอยู่บน Ubuntu 12.04 หรือ 14.04 จะ ทำการ restore ไฟล์ทั้งหมดเท่านั้น ซึ่งหากเรามีข้อมูลจำนวนมาก ก็จะใช้เวลานาน หากมีข้อมูลที่เราต้องการ restore เฉพาะบางไฟล์ จะต้องใช้ command-line คือ

Usage:
  deja-dup [OPTION...]
  deja-dup --backup
  deja-dup --restore [FILES…]
  deja-dup --restore-missing DIRECTORY

Déjà Dup is a simple backup tool.  It hides the complexity of backing up
the Right Way (encrypted, off-site, and regular) and uses duplicity as
the backend.

Help Options:
  -h, --help               Show help options
  --help-all               Show all help options
  --help-gtk               Show GTK+ Options

Application Options:
  --version                Show version
  --restore                Restore given files
  --backup                 Immediately start a backup
  --restore-missing        Restore deleted files
  --display=DISPLAY        X display to use

โดยต้องไปต้้งค่าต่างๆด้วย GUI ให้เรียบร้อยก่อน เช่นค่า Storage location

วันอังคารที่ 1 กรกฎาคม พ.ศ. 2557

ข้อควรระวังในการใช้ Static References เมื่อเขียน API

ช่วงนี้ผมกำลังเขียนโปรแกรมเกี่ยวกับ Hadoop และ HBase อยู่ และกะว่าจะทำเป็น API สำหรับใช้งานกับโปรแกรมอื่นๆได้ด้วย บังเอิญว่า ผมต้องการใช้ Logging เพื่อให้สะดวกต่อการนำไปใช้และตรวจสอบปัญหา ซึ่งเมื่อ 10 กว่าปีก่อน ผมก็เคยศึกษามาบ้าง แต่เนื่องจากไม่ค่อยได้ใช้ จึงไม่ได้ลงลึกอะไรมากนัก แต่ตอนนี้ผมต้องเลือกว่าจะใช้ Framework ตัวไหนดี ที่ผมมองๆอยู่ก็มี 4 ตัว คือ
  1. Java Logging ซึ่งเป็นมาตรฐานหลักเพราะมีอยู่ใน JRE อยู่แล้ว ถ้าเอามาใช้ก็จะได้ไม่ต้องพ่วง Library อะไรให้ยุ่งยาก แต่ Java Logging ไม่ค่อยจะเก่งสักเท่าไหร่ การใช้งานจึงไม่สะดวกนัก
  2. Apache log4j ซึ่งเป็น Logging API ที่มีมานานมาก ถ้าจำไม่ผิดน่าจะก่อน Java Logging เสียอีก และเท่าที่ได้กลับไปดูหน้าเว็บของโครงการ Apache Logging Services พบว่ามีการพัฒนาไปเป็น Apache log4php log4net และ log4cxx อีกด้วย
  3. Apache หรือ Jakarta Commons Logging (JCL) มีแนวคิด ซึ่งออกแบบมาให้เป็น abstract หรือ wrapper เพื่อให้ผู้ใช้สามารถเลือกตัว Logger ที่ต้องการใช้งานจริงๆได้ ถอดเปลี่ยนได้ จึงสามารถใช้งานร่วมกับ Logger ตัวอื่นๆได้ เช่น log4j, Java Logging และ Avalon LogKit
  4. Simple Logging Facade for Java (SLF4J) ตัวนี้ HBase ใช้งานอยู่ ออกแบบมาให้เป็น abstract หรือ wrapper เช่นเดียวกับ JCL สามารถใช้งานร่วมกับ Logger ตัวอื่นๆได้ เช่น log4j, Java Logging และ Logback
แต่ประเด็นที่ผมนำมาเขียนบทความนี้ไม่ใช่ว่าจะเลือกตัวไหนดีนะครับ ประเด็นก็คือปัญหาเรื่องการใช้ static ในการสร้างตัว Logger ซึ่งมักจะทำไปเพราะเป็นที่คุ้นเคย เช่น
public class Root {
    private static Log log = LogFactory.getLog(Root.class);
    ....
}
การประกาศให้ตัว Logger เป็น static (class variable) จะช่วยให้ไม่สิ้นเปลือง เพราะมี reference เพียงแค่ตัวเดียวในระบบ ผิดกับการสร้าง Logger ที่ไม่เป็น static (instance variable) ทั้งนี้ ประสิทธิภาพจะแตกต่างกันมากน้อยก็ขึ้นอยู่กับว่า ในระบบของเรามี instance เกิดขึ้นมากแค่ไหน และมีการเรียกใช้ Logger บ่อยแค่ไหนด้วย

Stand-Alone Application

ประเด็นปัญหาของการสร้าง Logger เป็น static จะไม่เกิดขึ้นเลยหากโค้ดที่เราเขียนเป็น Stand-alone Application แต่จะเป็นปัญหาก็ต่อเมื่อ โค้ดที่เราเขียนเป็น API และถูกนำไปใช้งานโดย Application หลายๆตัวที่ทำงานอยู่บน Container เดียวกัน เช่น Web Application เนื่องจาก Web Server อย่าง Tomcat (Servlet Container) จะมีตัว ClassLoader หลายตัวในการโหลด Web Application แต่ละตัวขึ้นมา ซึ่งทำให้ Application แต่ละตัวใช้ Logger ตัวเดียวกันได้ (เนื่องจากเป็น static) เกิดปัญหาว่าข้อมูล Log ของ Application แต่ละตัวจะปะปนกัน ไม่สามารถแยกออกได้

ปัญหานี้ไม่ใช่จะเกิดขึ้นกับเฉพาะ Logger เท่านั้น แต่จะเกิดกับตัวแปร static อื่นๆด้วยเช่นกัน จึงทำให้ผมต้องคิดหนักขึ้นกับกรณีอื่นๆ และเป็นที่มาของการทดลอง เพื่อแสดงให้เห็นถึงผลลัพธ์ที่เกิดขึ้นกับปัญหานี้

CLASSPATH and ClassLoader

ก่อนอื่นขออธิบายเรื่อง ClassLoader สักนิดหนึ่ง เผื่อว่าบางคนอาจจะยังไม่เข้าใจนะครับ (สำหรับคนที่เข้าใจเรื่องนี้แล้วก็ข้ามหัวข้อนี้ไปได้เลย) การโหลดโปรแกรมมาทำงานของ java (Java Virtual Machine, JVM) จะทำโดย ClassLoader ซึ่ง ClassLoader หลักคือ Bootstrap ClassLoader จะทำการอ่านคลาสต่างๆที่จำเป็น และค้นหาคลาสไปตามโฟลเดอร์ต่างๆใน CLASSPATH ที่ตั้งไว้ โปรแกรมใดต้องการใช้คลาสอะไรบ้างก็ต้องระบุที่อยู่ของมันไว้ใน CLASSPATH ให้ถูกต้อง มิฉะนั้นจะเกิด ClassNotFoundException ขึ้นและโปรแกรมก็จะทำงานไม่ได้ นี่คือหลักปกติ ทุกอย่างจะต้องเตรียมไว้ให้ครบถ้วนก่อนที่จะรันโปรแกรม

แต่ในความเป็นจริงแล้ว กลไกนี้สามารถเพิ่มเติมหรือขยายขอบเขตของโปรแกรมออกไปได้ด้วยการสร้าง ClassLoader เพิ่มเติมในโปรแกรมของเรา อันนี้เป็นความพิเศษของภาษา Java เรียกว่า Dynamic Class Loading จะทำให้โปรแกรมมีความยืดหยุ่นสูง และการทำ Plug-in เป็นไปได้ง่ายมาก (รายละเอียดศึกษาเพิ่มเติมในเรื่อง ClassLoader และเรื่อง Reflection) เมื่อโปรแกรมของเราทำงานและมีการสร้าง ClassLoader เพิ่มเติมขึ้นมา โปรแกรมของเราก็จะสามารถโหลดคลาสใหม่ๆเข้ามาทำงานโดยที่คลาสนั้นๆไม่จำเป็นต้องอยู่ใน CLASSPATH และเราไม่จำเป็นต้องหยุดการทำงานของโปรแกรมเพื่อจัดการและรันใหม่ โดยที่กลไกการสร้าง ClassLoader นั้นจะกำหนดให้เราต้องสร้างและเชื่อมต่อกันเป็นทอดๆ กลายเป็น Tree ของ ClassLoader โดยมี Bootstrap ClassLoader เป็นจุดบนสุด (root) ของ Tree หาก ตัว ClassLoader ใด ต้องการหาคลาสที่จะโหลด จะต้องเรียกไปใหั Parent ของมันค้นหาก่อน และส่งต่อกันเป็นทอดๆ จนไปสุดที่จุดสูงสุดของ Tree หากหาไม่พบจึงจะค่อยๆหาย้อนกลับมาจนหมดแล้วจึงเกิด ClassNotFoundException แต่ถ้าหาพบโดย ClassLoader ตัวใด คลาสนั้นก็จะถูกโหลดมาด้วย CassLoader นั้นๆ นั่นหมายความว่า หากมีคลาสวางอยู่ใน CLASSPATH แม้เราตั้งใจจะให้โหลดจากที่อื่นก็จะทำไม่ได้ (ยกเว้นว่าเราจะสร้าง ClassLoader แบบของเราเองและไม่ทำตามกฎ)

ด้วยโครงสร้างอย่างนี้ คลาสที่ถูกโหลดเข้ามาในโปรแกรมด้วย ClassLoader ที่อยู่คนละกิ่งของ Tree จะมีขอบเขตของการมองเห็นคลาสอื่นๆจำกัดเฉพาะเส้นทางในกิ่งของตัวเองเท่านั้น แต่ก็จะมีจุดร่วมที่กิ่งมารวมกันเพราะมี Parent ร่วมกันอยู่ ตรงนี้จึงเป็นที่มาของปัญหาหากไม่จัดการให้ถูกต้อง

Preparations

source code ของโปรแกรมทดสอบ สามารถดาวโหลดได้ที่ Google Drive ไฟล์ที่ดาวโหลดมา เป็น zip ของโฟลเดอร์โปรเจ็คใน Eclipse เมื่อแตกไฟล์มาแล้ว ในโฟลเดอร์ src มี source code ทั้งหมดด้วยกัน 4 ไฟล์คือ
  1. StaticFieldTest.java เป็น main โปรแกรมที่จะรันทดสอบ
  2. test/App1.java เป็นคลาสที่จำลองว่าเป็น Application ตัวที่หนึ่ง
  3. test/App2.java เป็นคลาสที่จำลองว่าเป็น Application ตัวที่สอง
  4. test/StaticField.java เป็นคลาสที่จำลองว่าเป็น Library ที่ทั้งสอง Application เรียกใช้งาน
ก่อนอื่นต้องขออธิบายโปรแกรมหลักก่อนนะครับ ไฟล์ StaticFieldTest.java เป็นดังนี้

import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;

public class StaticFieldTest {
  public static void main(String[] args) throws Exception {
    System.out.println("SystemClassLoader: "+ClassLoader.getSystemClassLoader());
    ClassLoader sftCL = StaticFieldTest.class.getClassLoader();
    System.out.println("StaticFieldTest ClassLoader: "+sftCL);
    System.out.println("StaticFieldTest ParentClassLoader: "+sftCL.getParent());
    URLClassLoader shareCL = new URLClassLoader(new URL[] { new URL("file:///tmp/test/share/") });
    URLClassLoader app1CL = new URLClassLoader(new URL[] { new URL("file:///tmp/test/app1/") }, shareCL);
    URLClassLoader app2CL = new URLClassLoader(new URL[] { new URL("file:///tmp/test/app2/") }, shareCL);
    Object obj1 = app1CL.loadClass("test.App1").newInstance(); // Load app1
    Runnable app1 = (Runnable)obj1;
    app1.run(); // Run app1
    Object obj2 = app2CL.loadClass("test.App2").newInstance(); // Load app2
    Runnable app2 = (Runnable)obj2;
    app2.run(); // Run app2
  }
}

โปรแกรมนี้จำลองการทำงานว่าตัวเองเป็น Application Container กล่าวคือ โปรแกรมจะโหลด Application App1 และ App2 เข้ามารันผ่านทาง URLClassLoader ในที่นี้ผมสร้างเตรียมไว้ 3 ตัวคือ shareCL เป็นตัวโหลดคลาสต่างๆที่อยู่ในโฟลเดอร์ /tmp/test/share ส่วน app1CL และ app2CL จะโหลดคลาสต่างๆที่อยู่ในโฟลเดอร์ /tmp/test/app1 และ /tmp/test/app2 ตามลำดับ โดยที่ app1CL และ app2CL จะถูกสร้างให้มี parent คือ shareCL หากใครจะทดลองตามก็ขอให้สร้างโฟลเดอร์นี้ไว้ หรือแก้ไข source code ไปที่โฟลเดอร์ที่ต้องการก่อนนะครับ

หากใครไม่ได้ใช้ Eclipse ผมก็ขอแนะนำ command-line สำหรับ compile โปรแกรมง่ายๆคือ
cd StaticFieldTest
javac -d bin src/*.java src/test/*.java
จะได้ .class ไฟล์ที่โฟลเดอร์ bin ซึ่งประกอบไปด้วย 4 ไฟล์คือ
  1. StaticFieldTest.class
  2. test/App1.class
  3. test/App2.class
  4. test/StaticField.class

การทดลองที่ 1: Stand-alone Application

เมื่อ compile เสร็จแล้ว เราจะรันโปรแกรมโดยมีทุกๆคลาสอยู่ใน CLASSPATH (มองจาก working dir) นั่นคือ หากรันโปรแกรมด้วยคำสั่ง
cd bin
java StaticFieldTest
จะได้ผลลัพธ์ดังนี้

SystemClassLoader: sun.misc.Launcher$AppClassLoader@1978b0f9
StaticFieldTest ClassLoader: sun.misc.Launcher$AppClassLoader@1978b0f9
StaticFieldTest ParentClassLoader: sun.misc.Launcher$ExtClassLoader@42aab87f
Running App1
App1 ClassLoader: sun.misc.Launcher$AppClassLoader@1978b0f9
App1 ParentClassLoader: sun.misc.Launcher$ExtClassLoader@42aab87f
Static Initialize StaticField
StaticField ClassLoader: sun.misc.Launcher$AppClassLoader@1978b0f9
StaticField ParentClassLoader: sun.misc.Launcher$ExtClassLoader@42aab87f
StaticField.getSvar(): 0
End of Initialize StaticField
StaticField.getSvar(): 0
StaticField.setSvar(10): 10
StaticField.increaseSvar(): 11
End of App1
Running App2
App2 ClassLoader: sun.misc.Launcher$AppClassLoader@1978b0f9
App2 ParentClassLoader: sun.misc.Launcher$ExtClassLoader@42aab87f
StaticField.getSvar(): 11
StaticField.setSvar(10): 10
StaticField.increaseSvar(): 11
End of App2

กรณีนี้จะเหมือน Stand-alone Application นั่นคือ Bootstrap ClassLoader จะเป็นตัวโหลดคลาสต่างๆเข้ามา เนื่องจากมีคลาสต่างๆอยู่ใน CLASSPATH  อยู่แล้ว shareCL, app1CL และ app2CL ส่งการค้นหามาให้ Parent โหลดก่อน ClassLoader ของ App1, StaticField และ App2 จึงเป็นตัวเดียวกัน โดยผลลัพธ์แสดงให้เห็นว่า App1 และ App2 ใชั StaticField ร่วมกันโดยสังเกตได้จากค่า StaticField เริ่มต้น initialize เพียงครั้งเดียวจากค่า 0 และ App1 เซ็ตค่าให้เป็น 10 จากนั้นก็เพิ่มเป็น 11 ส่วน App2 ก็เริ่มที่ค่า 11 จากผลของ App1 และเซ็ตค่าเป็น 10 แล้วก็เพิ่มค่าเป็น 11

การทดลองที่ 2: ย้าย App1 และ App2 และ Share ไปไว้คนละที่

ทำการย้ายไฟล์ test/App1.class ไปไว้ที่ /tmp/test/app1/test/App1.class ย้าย test/App2.class ไปไว้ที่ /tmp/test/app2/test/App2.class และ test/StaticField.class ไปไว้ที่ /tmp/test/share/test/StaticField.class สังเกตว่า เราจะต้องย้ายไปทั้ง package นะครับ นั่นคือแต่ละคลาสอยู่ใน package test ก็เลยต้องมีโฟลเดอร์ย่อยชื่อ test ติดไปด้วย ไม่อย่างนั้นจะเกิด ClassNotFoundException ครับ เมื่อรันเหมือนเดิม คือ java StaticFieldTest ที่โฟลเดอร์ bin เหมือนเดิม จะได้ผลลัพธ์ดังนี้

SystemClassLoader: sun.misc.Launcher$AppClassLoader@1978b0f9
StaticFieldTest ClassLoader: sun.misc.Launcher$AppClassLoader@1978b0f9
StaticFieldTest ParentClassLoader: sun.misc.Launcher$ExtClassLoader@42aab87f
Running App1
App1 ClassLoader: java.net.URLClassLoader@27021e58
App1 ParentClassLoader: java.net.URLClassLoader@7c163769
Static Initialize StaticField
StaticField ClassLoader: java.net.URLClassLoader@7c163769
StaticField ParentClassLoader: sun.misc.Launcher$AppClassLoader@1978b0f9
StaticField.getSvar(): 0
End of Initialize StaticField
StaticField.getSvar(): 0
StaticField.setSvar(10): 10
StaticField.increaseSvar(): 11
End of App1
Running App2
App2 ClassLoader: java.net.URLClassLoader@5f0a94c5
App2 ParentClassLoader: java.net.URLClassLoader@7c163769
StaticField.getSvar(): 11
StaticField.setSvar(10): 10
StaticField.increaseSvar(): 11
End of App2

จะเห็นว่า App1, App2 และ StaticField ถูกโหลดมาด้วย ClassLoader คนละตัวกัน แต่เนื่องจากผมสร้างให้ StaticField โหลดมาจาก shareCL ที่ใช้ร่วมกันระหว่าง App1 และ App2 จึงทำให้ StaticField มีการ initialize เพียงครั้งเดียวเช่นเดิม และผลลัพธ์ค่าของ StaticField ยังคงเหมือนเดิม คือ App1 กำหนดค่าให้เป็น 11 แล้วพอมาที่ App2 ก็เป็นค่า 11

การทดลองที่ 3: App1 และ App2 ใช้ StaticField คนละตัวกัน

ทำการก็อปปี้ /tmp/test/share/test/StaticField.class ไปไว้ยัง /tmp/test/app1/test/StaticField.class และ /tmp/test/app2/test/StaticField.class  แยกเป็นสองไฟล์ในแต่ละโปรแกรม จากนั้นลบไฟล์ /tmp/test/share/test/StaticField.class ทิ้งไป เพื่อไม่ให้ shareCL สามารถโหลดคลาสมาได้ เมื่อรันเหมือนเดิม คือ java StaticFieldtest ที่โฟลเดอร์ bin เหมือนเดิม จะได้ผลลัพธ์ดังนี้

SystemClassLoader: sun.misc.Launcher$AppClassLoader@6521f956
StaticFieldTest ClassLoader: sun.misc.Launcher$AppClassLoader@6521f956
StaticFieldTest ParentClassLoader: sun.misc.Launcher$ExtClassLoader@1978b0f9
Running App1
App1 ClassLoader: java.net.URLClassLoader@6d15a113
App1 ParentClassLoader: java.net.URLClassLoader@27021e58
Static Initialize StaticField
StaticField ClassLoader: java.net.URLClassLoader@6d15a113
StaticField ParentClassLoader: java.net.URLClassLoader@27021e58
StaticField.getSvar(): 0
End of Initialize StaticField
StaticField.getSvar(): 0
StaticField.setSvar(10): 10
StaticField.increaseSvar(): 11
End of App1
Running App2
App2 ClassLoader: java.net.URLClassLoader@fe9e47
App2 ParentClassLoader: java.net.URLClassLoader@27021e58
Static Initialize StaticField
StaticField ClassLoader: java.net.URLClassLoader@fe9e47
StaticField ParentClassLoader: java.net.URLClassLoader@27021e58
StaticField.getSvar(): 0
End of Initialize StaticField
StaticField.getSvar(): 0
StaticField.setSvar(10): 10
StaticField.increaseSvar(): 11
End of App2

เราจะพบว่า StaticField ที่เรียกใช้โดย App1 กับ App2 กลายเป็นคนละตัวกัน และแยกจากกันโดยสิ้นเชิง ทำให้การ initialize static field และการกำหนดค่า static field ของแต่ละโปรแกรม แยกจากกันโดยเด็ดขาดได้

สรุป

หากต้องการจะหลีกเลี่ยงปัญหาที่ static field มีการใช้งานร่วมกันระหว่างโปรแกรมหลายๆโปรแกรมที่ถูกโหลดมาจาก ClassLoader แตกต่างกันแล้วไม่สามารถควบคุมให้แยกจากกันได้ วิธีการแก้ก็คือ จะต้องวางคลาสไฟล์ หรือ JAR ไฟล์ของ API ตัวนั้นไว้ที่เดียวกับโปรแกรมนั้นๆ เพื่อให้ ClassLoader ของโปรแกรมนั้นมองเห็นโดยตรง จะต้องไม่ไปวางไว้ในโฟลเดอร์ที่แชร์ร่วมกัน

References

วันอังคารที่ 17 มิถุนายน พ.ศ. 2557

HBase 0.96.2 Debian Package

เมื่อสองวันก่อนผมได้เขียนแนะนำการใช้งาน Hadoop Debian Package ที่ผมได้นำมาจัดการแก้ไขข้อผิดพลาดและทำ package ขึ้นมาใหม่ไว้แล้ว เพราะต้องการเผยแพร่ให้กับนักศึกษาของผม วันนี้ผมก็ได้ทำ Debian Package ของ HBase ขึ้นมาอีกไฟล์หนึ่ง เนื่องจาก HBase นั้นไม่มี Debian Package ให้ การจัดสภาพแวดล้อมและการตั้งค่าต่างๆ ยังไม่ค่อยจะสอดคล้องกับระบบของ Debian/Ubuntu มากนัก ผมจึงจำเป็นต้องแก้ไข ก็เลยจัดทำเป็น Debian Package ซะเลย จึงต้องการเผยแพร่และเขียนบันทึกแนะนำการใช้งานไว้ ณ. ที่นี้

Download

ไฟล์ที่ได้จัดทำขึ้นนี้เป็น HBase รุ่น 0.96.2 สามารถดาวโหลดได้ที่ Google Drive (md5sum) ซึ่งเป็นรุ่นที่ทำไว้สำหรับใช้งานร่วมกับ Hadoop 1.x สามารถดาวโหลดได้ที่ Google Drive (md5sum) เช่นกัน

Installation

เมื่อดาวโหลดมาแล้ว จะต้องติดตั้ง Hadoop 1.x ให้เรียบร้อยเสียก่อนนะครับ ซึ่งสามารถอ่านรายละเอียดวิธีการติดตั้งได้จากบทความเก่าที่เขียนไว้ จากนั้น สำหรับผู้ที่ใช้ GUI จะสามารถ Double-Click ที่ไฟล์และทำการติดตั้งได้เหมือนโปรแกรมอื่นๆตามปกติ แต่สำหรับผู้ที่ใช้ command-line interface สามารถติดตั้งได้ด้วยคำสั่ง
sudo dpkg -i hbase_0.96.2-hadoop1-SNORM_all.deb
โปรแกรมติดตั้งจะทำการสร้างกุญแจรหัสสำหรับใช้ในการเชื่อมต่อระหว่าง HBase กับ Hadoop โดยกุญแจรหัสนี้ จะสร้างไว้ในนามของ user hdfs จากนั้น จะทำการสร้างโฟลเดอร์ /hbase และโครงสร้างต่างๆสำหรับเก็บข้อมูลบนระบบไฟล์ HDFS ของ Hadoop แล้วจึงตั้งค่าให้บริการต่างๆทำงานอัตโนมัติ ไฟล์และโฟลเดอร์ที่ติดตั้งลงในเครื่องมีดังนี้
  1. /etc/hbase เป็นโฟลเดอร์ที่เก็บ configuration ไฟล์ต่างๆ เช่น hbase-env.sh ซึ่งเป็นไฟล์สำหรับตั้งค่าสภาพแวดล้อมสำคัญๆที่จำเป็น เช่น JAVA_HOME และ HBASE_CONF_DIR เป็นต้น และไฟล์ hbase-site.xml เป็นต้น
  2. /usr/bin และ /usr/sbin เก็บไฟล์โปรแกรม hbase, start-hbase.sh และ stop-hbase.sh
  3. /usr/share/doc/hbase เป็นโฟลเดอร์ที่เก็บเอกสาร คู่มือของ HBase
  4. /usr/share/hbase เป็นโฟลเดอร์ที่เก็บโปรแกรม HBase
  5. /var/log/hadoop/hbase เป็นโฟลเดอร์ที่เก็บ log ไฟล์ของ hbase ใช้ตรวจสอบการทำงานต่างๆหากมีปัญหา
เมื่อเสร็จสิ้นการติดตั้งแล้ว บริการต่างๆจะถูกสั่งให้ start ไว้แล้ว เราก็สามารถตรวจสอบระบบได้ว่ามีบริการต่างๆทำงานอยู่หรือไม่ ซึ่งทำได้ด้วยคำสั่ง
sudo jps
ซึ่งเป็นคำสั่งตรวจสอบ process ของโปรแกรมภาษา Java ทำงานคล้ายกับคำสั่ง ps ของ Unix นะครับ โดยผลลัพธ์จะต้องปรากฏชื่อบริการที่รันอยู่ทั้งหมด 3 บริการคือ
  1. hbase-zookeeper ชื่อที่แสดงคือ HQuorumPeer
  2. hbase-master ชื่อที่แสดงคือ HMaster เปิดเว็บดูได้ที่เว็บ http://localhost:60010
  3. hbase-regionserver ชื่อที่แสดงคือ HRegionServer เปิดเว็บดูได้ที่เว็บ http://localhost:60030
หากมีบริการครบถ้วน แสดงว่าการติดตั้งสำเร็จ แต่หากไม่ครบถ้วน สามารถตรวจสอบข้อผิดพลาดได้ที่ log ไฟล์นะครับ

Start/Stop Service

หากมีปัญหา ต้องการหยุดบริการเพื่อแก้ไขปัญหา และสั่งให้ทำงานใหม่ สามารถสั่งได้ด้วยคำสั่งดังนี้
  • สำหรับ start บริการทั้งหมดคือ zookeeper master และ regionserver ตามลำดับ
sudo start-hbase
  •  สำหรับ stop บริการทั้งหมด
sudo stop-hbase
แต่หากต้องการ start/stop บริการทีละตัวก็สามารถทำได้ด้วยคำสั่ง
  • สำหรับ start บริการทีละตัว
sudo service ชื่อบริการ start
  • สำหรับ stop บริการทีละตัว
sudo service ชื่อบริการ stop
สำหรับชื่อบริการของ HBase นั้น ใช้ชื่อจากหัวข้อที่แล้วนะครับ และบริการเหล่านี้ควรจะ start ตามลำดับที่เรียงไว้ 1-3 และเวลา stop ก็ควรย้อนลำดับกลับ 3-1

Usage

การใช้งานหลักๆคือคำสั่ง hbase หากสั่งโดยไม่ใส่ค่าใดๆจะแสดงข้อความแนะนำการใช้งานดังนี้
Usage: hbase [<options>] <command> [<args>]
Options:
  --config DIR    Configuration direction to use. Default: ./conf
  --hosts HOSTS   Override the list in 'regionservers' file
Commands:
Some commands take arguments. Pass no args or -h for usage.
  shell           Run the HBase shell
  hbck            Run the hbase 'fsck' tool
  hlog            Write-ahead-log analyzer
  hfile           Store file analyzer
  zkcli           Run the ZooKeeper shell
  upgrade         Upgrade hbase
  master          Run an HBase HMaster node
  regionserver    Run an HBase HRegionServer node
  zookeeper       Run a Zookeeper server
  rest            Run an HBase REST server
  thrift          Run the HBase Thrift server
  thrift2         Run the HBase Thrift2 server
  clean           Run the HBase clean up script
  classpath       Dump hbase CLASSPATH
  mapredcp        Dump CLASSPATH entries required by mapreduce
  version         Print the version
  CLASSNAME       Run the class named CLASSNAME
คำสั่งที่ใช้ประจำคือคำสั่ง hbase shell เพื่อเข้าไปจัดการฐานข้อมูล รายละเอียดการใช้งานอื่นๆสามารถศึกษาเพิ่มเติมได้จาก document ที่ /usr/share/doc/hbase/docs นะครับ

วันเสาร์ที่ 14 มิถุนายน พ.ศ. 2557

Hadoop 1.x Debian Re-Package

นานมาแล้ว ผมได้เคยเขียนถึงปัญหาในการติดตั้ง Hadoop 1.x บน Ubuntu โดยใช้ Debian Package ซึ่งตอนนั้นผมเองก็ได้แก้ปัญหาหลายๆอย่างไปเรียบร้อยแล้ว และได้จัดการ repackage ใหม่ คือทำไฟล์ .deb ขึ้นมาใหม่ที่แก้ไขแล้ว แต่ผมก็ไม่ได้เผยแพร่ให้ใครใช้ เพราะไม่แน่ใจว่าจะมีใครต้องการบ้าง จนมาถึงวันนี้ ผมต้องการให้นักศีกษาของผมได้นำไปใช้ จึงคิดว่าน่าจะเขียนแนะนำไว้ตรงนี้เลยจะดีกว่า จึงเป็นที่มาของบันทึกนี้

Download

ไฟล์ที่ได้ทำการแก้ไขแล้ว สามารถดาวโหลดได้ที่ Google Drive (md5sum)

Installation

เมื่อดาวโหลดมาแล้ว สำหรับผู้ที่ใช้ GUI จะสามารถ Double-Click ที่ไฟล์และทำการติดตั้งได้เหมือนโปรแกรมอื่นๆตามปกติ แต่สำหรับผู้ที่ใช้ command-line interface สามารถติดตั้งได้ด้วยคำสั่ง
sudo dpkg -i hadoop_1.2.1-SNORM_amd64.deb
โปรแกรมติดตั้งจะทำการสร้าง group ชื่อ hadoop และ user ชื่อ mapred และ hdfs ขึ้นมา โดยแบ่งหน้าที่ให้ hdfs ใช้จัดการกับระบบไฟล์ HDFS ส่วน mapred ใช้จัดการกับ job และ task นอกจากนี้ ไฟล์และโฟลเดอร์ที่ติดตั้งลงในเครื่องมีดังนี้
  1. /etc/default/hadoop-env.sh เป็นไฟล์สำหรับตั้งค่าสภาพแวดล้อมสำหรับรัน hadoop ซึ่งตัวแปรสำคัญที่จำเป็น เช่น JAVA_HOME และ HADOOP_CONF_DIR เป็นต้น
  2. /etc/hadoop เป็นโฟลเดอร์ที่เก็บ configuration ไฟล์ต่างๆ เช่น core-site.xml และ mapred-site.xml เป็นต้น
  3. /usr/share/doc/hadoop เป็นโฟลเดอร์ที่เก็บเอกสาร คู่มือของ Hadoop
  4. /usr/share/hadoop เป็นโฟลเดอร์ที่เก็บโปรแกรม hadoop
  5. /var/lib/hadoop เป็นโฟลเดอร์ที่เก็บข้อมูลของ hadoop (ระบบไฟล์ HDFS)
  6. /var/log/hadoop เป็นโฟลเดอร์ที่เก็บ log ไฟล์ของ hadoop ใช้ตรวจสอบการทำงานต่างๆหากมีปัญหา

Setup Single-Node Mode

เมื่อติดตั้งเสร็จแล้ว จะยังไม่สามารถใช้งานได้ทันที เนื่องจากเราจะต้องทำการตั้งค่า Configuration อีกหลายอย่าง รวมทั้งการ Format ระบบไฟล์ HDFS ขึ้นมาก่อน (เสมือนการ Format Harddisk) ซึ่งหากยังไม่คุ้นเคยกับ Hadoop มากนัก ผมขอแนะนำให้ใช้ค่า default คือการตั้งค่าให้ Hadoop ทำงานในโหมด Single Node (ทำงานอยู่บนเครื่องเดียว) โดยใช้คำสั่งดังนี้
sudo hadoop-setup-singlenode.sh
คำสั่งนี้จะแสดงหัวข้อให้เลือกว่าจะทำอะไรบ้าง นั่นคือ
  1. ถามว่าจะใช้ค่า default ของ single-node หรือไม่
  2. ถามว่าจะ format HDFS หรือไม่
  3. ถามว่าจะให้สร้างโครงสร้าง directory ต่างๆบน HDFS ด้วยหรือไม่
  4. ถามว่าจะให้รัน Hadoop ด้วยเลยหรือไม่
  5. ถามว่าจะให้ตั้งค่าให้รัน Hadoop เมื่อ reboot เครื่องด้วยหรือไม่
ให้ตอบ y ทั้งหมดเลย และเนื่องจากในข้อ 4 ระบุให้รัน Hadoop เลย เมื่อเสร็จสิ้นการ setup นี้แล้ว ดังนั้น เมื่อเสร็จสิ้นการ setup เราก็สามารถตรวจสอบระบบได้ว่า มี Hadoop ทำงานอยู่หรือไม่ ซึ่งทำได้ด้วยคำสั่ง
sudo jps
ซึ่งเป็นคำสั่งตรวจสอบ process ของโปรแกรมภาษา Java ทำงานคล้ายกับคำสั่ง ps ของ Unix นะครับ โดยผลลัพธ์จะต้องปรากฏชื่อบริการที่รันอยู่ทั้งหมด 4 บริการคือ
  1. hadoop-namenode ชื่อที่แสดงคือ NameNode เปิดเว็บดูได้ที่เว็บ http://localhost:50070
  2. hadoop-datanode ชื่อที่แสดงคือ DataNode
  3. hadoop-jobtracker ชื่อที่แสดงคือ JobTracker เปิดเว็บดูได้ที่เว็บ http://localhost:50030
  4. hadoop-tasktracker ชื่อที่แสดงคือ TaskTracker
หากมีบริการครบถ้วน แสดงว่าการติดตั้งสำเร็จ

Start/Stop Service

หากเลือกให้บริการต่างๆทำงานอัตโนมัติตอนเปิดเครื่องเลย (ตอบ y ข้อ 5) เราก็ไม่จำเป็นต้องสั่งให้บริการทำงานด้วยตัวเองแต่อย่างใด แต่หากมีปัญหา ต้องการหยุดบริการเพื่อแก้ไขปัญหา และสั่งให้ทำงานใหม่ สามารถสั่งได้ด้วยคำสั่งดังนี้
  • Start Services
sudo service ชื่อบริการ start
  • Stop Services
sudo service ชื่อบริการ stop
สำหรับชื่อบริการของ Hadoop นั้น ใช้ชื่อจากหัวข้อที่แล้วนะครับ และบริการเหล่านี้ควรจะ start ตามลำดับที่เรียงไว้ 1-4 และเวลา stop ก็ควรย้อนลำดับกลับ 4-1

นอกจากนี้ ผมได้เขียน script สำหรับ start/stop ไว้เพื่อให้สะดวกต่อการใช้งานไว้ด้วย คือคำสั่ง start-hadoop และ stop-hadoop ซึ่งสามารถเรียกใช้ได้เลย

Hadoop File System

เมื่อบริการทุกอย่างทำงานได้เรียบร้อยแล้ว และได้ Format HDFS เรียบร้อยแล้ว ซึ่งจะสามารถเข้าไปจัดการไฟล์ต่างๆในระบบได้ด้วยคำสั่ง hadoop แต่เนื่องจากตอนที่เรา Format และมีการเตรียมโครงสร้างไฟล์ต่างๆไว้ในเบื้องต้นนั้น โปรแกรม hadoop-setup-singlenode.sh ได้ใช้ user hdfs เป็นผู้สร้างโครงสร้างไฟล์ทั้งหมด ทำให้สิทธิ์ในการเข้าถึงไฟล์และโฟลเดอร์ต่างๆเป็นของ hdfs ดังนั้น เราควรจะใช้ user hdfs นี้จัดการ ซึ่งสามารถทำได้หลายแบบ เช่น ใช้คำสั่ง
sudo su hdfs
เป็นการเปลี่ยนไปใช้ user hdfs  เสมือนว่าเรา login ด้วย hdfs เมื่อเสร็จงานแล้ว เราจะต้องสั่ง exit เพื่อกลับออกมาเป็น user เดิม

หรืออีกวิธีหนึ่งคือใช้คำสั่ง sudo -u hdfs นำหน้าคำสั่งที่ต้องการทุกครั้ง ซึ่งหากพิมพ์คำสั่ง
sudo -u hdfs hadoop
โดยไม่ระบุค่าใดๆ โปรแกรมจะแสดงข้อความแนะนำการใช้งานดังนี้
Usage: hadoop [--config confdir] COMMAND
where COMMAND is one of:
  namenode -format     format the DFS filesystem
  secondarynamenode    run the DFS secondary namenode
  namenode             run the DFS namenode
  datanode             run a DFS datanode
  dfsadmin             run a DFS admin client
  mradmin              run a Map-Reduce admin client
  fsck                 run a DFS filesystem checking utility
  fs                   run a generic filesystem user client
  balancer             run a cluster balancing utility
  oiv                  apply the offline fsimage viewer to an fsimage
  fetchdt              fetch a delegation token from the NameNode
  jobtracker           run the MapReduce job Tracker node
  pipes                run a Pipes job
  tasktracker          run a MapReduce task Tracker node
  historyserver        run job history servers as a standalone daemon
  job                  manipulate MapReduce jobs
  queue                get information regarding JobQueues
  version              print the version
  jar <jar>            run a jar file
  distcp <srcurl> <desturl> copy file or directories recursively
  distcp2 <srcurl> <desturl> DistCp version 2
  archive -archiveName NAME -p <parent path> <src>* <dest> create a hadoop archive
  classpath            prints the class path needed to get the
                       Hadoop jar and the required libraries
  daemonlog            get/set the log level for each daemon
 or
  CLASSNAME            run the class named CLASSNAME
Most commands print help when invoked w/o parameters.
คำสั่งที่มักใช้บ่อยก็คือ fs ซึ่งจะใช้สำหรับจัดการไฟล์บนระบบ HDFS และมีคำสั่งย่อยซึ่่งจะคล้ายๆกับคำสั่งของ Unix เช่น ls,  mv, cp, chmod, chown ฯลฯ เป็นต้น โดยเมื่อเราสั่ง hadoop fs โดยไม่ระบุค่าใดๆ จะแสดงข้อความแนะนำคำสั่งของ fs ดังนี้
 Usage: java FsShell
           [-ls <path>]
           [-lsr <path>]
           [-du <path>]
           [-dus <path>]
           [-count[-q] <path>]
           [-mv <src> <dst>]
           [-cp <src> <dst>]
           [-rm [-skipTrash] <path>]
           [-rmr [-skipTrash] <path>]
           [-expunge]
           [-put <localsrc> ... <dst>]
           [-copyFromLocal <localsrc> ... <dst>]
           [-moveFromLocal <localsrc> ... <dst>]
           [-get [-ignoreCrc] [-crc] <src> <localdst>]
           [-getmerge <src> <localdst> [addnl]]
           [-cat <src>]
           [-text <src>]
           [-copyToLocal [-ignoreCrc] [-crc] <src> <localdst>]
           [-moveToLocal [-crc] <src> <localdst>]
           [-mkdir <path>]
           [-setrep [-R] [-w] <rep> <path/file>]
           [-touchz <path>]
           [-test -[ezd] <path>]
           [-stat [format] <path>]
           [-tail [-f] <file>]
           [-chmod [-R] <MODE[,MODE]... | OCTALMODE> PATH...]
           [-chown [-R] [OWNER][:[GROUP]] PATH...]
           [-chgrp [-R] GROUP PATH...]
           [-help [cmd]]
Generic options supported are
-conf <configuration file>     specify an application configuration file
-D <property=value>            use value for given property
-fs <local|namenode:port>      specify a namenode
-jt <local|jobtracker:port>    specify a job tracker
-files <comma separated list of files>    specify comma separated files to be copied to the map reduce cluster
-libjars <comma separated list of jars>    specify comma separated jar files to include in the classpath.
-archives <comma separated list of archives>    specify comma separated archives to be unarchived on the compute machines.
The general command line syntax is
bin/hadoop command [genericOptions] [commandOptions]
เช่น หากเราต้องการแสดงรายชื่อไฟล์และโฟลเดอร์ต่างๆที่โฟลเดอร์ root เหมือนคำสั่ง ls / สามารถสั่งได้ดังนี้
sudo -u hdfs hadoop fs -l /
หากต้องการอัพโหลดไฟล์จากเครื่องเข้าสู่ระบบ สามารถใช้คำสั่ง -put เช่น
sudo -u hdfs hadoop fs -put local.txt /tmp/hdfs.txt
หมายถึงการอัพโหลดไฟล์ชื่อ local-test.txt จากเครื่องของเรา เข้าสู่ระบบที่โฟลเดอร์ /tmp โดยเปลี่ยนเป็นชื่อ hdfs.txt เป็นต้น
หากต้องการดาวโหลดไฟล์จากระบบมาที่เครื่อง สามารถใช้คำสั่ง -get เช่น
sudo -u hdfs hadoop fs -get /tmp/hdfs.txt hdfs2.txt
หมายถึงการดาวโหลดไฟล์ชื่อ hdfs.txt ที่โฟลเดอร์ /tmp จากระบบมาสู่เครื่อง โดยเปลี่ยนชื่อเป็น hdfs2.txt เป็นต้น
sudo -u hdfs hadoop fs -rm /tmp/hdfs.txt
หมายถึงการลบไฟล์ /tmp/hdfs.txt บนระบบ เป็นต้น ส่วนคำสั่งอื่นๆก็ใช้งานลักษณะเดียวกัน

วันพฤหัสบดีที่ 22 พฤษภาคม พ.ศ. 2557

หนังสือเก่ามีคุณค่าทางใจ

หนังสือเล่มนี้ซื้อมาตอนเรียนปริญญาตรี ปี 3 ขึ้นปี 4 (24 ปีมาแล้ว) แม้ว่าจะไม่ได้เขียน Assembly อีกเลยหลังจากเรียนจบ แต่ก็ได้นำความรู้มาใช้อยู่บ้างตอนทำชุมสายโทรศัพท์ขนาดเล็ก แม้ตอนนั้นจะเขียนด้วยภาษา C ควบคุมฮาร์ดแวร์ แต่พอเจอปัญหาลึกๆก็ถึงกับต้อง Disassembly ดู binary code ที่ Compiler แปลงมากันเลยทีเดียว

ถึงวันนี้เทคโนโลยีจะเปลี่ยนไปมากแล้ว หนังสือเล่มนี้ก็ไม่มีประโยชน์อีกต่อไป แต่เห็นมันทีไร ก็ทิ้งไม่ลงจริงๆ เพราะมันมีค่าทางใจที่หาค่าไม่ได้

วันอังคารที่ 6 พฤษภาคม พ.ศ. 2557

Ubuntu 14.04 LTS: ปัญหา Remote Desktop VNC

ปกติแล้วผมใช้ VNC ทำ Remote Desktop มาที่เครื่องเป็นประจำ แต่พออัพเกรดเครื่องเป็น 14.04 LTS แล้ว ปรากฏว่ามีปัญหา เข้าไม่ได้ ผมพยายามทดสอบหลายๆอย่างแล้ว พบว่าทุกอย่างเป็นปกติดี ไม่ว่าจะเป็นเรื่อง network หรือ security ต่างๆ ไม่ได้เป็นปัญหา เพราะมี VNC Viewer บางตัวใช้งานได้

ปัญหาที่ VNC Viewer?

ปกติแล้วบน Ubuntu ผมจะใช้ SSL/SSH VNC Viewer หรือที่เรียกชื่อโปรแกรมว่า ssvnc ซึ่งไม่เคยมีปัญหาเลย แต่คราวนี้ไม่ว่าจะเซ็ตค่าอย่างไรก็ไม่สามารถเข้าได้ สุดท้ายผมได้ทดลองโปรแกรม Remote Desktop Viewer หรือที่เรียกชื่อโปรแกรมว่า vinagre ตัวนี้กลับใช้งานได้ไม่มีปัญหา เพียงแต่ไม่ค่อยมีลูกเล่นให้ตั้งค่าอะไรมากมายนัก และการทำงานค่อนข้างช้า แต่บนเครื่อง iPad ของผมนั้น ผมใช้ iSSH ซึ่งก็มีปัญหาเช่นเดียวกัน และบน Android ก็คาดว่าคงจะมีปัญหา

หลังจากพยายามทดสอบและติดตามกระทู้ที่เกี่ยวข้องแล้ว ก็พบว่า ปัญหาอยู่ที่การตั้งค่า default ของ Gnome บน Ubuntu 14.04 เกี่ยวกับ Remote Desktop ไว้ว่า จะต้องใช้การเชื่อมต่อแบบเข้ารหัสเท่านั้น ซึ่งการใช้งานปกติของผมจะใช้ผ่าน SSH อยู่แล้ว ดังนั้น VNC จึงไม่เข้ารหัส และ VNC Viewer หลายๆตัวที่ใช้ก็เป็นลักษณะนี้ พอโดนบังคับให้ต้องเข้ารหัส ก็เลยทำให้ VNC Viewer หลายๆตัวใช้งานไม่ได้

วิธีแก้

ในกระทู้รายงาน Bug#1290666 เขาได้แนะนำให้ตั้งค่า org.gnome.desktop.remote-access ใหม่ โดย disable ค่า require-encryption ซึ่งจะต้องใช้เครื่องมือช่วยตั้งค่าชื่อ dconf-editor ช่วย ผมจึงต้องติดตั้งโปรแกรมนี้เสียก่อนด้วยคำสั่ง
sudo apt-get install dconf-editor
จากนั้น ก็เรียกใช้งานโปรแกรม dconf-editor ดังภาพ
เมื่อ disable ค่า require-encryption แล้ว ทุกอย่างก็กลับมาใช้งานได้เป็นปกติ

วันพุธที่ 30 เมษายน พ.ศ. 2557

Ubuntu 14.04 LTS: ปัญหา Subversion Server ทำงานไม่เสถียร

วันก่อนผมได้เล่าให้เพื่อนๆฟังไปแล้ว เกี่ยวกับปัญหาการใช้งาน Subversion บน Eclipse ซึ่งเมื่อผมทำการ Checkout โปรเจ็คออกมา บางโปรเจ็คก็ไม่มีปัญหาอะไร แต่บางโปรเจ็คก็เกิดอาการดาวโหลดบางไฟล์ไม่ได้ โดยฟ้อง
RA layer request failed
GET ... Internal Server Error

เกี่ยวอะไรกับ MySQL?

ทีแรกนึกว่ามีบางไฟล์เสียหาย แต่เมื่อผมทดลองซ้ำๆหลายๆรอบ ดูเหมือนว่าอาการจะไม่แน่นอน ไม่ซ้ำไฟล์เดิม และบางครั้ง Error ก็เปลี่ยนไปเป็น server close connection ผมจึงคิดว่าปัญหามันน่าจะเกิดที่ Apache Web Server มากกว่า โดยเมื่อเข้าไปดู Log ไฟล์ ก็แทบจะไม่มีอะไรบอกเกี่ยวกับ svn เลย แต่กลับมีข้อความเกี่ยวกับ MySQL เช่น query ไม่ได้ database connection lost อะไรทำนองนั้น ทีแรกผมก็ไม่ได้เอะใจ เพราะคิดว่าไม่เกี่ยวข้องกับ Subversion สักหน่อย แต่ทำไมมันดูเหมือนจะเกี่ยวกัน เพราะฟ้อง warning ตรงตอน GET ไฟล์ แต่เอาเถอะโปรแกรมรุ่นใหม่อาจจะมีการเปลี่ยนไปใช้ฐานข้อมูล MySQL ร่วมด้วยก็ได้ ผมก็เลยพุ่งเป้าไปที่ MySQL

ทีแรกก็ลองปรับค่า max_allowed_packet จาก 16M เป็น 64M ค่า max_connection จาก 100 เป็น 200 ค่า query_cache_limit จาก 1M เป็น 16M ด้วยเหตุผลที่ว่า อาการคล้ายๆกับ MySQL ทำงานไม่ได้ อาจจะเพราะตอน Checkout ไฟล์จำนวนมากๆ เกิด connection จำนวนมาก (ถ้าเป็น 1 ไฟล์ต่อ 1 connection) ก็จะเกิดปัญหาประมาณนี้ ซึ่งก็ช่วยให้สามารถ Checkout ไฟล์ได้มากขึ้น แต่สุดท้ายก็ตายทุกครั้ง

ผมเข้าไปดูค่าใน /var/log/mysql/error.log แล้ว ก็ไม่พบความผิดปกติอะไรเลย ดูเหมือน MySQL จะทำงานได้เป็นปกติดีทุกประการ ผมก็คิดไม่ออกว่ามันเกิดอะไรขึ้น และคิดว่า มันต้องมีปัญหาที่คำสั่ง SQL เพราะ error ใน Apache Web Server ระบุว่า query ไม่ได้ บางครั้งมีคำว่า "SELECSELECT ..." เหมือนคำสั่งมันกลายเป็นขยะไป ทำให้ระบบรวนไปหมด ผมจึงต้องลงลึกเข้าไปในระดับทุกๆคำสั่งที่ส่งให้กับ MySQL เลยว่ามีปัญหาอะไร ผมจึงไปตั้งค่า general_log_file และ general_log (ปกติจะปิดไว้) เพื่อดูค่าการทำงานในแต่ละคำสั่งของ MySQL ซึ่งจะบันทึกไว้ที่ /var/log/mysql/mysql.log (ปกติจะไม่มีไฟล์นี้อยู่)

สุดท้ายผมก็เห็นคำสั่ง
SELECT xxx FROM profiles WHERE login_name=? AND disabledtext = ''
ก็ได้ถึงบางอ้อว่า จริงๆแล้ว คำสั่งนี้เป็นคำสั่งที่เกิดจาก Apache Module ที่มีชื่อว่า mod_auth_mysql เป็นโมดูลที่เกี่ยวกับ การ Authentication เข้าใช้งาน และ Authorization สำหรับยืนยันสิทธิ์ในการเข้าถึงไฟล์ต่างๆของ Subversion  ซึ่งผมเลือกใช้โมดูลนี้เพื่อตั้งค่าให้มีการใช้ account จากฐานข้อมูล MySQL ของ Bugzilla ดังนั้น ทุกๆครั้งที่มีการดาวโหลดไฟล์ จะมีการตรวจสอบสิทธิ์ทุกครั้ง และทำให้เกิดการ query ด้วยคำสั่งข้างบนซ้ำๆจำนวนมาก อาจจะทำให้เกิดอาการไม่เสถียร

Multi-Processing Module (MPM) Prefork vs. Worker

เมื่อรู้ว่าปัญหาเกิดจากโมดูล mod_auth_mysql ทำงานไม่เสถียรแล้ว ผมจึงคิดว่าน่าจะต้องปรับค่าที่เกี่ยวกับการจัดการประมวลผลโมดูล หรือที่เรียกว่า MPM ซึ่งเมื่อตรวจสอบ Apache Web Server ก็พบว่า มีการตั้งค่าแตกต่างไปจากรุ่นก่อน (Ubuntu 12.04) คือ การแยก MPM ออกเป็นโมดูล และเลือกตั้งค่าไว้เป็น mpm_event ในขณะที่ค่าเก่าที่ผมเคยตั้งไว้เป็น mpm_prefork ผมเองก็จำไม่ได้แล้วว่าทำไมถึงใช้ prefork แต่ไหนๆก็ไหนๆแล้ว ผมก็เลยทดลองปรับค่าดู ซึ่งค่าเดิมที่ตั้งไว้คือ default ตามข้างล่างนี้
<IfModule mpm_event_module>
    StartServers                    2
    MinSpareThreads          25
    MaxSpareThreads         75
    ThreadLimit                   64
    ThreadsPerChild            25
    MaxClients                   150
    MaxRequestsPerChild      0
</IfModule>
เมื่อทดลองปรับค่า เช่น MaxClient ลดให้น้อยลง MaxRequestsPerChild กำหนดให้เป็นค่า 1000 แทนที่จะเป็น 0 ซึ่งหมายถึงไม่จำกัด ค่า MaxSpareThreads ลดลง ค่า ThreadLimit ลดลง ThreadsPerChild ลดลง ผลปรากฏว่า เมื่อ Checkout การทำงานค่อนข้างดีขึ้น แต่สุดท้ายแล้วก็นิ่งไปนาน แล้วเกิด connection timeout ขี้น เมื่อดูใน Log ไฟล์ ไม่มีข้อมูลใดๆ เพราะ Apache Web Server ตายไปเรียบร้อยแล้ว ผมก็พยายามทดลองปรับค่าต่างๆแล้ว สุดท้ายสรุปว่าโมดูล mod_auth_mysql ไม่ใช่ thread safe โมดูล (ในขณะที่ข้อมูลจากบางเว็บไซต์ระบุว่าเป็น thread safe) ไม่สามารถใช้งานกับ mpm_worker หรือ mpm_event ได้

prefork เป็นการประมวลผลแบบไม่ใช้ thread โดย root process จะมีการแตก child process ออกมา และแต่ละ child process จะดูแล connection หลาย connection ต่อ process

worker เป็นการประมวลผลแบบใช้ thread โดย root process จะมีการแตก child process ออกมา และแต่ละ child process จะแตก thread ออกมาเพื่อดูแลแต่ละ connection

prefork เหมาะกับการทำงานที่รองรับโมดูลที่ไม่ได้ออกแบบมาให้เป็น thread safe และสำหรับประสิทธิภาพแล้ว ทั้ง prefork และ worker ทำงานได้พอๆกัน แต่ prefork จะใช้ทรัพยากรณ์ หน่วยความจำมากกว่า worker ส่วน event นั้นเป็น worker อีกรูปแบบหนึ่งที่จะจัดการ thread ได้ดีขึ้น

สรุปว่า mod_auth_mysql ไม่เป็น thread safe ต้องใช้กับ prefork เท่านั้น ซึ่งเมื่อผมทดลองปรับไปใช้ prefork ปัญหาทุกอย่างก็หมดไป นิ่งเสถียรดีมาก แต่ด้วยความคิดที่ว่า event มันดูดีกว่า prefork นะ ใช้ทรัพยากรณ์น้อยกว่า ผมจึงคิดว่า เราน่าจะหาทางใช้ event นะ

Apache DBD Module

ผมค้นไปค้นมาก็พบว่า Apache มีโมดูลที่ทำงานกับฐานข้อมูล SQL อยู่แล้ว ชื่อว่า mod_dbd ดังรายละเอียดในหน้า Apache DBD API ที่น่าสนใจคือ
  1. ไม่ยึดติดกับฐานข้อมูลใดๆ โดยจะต้องติดตั้ง driver สำหรับแต่ละฐานข้อมูลเพิ่ม เช่นฐานข้อมูลที่มี driver แล้วคือ MSSQL, SyBase, MySQL, Oracle, PostgreSQL, SQLite, และ ODBC
  2. ใช้กับ MPM ได้หลายแบบทั้งแบบ threaded หรือ non-threaded
  3. จัดการ connection pooling แบบไดนามิกส์เพื่อรองรับโปรแกรมใหญ่ๆได้
และนอกจากนี้ยังมีโมดูล mod_authn_dbd สำหรับการทำ Authentication และโมดูล mod_authz_dbd สำหรับการทำ Authorization ด้วย ทำให้ตอบโจทย์ที่ผมคิดไว้พอดี ผมจึงตัดสินใจทดสอบการใช้งานดู โดยติดตั้งโมดูลต่างๆ และแก้ไข config ไฟล์ ตามตัวอย่างใน document ของ mod_authn_dbd ดังนี้
# mod_dbd configuration
# UPDATED to include authentication cacheing
DBDriver pgsql
DBDParams "dbname=apacheauth user=apache password=xxxxxx"
DBDMin  4
DBDKeep 8
DBDMax  20
DBDExptime 300
<Directory /usr/www/myhost/private>
  # mod_authn_core and mod_auth_basic configuration
  # for mod_authn_dbd
  AuthType Basic
  AuthName "My Server"
  # To cache credentials, put socache ahead of dbd here
  AuthBasicProvider socache dbd
  # Also required for caching: tell the cache to cache dbd lookups!
  AuthnCacheProvideFor dbd
  AuthnCacheContext my-server
  # mod_authz_core configuration
  Require valid-user
  # mod_authn_dbd SQL query to authenticate a user
  AuthDBDUserPWQuery "SELECT password FROM authn WHERE user = %s"
</Directory>
ในตัวอย่างนี้ จะมีการใช้ AuthBasicProvider โดยเน้นว่าให้ใช้ socache เพื่อทำ cache ให้กับ credential ด้วย ผมก็ได้ทำตามคือ ติดตั้ง socache เพิ่ม และที่สำคัญคือ AuthDBUserPWQuery ที่ต้องสั่ง query รหัสผ่านสำหรับผู้ใช้มาเพื่อตรวจสอบ โดยใส่ %s ไว้ตรงที่เป็นชื่อผู้ใช้ที่จะกรอกผ่าน browser มา ผลการรันปรากฏว่าเกิด Segmentation fault error ครับ Apache สตาร์ทไม่ขึ้น ผมจึงค่อยๆถอดบางคำสั่งที่ไม่จำเป็นออก และสุดท้ายพบว่าปัญหาอยู่ที่ AuthnCacheProvideFor และ AuthnCacheContext ครับ เมื่อตัดสองคำสั่งนี้ออก Apache ก็ทำงานได้ตามปกติ

Password Format

จากคำอธิบายของคำสั่ง AuthDBUserPWQuery ระบุว่า ผลของการ query จะนำเอาเฉพาะข้อความที่อยู่ใน row แรก column แรกเท่านั้นมาพิจารณาเป็นรหัสผ่านในรูปแบบรหัสที่เข้ารหัสแล้ว ซึ่งวิธีพิจารณามีสองแบบคือ แบบ Basic หรือ แบบ Digest ตามที่ได้ระบุ provider ไว้ (ผมขอไม่พูดถึงแบบ Digest นะครับ) เช่นในกรณีนี้ ผมใช้ AuthBasicProvider มีรายละเอียดรูปแบบของข้อมูลรหัสที่จะนำมาเปรียบเทียบตามที่ระบุไว้ชัดเจนในเอกสารคู่มือของ Apache เรื่อง Password Format โดยแบบ Basic มีอยู่ 5 รูปแบบคือ
  1. bcrypt: ข้อความ "$2y$" + ข้อความที่เข้ารหัสด้วยอัลกอริทึม crypt_blowfish
  2. MD5: ข้อความ "$apr1$" + ข้อความที่เข้ารหัสด้วยอัลกอริทึม MD5 ในแบบของ Apache เอง (ไม่เหมือนกับ MD5 อื่นๆ ผมเสียเวลาเข้าใจผิดทดสอบอยู่นานเหมือนกัน)
  3. SHA1: ข้อความ "{SHA}" + ข้อความที่เข้ารหัสด้วย Base64 ของอัลกอริทึม SHA-1 (ไม่ปลอดภัย)
  4. CRYPT: ข้อความที่เข้ารหัสด้วยฟังก์ชัน crypt บนระบบ Unix เท่านั้น (ไม่ปลอดภัย)
  5. PLAIN: ข้อความที่ไม่เข้ารหัส เฉพาะบนระบบ Windows และ Netware (ไม่ปลอดภัย)
อ๊ะๆ สังเกตเห็นอะไรไหมครับ รหัสผ่านที่เข้ารหัสแล้ว จะมีการใส่สัญลักษณ์พิเศษไว้เพื่อให้รู้ว่าเข้ารหัสด้วยวิธีไหน แต่ไม่ต้องกังวลนะครับ อัลกอริทึมที่เข้ารหัสพวกนี้ เป็นอัลกอริทึมที่ออกแบบมาให้ย้อนกลับได้ยาก ทางเดียวที่ง่ายกว่าคือ ต้องหารหัสมาเข้ารหัสด้วยวิธีเดียวกันแล้วได้ค่าที่ตรงกันจึงจะบอกได้ว่าเป็นรหัสที่ถูกต้อง ใครสนใจเรื่องรูปแบบของรหัสสามารถอ่านเพิ่มเติมจากลิ้งค์ที่ให้ไว้ได้นะครับ ในนั้นมีการแนะนำการใช้คำสั่ง htpasswd และคำสั่ง openssl เพื่อทดสอบดูว่า รหัสที่เข้าด้วยอัลกอริทึมแบบต่างๆแล้วได้รูปแบบเป็นอย่างไร

กลับมาเข้าเรื่องปัญหาของผมต่อนะครับ จากฐานข้อมูลผู้ใช้ใน MySQL ของ Bugzilla รหัสผ่านที่เข้ารหัสไว้แล้วนั้น มีรูปแบบไม่เหมือนกับที่ Apache ระบุไว้ ดังนั้น คำสั่งที่จะ query รหัสผ่านมานั้น จะต้องมีการจัดรูปแบบใหม่ให้ถูกต้อง โดยรูปแบบของ Bugzilla จะอยู่ในแบบ
รหัสที่เข้ารหัสแล้ว + "{" + ชื่อ Digest อัลกอริทึม + "}"
เช่น (ขอสมมติรหัสนะครับ)
123456789ABCDEFGHIjklmnopqrstuvwxyZ{MD5}
ถ้าหากผม query มาตรงๆ ก็จะทำให้ mod_authn_dbd ตรวจสอบรหัสผ่านไม่ถูก เพราะถ้าเป็น MD5 จะต้องมีรหัสเป็นประมาณ
$apr1$123456789ABCDEFGHIjklmnopqrstuvwxyZ
ซึ่งก็ไม่ใช่เรื่องยากสักเท่าไรนักที่จะใช้คำสั่ง substring ของ SQL ตัดและต่อคำเข้าไป แต่ปัญหาใหญ่ของผมตอนนี้คือ รหัส MD5 แบบทั่วไปที่ใช้อยู่ใน Bugzilla ซึ่งเขียนด้วยภาษา perl โดยใช้ Digest::MD5 นั้นไม่เหมือนกับ Apache MD5 หรือที่เรียกว่า APR1 ทำให้ผมต้องไปควานหาว่า perl มีอัลกอริทึมเข้ารหัสแบบไหนให้ใช้บ้าง ซึ่งถ้าเป็นไปตาม man page ของ Digest ก็คือมีดังนี้ (เรียงตามลำดับความเร็ว)
Algorithm   Size  Implementation               MB/s
MD4         128   Digest::MD4 v1.3            165.0
MD5         128   Digest::MD5 v2.33            98.8
SHA-256     256   Digest::SHA2 v1.1.0          66.7
SHA-1       160   Digest::SHA v4.3.1           58.9
SHA-1       160   Digest::SHA1 v2.10           48.8
SHA-256     256   Digest::SHA v4.3.1           41.3
Haval-256   256   Digest::Haval256 v1.0.4      39.8
SHA-384     384   Digest::SHA2 v1.1.0          19.6
SHA-512     512   Digest::SHA2 v1.1.0          19.3
SHA-384     384   Digest::SHA v4.3.1           19.2
SHA-512     512   Digest::SHA v4.3.1           19.2
Whirlpool   512   Digest::Whirlpool v1.0.2     13.0
MD2         128   Digest::MD2 v2.03             9.5
Adler-32     32   Digest::Adler32 v0.03         1.3
CRC-16       16   Digest::CRC v0.05             1.1
CRC-32       32   Digest::CRC v0.05             1.1
MD5         128   Digest::Perl::MD5 v1.5        1.0
CRC-CCITT    16   Digest::CRC v0.05             0.8
ผมควานหา package อื่นๆที่เกี่ยวข้องหมดแล้วพบคำสั่งเข้ารหัส Apache MD5 อยู่ใน libcrypt-passwdmd5-perl แต่นั่นเป็น Crypt::PasswdMD5 ไม่ใช่ Digest ซึี่งหากจะใช้คำสั่งนี้ ผมจะต้องเข้าไปแก้ Bugzilla ซึ่งเป็นวิธีที่ไม่น่าทำสักเท่าไรนัก (แต่จริงๆแล้วก็อยากจะลองดูเหมือนกันนะ ☺) จึงต้องตัดสินใจลดระดับความปลอดภัยลงมาเป็น SHA-1

สรุป

ความจริงแล้วปัญหายังไม่ได้จบแค่นี้นะครับ แต่เนื่องจากผมไม่แน่ใจว่า การเปิดเผยอะไรมากไปกว่านี้ จะทำให้ Server ของผมโดนของหรือเปล่า ผมยิ่งไม่ค่อยมีเวลาคอยตรวจดูซะด้วยสิ จึงขอจบเรื่องนี้ไว้แค่นี้ หากใครอยากทราบประสบการณ์เชิงลึก ก็มาคุยกันได้นะครับ ยินดีเล่าให้ฟัง(ถ้าสัญญาว่าจะไม่มาแฮ็คเครื่องผม ☺) เอ้า สรุปว่า ปัญหาต่างๆก็แก้ไปได้ด้วยดี มีความปลอดภัยพอสมควร และเสถียรแล้วสำหรับ Subversion server

วันจันทร์ที่ 28 เมษายน พ.ศ. 2557

Ubuntu 14.04 LTS: ปัญหา Subversion บน Eclipse 3.8

หลังจากที่ผมติดตั้ง Ubuntu 14.04 LTS ไปประมาณหนึ่งสัปดาห์ ตอนนี้ก็ได้ฤกษ์ใช้งาน Eclipse เขียนโปรแกรมสักที และเนื่องจากโปรแกรมหลายๆตัวที่ผมเขียน ผมใช้ Subversion เก็บซอสโค้ดไว้ จึงทำการ Import โปรแกรมมา แต่ปรากฏว่าเกิดปัญหาขึ้น 2 เรื่อง

ปัญหาที่ 1 SVN มี 2 เมนู

เมื่อเลือกเมนู File/Import จะปรากฏหน้าต่าง ซึ่งปกติแล้ว เราก็จะเลือกหัวข้อย่อย SVN โดยจะมีหัวข้อ Checkout Projects from SVN ดังภาพ (ขออภัยที่ผมลืมเก็บภาพตอนที่มี 2 หัวข้อ) แต่ปรากฏว่ามี SVN อีกอันหนึ่ง มีหัวข้อย่อยคล้ายๆกัน ผมก็เกิดความสงสัยขึ้น แอ๊ะเกิดอะไรขึ้นกับ Eclipse และเมื่อเปิดไปที่เมนู Window/Preferences/Team เพื่อดูการตั้งค่าของ Subversion ก็ปรากฏว่า มีหัวข้อ SVN อยู่ 2 หัวข้อเช่นกัน

หลังจากที่ตรวจสอบ Plug-In ต่างๆที่ติดตั้ง (เลือกลิ้งค์ Uninstall or update ที่เมนู Window/Preferences/Install/Update) ก็ได้ข้อสรุปว่า ผมติดตั้ง Eclipse Team Provider สำหรับ Subversion ไปสองตัวคือ Subversive และ Subclipse ซึ่งปกติแล้ว ผมจะใช้ Subversive อยู่เป็นประจำ เพราะมี Connector หลายแบบให้เลือกใช้ แต่เอ Subclipse มันมาได้ไง? เราเผลอไปติดตั้งตอนไหนนะ? จะเอาออกดีหรือเปล่า? เพื่อให้มั่นใจ ผมจึงศึกษาข้อมูลดูจาก StackOverflow และพบว่า เขาแนะนำให้ใช้ Subclipse ดีกว่า เพราะทีมพัฒนา(มาตอบเอง) บอกว่า ทีมงานเขา Active กว่า Subversive ที่มีผู้พัฒนาอยู่น้อย และยังติดวงเล็บต่อท้ายว่า Incubation ตั้งนานแล้ว นอกจากนี้ เขายังทำงานร่วมกับทีมที่พัฒนา Subversion บนระบบอื่นๆ เช่น AnkhSVN และ TortoiseSVN อย่างไกล้ชิด เพื่อให้สามารถทำงานเข้ากันได้

โอเคครับ ผมก็เลยตัดสินใจ ถอน Subversive ออก แต่ตอนที่ถอนออก เราจะต้องไล่ถอน Plug-In ต่างๆที่เกี่ยวข้องออกให้หมดนะครับ ไม่อย่างนั้นแล้ว เมนู SVN ของ Subversive จะยังคงปรากฏให้เห็นอยู่ อาจจะทำให้เกิดความสับสนในการใช้งานได้ เพราะมันจะคล้ายๆกัน สำหรับ Plug-In ที่เกี่ยวข้องกับ Subversive ก็คือ
  • Subversive SVN Team Provider (Incubation)
  • Subversive SVN JDT Ignore Extensions (Optional) (Incubation)
  • Subversive SVN Integration for the Mylyn Project (Optional) (Incubation)
  • Subversive Revision Graph (Optional) (Incubation)
  • Subversive SVN Connectors
  • Native JavaHL x.x Implementation (Optional)
  • SVNKit x.x.x Implementation (Optional)

ปัญหาที่ 2 Subclipse 1.8 ไม่สามารถทำงานร่วมกับ JavaHL 1.8 ได้

เมื่อเหลือแต่ Subclipse แล้ว ผมก็เริ่มต้น Import โปรแกรมใหม่ แต่ก็ไม่สำเร็จเนื่องจาก Subclipse ฟ้องว่าไม่สามารถทำงานร่วมกับ JavaHL ที่มีอยู่ได้ ต้องใช้รุ่น 1.7.x ดังภาพ
เมื่อตรวจสอบระบบแล้ว พบว่า Ubuntu 14.04 มี Package ของ Subclipse รุ่น 1.8 และ JavaHL รุ่น 1.8 เช่นกัน แต่จากคำอธิบายในบทความเรื่อง Wiki ของ JavaHL ระบุรุ่นที่ทำงานร่วมกันไว้คือ
Subclipse VersionSVN/JavaHL Version
1.10.x1.8.x
1.8.x1.7.x
1.6.x1.6.x
1.4.x1.5.x
1.2.x1.4.x
1.0.x1.4.x
นั่นหมายความว่า ถ้าใช้ Subclipse 1.8 ก็จะต้องติดตั้ง JavaHL 1.7 ซึ่ง Ubuntu 14.04 ไม่มีให้ และถ้าจะใช้ JavaHL 1.8 ตามที่ Ubuntu 14.04 มีให้ ก็ต้องอัพเกรด Subclipse เป็น 1.10 โดยทีแรกผมพยายามจะหา JavaHL 1.7 มาติดตั้งแล้ว แต่เกิดปัญหาว่า ในระบบอาจจะมีโปรแกรมอื่นๆที่ต้องใช้งาน JavaHL ด้วยเช่นกัน ไม่ใช่มีเพียง Eclipse เท่านั้น ทำให้เกิดปัญหา dependency ต่างๆพันกันไปหมด สุดท้ายผมก็เลยตัดสินใจ อัพเกรด Subclipse โดยเข้าไปที่ Install New Software และเพิ่ม Repository สำหรับ Subclipse รุ่น 1.10.x ตรงช่อง Work with ด้วย URL http://subclipse.tigris.org/update_1.10.x เท่านี้ก็จะปรากฏ Plug-In รุ่น 1.10 และส่วนที่เกี่ยวข้องอื่นๆมาให้เราเลือกติดตั้งได้ทันทีดังภาพ
เมื่อติดตั้งแล้ว ก็จะสามารถใช้งาน Subversion ได้ตามปกติ

วันศุกร์ที่ 21 มีนาคม พ.ศ. 2557

Setup Android on Ubuntu

ตั้งแต่ที่ผมได้ทดลองเขียนโปรแกรมบน Android ครั้งแรกก็นานมากแล้ว หลายๆอย่างเปลี่ยนไปเร็วมาก จาก Android เวอร์ชั่น 2.3 มาตอนนี้เป็นเวอร์ชั่น 4.4 ดูตัวเลขแล้วก็ไม่ได้มากมายอะไรเลย แต่ถ้านับเป็นรุ่นของ Platform API ก็คือตั้งแต่ Platform API 10 (2.3.3) มาเป็น Platform API 19 (4.4.2)  ผ่านไปตั้งเกือบ 10 รุ่น ทำให้หลายๆคนก็ท้อแท้ไปเลยก็มี เพราะว่าตามไม่ทันจริงๆ สำหรับผมนั้น ขอเรียกว่าตามดูอยู่ห่างๆก็แล้วกัน พอดียังไม่ค่อยจะมีงานที่ต้องใช้ จึงยังไม่ทุ่มเทเวลาให้มันมากนัก วันนี้พอจะมีเวลา ก็เลยลองกลับมารื้อฟื้นความจำสักหน่อย แต่บังเอิญเครื่องที่ใช้อยู่ไม่ได้ลงโปรแกรมไว้ ก็เลยต้องเริ่มต้นใหม่ตั้งแต่การติดตั้งกันเลย

เครื่องที่ผมใช้เป็น Ubuntu 12.04 LTS ติดตั้ง Eclipse 3.7.2 ซึ่งเป็น Eclipse ที่มากับ Ubuntu นะครับ ผมจะเน้นใช้ Package ที่มาพร้อมกับ OS มากกว่าการดาวโหลด Eclipse มาใหม่ๆ เพราะมักจะเจอปัญหาเข้ากันไม่ได้กับ Package โน่น นั่น นี่ ทำให้เราปวดหัว แต่ถ้าจำเป็นจริงๆ ผมก็จะใช้ตัวใหม่นะครับ

ADT (Android Developer Tools)

Android Developer Tools หรือเรียกย่อๆว่า ADT นั้น เป็นเครื่องมือสำหรับพัฒนาโปรแกรมบน Android ซึ่งทั้งหมดจะประกอบไปด้วย
  • Eclipse + ADT Plugin: Eclipse IDE ที่ติดตั้ง ADT Plugin เพื่อให้ Eclipse เชื่อมต่อกับ Tools ต่างๆของ Android ได้
  • Android SDK Tools: ประกอบไปด้วยเครื่องมือต่างๆสำหรับการพัฒนา ทดสอบ และดีบั๊กโปรแกรม เช่น ตัว emulator เป็นต้น เครื่องมือเหล่านี้จะอยู่ในโฟลเดอร์ <ANDROID_SDK>/tools
  • Android Platform Tools: ประกอบไปด้วยเครื่องมือที่ขึ้นอยู่กับแพล็ตฟอร์มสำหรับการพัฒนา ทดสอบ และดีบั๊กโปรแกรม เช่น ตัว debugger เป็นต้น เครื่องมือเหล่านี้จะอยู่ในโฟลเดอร์ <ANDROID_SDK>/platform-tools
  • Android Platform: ประกอบไปด้วย API Library ต่างๆของ Platform แต่ละรุ่น โดยเฉพาะไฟล์ android.jar โดยเราสามารถเลือกรุ่นที่เราต้องการพัฒนามาติดตั้งไว้ได้ (แต่ควรเป็นรุ่นล่าสุด) ซึ่งจะอยู่ในโฟลเดอร์ <ANDROID_SDK>/platforms/android-<รุ่น>
  • Android System Image: ประกอบไปด้วย Image ไฟล์ของ OS บนแต่ละสถาปัตยกรรม เช่น ARM หรือ x86 เป็นต้น โดยแยกเป็น Platform แต่ละรุ่น ซึ่งเราสามารถเลือกรุ่นที่เราต้องการพัฒนามาติดตั้งไว้ได้ (แต่ควรเป็นรุ่นล่าสุด) ซึ่งจะอยู่ในโฟลเดอร์ <ANDROID_SDK>/system-images/android-<รุ่น>
ทั้งหมดที่แจกแจงไปนั้นเป็นเฉพาะเครื่องมือที่จำเป็นสำหรับพัฒนาเท่านั้นนะครับ แต่จริงๆแล้ว สิ่งที่ขาดไม่ได้เลยสำหรับนักพัฒนาก็คือ คู่มือ ครับ เพราะมีคำสั่งมากมายที่เราเองก็ไม่เคยจำได้ ต้องเปิดคู่มือเป็นประจำ แต่ส่วนใหญ่เท่าที่พบเห็นมือใหม่ทั้งหลาย เมื่อติดตั้ง IDE เสร็จแล้ว ก็ไม่ค่อยจะสนใจติดตั้ง API Document กันเลย เวลาเขียนโปรแกรมก็เลยต้องเปิดบราวเซอร์ไปด้วยเขียนโปรแกรมไปด้วย จริงๆแล้ว ถ้าสามารถติดตั้งได้ เช่น มีเนื้อที่ใน HDD เหลือเฟือ ก็ควรจะติดตั้งไว้ในเครื่องด้วย จะได้ไม่ต้องต่ออินเตอร์เน็ตอยู่ตลอดเวลาที่พัฒนาโปรแกรมครับ อีกทั้งส่วนใหญ่ IDE เก่งอย่าง Eclipse จะสามารถดึงเอาคำอธิบายใน Document มาให้เราอ่านได้ทันทีที่เราพิมพ์คำสั่งที่ต้องการ จะสะดวกมากๆเลยจริงๆครับ

Setup

เมื่อเรารู้แล้วว่าเครื่องมือต่างๆที่จำเป็นจะต้องมีอะไรบ้าง ต่อไปก็ถึงขั้นตอนการติดตั้ง ซึ่งจริงๆแล้วก็ไม่ได้ยุ่งยากอะไร เพราะ Google ได้จัดทำ package สำเร็จรูปไว้แล้ว ผมจึงขอแยกวิธีการติดตั้งออกเป็น 3 วิธีนะครับ
  1. Bundle: เป็น package ที่มีทุกอย่างทั้ง Eclipse และ SDK ครบถ้วน บีบอัดไว้อยู่ในไฟล์เดียว สามารถดาวโหลดมาแล้วแตกไฟล์ออกมา และใช้งานได้เลยทันที โดยเข้าไปที่โฟลเดอร์ eclipse แล้วรันโปรแกรม eclipse เท่านี้ก็พัฒนาโปรแกรมได้เลย ทำให้สะดวกใช้มาก แต่ก็มีข้อเสียคือ เราแทบจะไม่รู้อะไรเลย
  2. Existing IDE: หากเรามี Eclipse อยู่แล้ว ก็สามารถติดตั้งทุกอย่างลงใน Eclipse ที่มีอยู่ได้เลย เพราะ Eclipse ออกแบบมาให้สามารถเพิ่มเติมความสามารถต่างๆได้ด้วย plugin ทำให้เราไม่ต้องมี Eclipse อยู่ถึงสองตัวในเครื่องเดียว ส่วนวิธีการนั้น จะขออธิบายในหัวข้อถัดไปนะครับ
  3. Android Studio: เป็น package แบบใหม่ที่คล้ายกับ Bundle คือมีทุกอย่างครบถ้วนใน package เดียว เพียงแต่ใช้กับ IntelliJ IDEA ซึ่งเป็น IDE ของ JetBrains ใครชอบ IDE ตัวนี้ก็สามารถดาวโหลดมาทดลองใช้ได้เลยนะครับ จริงๆแล้วผมเองไม่ค่อยประทับใจ Eclipse มากสักเท่าไรครับ ภาพข้างล่างนี้คือหน้าตาของ Android Studio แต่ผมยังไม่มีเวลาทดลองใช้ แค่รันดูเฉยๆครับ
Android Studio

Existing IDE Setup

หากมี Eclipse อยู่แล้ว (ซึ่งผมต้องการใช้ Eclipse ที่มีมากับ Ubuntu ตามที่ได้เกริ่นไว้) ขั้นตอนแรกเลยก็คือการติดตั้ง ADT Plugin ซึ่งจะเป็นตัวเชื่อมทุกอย่างเข้ากับ Eclipse โดยเราสามารถเลือกเมนู Help/Install New Software ใน Eclipse จากนั้นกดปุ่ม Add เพื่อเพิ่ม Repository โดยอาจจะตั้งชื่อว่า ADT Plugin และป้อน URL ดังนี้
https://dl-ssl.google.com/android/eclipse/
เมื่อติดตั้ง Plugin เสร็จแล้ว restart Eclipse จะปรากฎหน้าต่าง "Welcome to Android Development"  และให้เราป้อนโฟลเดอร์ของ Android SDK ซึ่งมีสองทางเลือก คือ ป้อนโฟลเดอร์ใหม่ โดยจะดาวโหลด Android SDK มาติดตั้ง หรือ เลือกโฟลเดอร์ที่เราได้แตกไฟล์ Android SDK ที่ดาวโหลดมาไว้แล้ว ในกรณีนี้ ผมยังไม่เคยแนะนำให้ดาวโหลด Android SDK มาก่อน จึงเลือกที่จะป้อนโฟลเดอร์ใหม่ให้ดาวโหลดมาติดตั้ง ซึ่งเมื่อติดตั้งเสร็จ จะปรากฎหน้าต่าง Android SDK Manager ดังภาพ
Android SDK Manager
ใน Android SDK Manager นี้ เราสามารถเลือกที่จะติดตั้ง Tools และ Platform ต่างๆได้ โดย Tools ที่จะต้องติดตั้งหลักๆก็คือ SDK Platform และ Build ส่วน Platform API ให้เลือกรุ่นที่ตรงกับอุปกรณ์เป้าหมายที่โปรแกรมของเราจะนำไปติดตั้ง สามารถเลือกติดตั้งไว้หลายๆรุ่นก็ได้ แล้วเลือกใช้อีกทีตอนพัฒนาโปรแกรมนะครับ เมื่อเลือกเสร็จแล้ว กดปุ่ม Install จะมีการติดตั้งส่วนอื่นๆที่จำเป็นร่วมด้วย สังเกตุให้ดีว่า เราได้ติดตั้งทุกอย่างครบถ้วนแล้วนะครับ ที่สำคัญ อย่าลืมเลือกติดตั้ง Documentation ด้วยนะครับ

ผมขอจบเรื่องไว้เท่านี้ก่อนนะครับ เรื่องการพัฒนาโปรแกรมบน Android ไว้โอกาสหน้าจะมาเล่าต่อว่า ผมจะทำอะไรบ้าง

วันเสาร์ที่ 8 กุมภาพันธ์ พ.ศ. 2557

HBase Installation

หลังจากที่ผมได้ทำการติดตั้ง Hadoop 1.2.1 บน Ubuntu 12.04 LTS ไปเรียบร้อยแล้ว ซึ่งก็จะได้โครงสร้างพื้นฐานของการประมวลผลแบบกระจายแบบ MapReduce บน HDFS (Hadoop Distributed File System) แต่การประมวลผลแบบ MapReduce บน HDFS นั้น ค่อนข้างมีความซับซ้อนพอสมควร ผมจึงยังไม่อยากแนะนำ ถ้าหากใครสนใจ สามารถอ่านเพิ่มเติมได้จากลิ้งค์อ้างอิงท้ายบทความนี้นะครับ

แต่สิ่งที่ผมอยากจะแนะนำในบทความนี้ก็คือ HBase ครับ ซึ่งก็คือระบบฐานข้อมูลแบบกระจายของ Hadoop และสามารถรองรับข้อมูลขนาดใหญ่มาก (Big Data) ในระดับ พันล้าน row และ ล้าน column ได้ โดย HBase จะจัดเก็บข้อมูลแบบกระจายอยู่บน HDFS และคำสั่งที่จะ Create Read Update Delete (CRUD) นั้นจะเป็นการประมวลผลแบบกระจายแบบ MapReduce น่าสนใจไหมครับ หากใครสนใจก็คอยติดตามอ่านบทความต่อๆไปด้วยนะครับ เพราะครั้งนี้ผมจะเริ่มต้นที่การติดตั้งก่อน

HBase Version

สำหรับรุ่น stable ปัจจุบันนั้น เป็นรุ่น 0.94.15 แต่มีรุ่นใหม่ล่าสุดคือ 0.96.1.1 เนื่องจาก Hadoop ได้แตกออกเป็น 2 รุ่นคือ 1.x และ 2.x ซึ่ง HBase รุ่นไหนควรใช้กับ Hadoop รุ่นไหนนั้น สามารถดูได้จากตารางในหัวข้อ 2.1.3 บทที่ 2 เรื่อง Apache HBase Configuration ของหนังสือ The Apache HBase™ Reference Guide

เนื่องจากผมใช้ Hadoop 1.2.1 และ HBase 0.96.0 ผ่านการทดสอบกับ Hadoop 1.1.x แล้ว (เสียดายที่ไม่มีข้อมูลรุ่นใหม่ล่าสดุ) ผมจึงคาดว่า HBase รุ่นใหม่ล่าสุดนี้ น่าจะใช้กับ Hadoop 1.2.1 ได้ (หวังว่าผมจะคาดการณ์ไม่ผิดนะ) จึงทดลองดาวโหลด Binary Package (hbase-0.96.1.1-hadoop1-bin.tar.gz) มาติดตั้ง

Operation Mode

HBase จะประกอบไปด้วยบริการ 3 ประเภทคือ
  1. master (HMaster) เป็นบริการหลักของ HBase ซึ่งจะคอยตรวจสอบการทำงานของ regionserver โดยปกติแล้วจะทำงานอยู่บน NameNode ของ Hadoop
  2. regionserver (HRegionServer) เป็นบริการที่ทำหน้าที่ดูแลและจัดการข้อมูลภายใน region นั้นๆ โดยปกติแล้วจะทำงานอยู่บน DataNode ของ Hadoop
  3. zookeeper (HQuorumPeer) เป็นบริการที่ทำหน้าที่ผสานงานระหว่างเครื่องต่างๆในระบบประมวลผลแบบกระจาย ซึ่ง HBase นำมาใช้ผสานงานระหว่างเครื่อง HMaster และ HRegionServer
ซึ่งบริการทั้ง 3 ประเภท สามารถทำงานอยู่บนเครื่องๆเดียวได้ (Single-Node โหมด) โดย HBase มีโหมดการทำงาน 3 โหมดเช่นเดียวกัน Hadoop  คือ Standalone Pseudo-Distributed และ Fully-Distributed สำหรับ Standalone นั้น จะไม่ทำงานเป็นเครือข่าย และข้อมูลจะถูกจัดเก็บแบบไฟล์ปกติ จะไม่ใช้ HDFS ส่วนการทำงานในโหมด Pseudo-Distributed และ Fully-Distributed จะทำงานเป็นเครือข่าย และข้อมูลจะถูกจัดเก็บอยู่ใน HDFS ของ Hadoop

Setup Single-Node Mode

เนื่องจาก HBase ไม่มี Debian Package ให้ มีแต่ Binary Package และภายใน Package จะมีทั้งโปรแกรมและ config ไฟล์รวมๆกันอยู่ ไม่ได้แยก config ไฟล์ไปไว้ที่ /etc แต่อย่างใด ผมจึงเลือกที่จะแตกไฟล์ไว้ที่ /usr/local แทน ด้วยคำสั่ง
cd /usr/local
sudo tar xzf /path/to/hbase-0.96.1.1-hadoop1-bin.tar.gz
Configuration ไฟล์ที่เกี่ยวข้องจะอยู่ในโฟลเดอร์ conf มีไฟล์ต่างๆดังนี้
  1. hbase-env.sh
    • กำหนดค่า JAVA_HOME ใหม่ให้ถูกต้อง สำหรับค่า default ที่กำหนดไว้คือ /usr/java/jdk1.6.0/ แต่เนื่องจากบน Ubuntu จะใช้ OpenJDK เป็นหลัก ผมจึงเปลี่ยนค่าให้เป็น /usr/lib/jvm/default-java ซึ่งจะใช้ default JDK ที่ติดตั้งไว้ โดยปกติแล้ว Ubuntu 12.04 LTS จะใช้ OpenJDK 6 ซึ่งน่าจะเข้ากันได้ดี แต่เนื่องจากผมมีโปรแกรมบางตัวที่ใช้ Java 7 จึงเปลี่ยนมาใช้ OpenJDK 7 (หวังว่าจะไม่มีปัญหา)
    • กำหนดค่า HBASE_MANAGES_ZK=true ซึ่งจะทำให้มีการ start zookeeper อัตโนมัติพร้อมๆกับตอน start HBase (โดย default ค่านี้จะเป็น true อยู่แล้ว เราอาจจะไม่กำหนดก็ได้)
  2. hbase-site.xml
    • ค่า default ต่างๆระบุไว้ที่หัวข้อ 2.3.1.1 เรื่อง HBase Default Configuration ของหนังสือ The Apache HBase™ Reference Guide ทำให้เราไม่จำเป็นต้องระบุค่าทุกๆค่า ซึ่งมีจำนวนมาก เราสามารถระบุเฉพาะค่าที่ไม่ตรงกับ default ก็พอ
    • กำหนดค่า hbase.rootdir เพื่อระบุว่าข้อมูล HBase ทั้งหมดจะอยู่ที่ไหน เช่น เก็บไว้ที่ /hbase ใน HDFS ซึ่งสามารถเข้าถึงได้ดังนี้
          <property>
              <name>hbase.rootdir</name>
              <value>hdfs://localhost:8020/hbase</value>
          </property>
    • กำหนดค่า hbase.cluster.distributed เป็น true
    • กำหนดค่า hbase.zookeeper.quorum เป็นรายชื่อเครื่องทั้งหมดที่อยู่ในกลุ่มของ HBase ในที่นี้เราตั้งค่าสำหรับ Single-Node จึงกำหนดให้มีเพียง localhost เท่านั้น
เนื่องจากเรากำหนดให้ HBase จัดเก็บข้อมูลไว้ที่ /hbase ตามที่ตั้งค่าไว้ใน hbase-site.xml เราจึงต้องสร้างโฟลเดอร์เตรียมไว้ด้วยคำสั่ง
hadoop fs -mkdir /hbase
หากไม่สามารถสร้างได้ เนื่องจากปกติแล้ว Hadoop ที่ติดตั้งด้วย Debian Package จะใช้ user ชื่อ hdfs ในการจัดการ HDFS แทนที่จะใช้ root เราจึงอาจจะต้องสั่งสร้าง /hbase ด้วยคำสั่ง
sudo -u hdfs hadoop fs -mkdir /hbase
จากนั้น จึงสร้าง user root บนระบบ Hadoop ด้วยคำสั่ง
hadoop-create-user.sh -u root
แล้วกำหนดสิทธิ์ให้ user root สามารถจัดการ /hbase ได้ ด้วยการเปลี่ยนเจ้าของ โดยสั่ง
sudo -u hdfs hadoop fs -chown root /hbase
หากตรวจสอบโฟลเดอร์ที่สร้างขึ้นด้วยคำสั่ง hadoop fs -ls / ดูจะพบว่า /hbase เปลี่ยนเป็นของ root เรียบร้อยดังนี้
# hadoop fs -ls /
Found 3 items
drwx------   - root supergroup          0 2014-02-08 17:58 /hbase
drwxrwxrwx   - hdfs supergroup          0 2014-02-07 10:29 /tmp
drwxr-xr-x   - hdfs supergroup          0 2014-02-07 15:19 /user

Start/Stop Service

เนื่องจากการ start/stop บริการต่างๆของ HBase นั้น จะมีการติดต่อไปยัง Hadoop ด้วย ssh ซึ่งจะมีการถามรหัสผ่านทุกครั้ง ทำให้ไม่สะดวกต่อการสั่งงาน โดยเฉพาะหากเราต้องการตั้งให้มีการ start/stop อัตโนมัติ การที่เราจะใช้ ssh โดยไม่ต้องป้อนรหัสผ่านเลยนั้น จะต้องใช้กุญแจอิเล็กทรอนิกส์เป็นรหัสผ่านแทน ซึ่งสามารถตรวจสอบว่ามีการตั้งค่ากุญแจอิเล็กทรอนิกส์สำหรับ root ไว้แล้วหรือยังโดยใช้คำสั่ง
sudo ssh localhost
หากสามารถเข้าได้โดยไม่ต้องป้อนรหัสผ่าน แสดงว่าเราได้เคยตั้งค่ากุญแจรหัสอิเล็กทรอนิกส์ไว้แล้ว (อาจจะมีโปรแกรมบางชนิดมีการแนะนำให้ทำไว้แล้ว) ถ้าหากต้องป้อนรหัสผ่าน แสดงว่ายังไม่ได้ตั้งค่ากุญแจอิเล็กทรอนิกส์ไว้ เราจะต้องสร้างกุญแจขึ้นมาใหม่ด้วยคำสั่ง ssh-keygen ดังนี้
ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa
คำสั่งนี้ จะสร้างกุญแจรหัสของเราขึ้นมา โดยเก็บไว้ที่ไฟล์ ~/.ssh/id_dsa ซึ่งเป็นกุญแจส่วนตัวของเรา (Private Key) และจะมีไฟล์ ~/.ssh/id_dsa.pub ซึ่งเป็นกุญแจสาธารณะ (Public Key) สำหรับให้ผู้ที่เราจะติดต่อแลกเปลี่ยนข้อมูลกัน โดยเราจะต้องนำเอากุญแจสาธารณะไปไว้ที่ไฟล์ ~/.ssh/authorized_keys ของเครื่องที่เราจะติดต่อด้วย โดยใช้คำสั่งดังนี้
cat ~/.ssh/id_dsa.pub >> ~/.ssh/authorized_keys
เมื่อตั้งค่ากุญแจรหัสอิเล็กทรอนิกส์แล้ว ก็จะสามารถสั่ง start/stop บริการต่างๆของ HBase ได้อย่างราบรื่น ซึ่งมีสคริปท์ให้เราเรียกใช้ได้อยู่ในโฟลเดอร์ bin คือ สั่ง start ด้วยคำสั่ง
sudo /path/to/hbase/bin/start-hbase.sh
สั่ง stop บริการด้วย
sudo /path/to/hbase/bin/stop-hbase.sh

ปัญหาที่อาจจะเกิดขึ้น

เมื่อ start HBase แล้ว ตรวจสอบบริการต่างๆด้วยคำสั่ง jps ต้องพบบริการ master (HMaster) regionserver (HRegionServer) และ zookeeper (HQuorumPeer) หากบริการใดไม่ทำงาน ควรตรวจสอบดูข้อผิดพลาดใน log ไฟล์ เช่น ถ้า master ไม่ทำงาน log ไฟล์ของ master แสดงข้อความ
FATAL [master:SLOffice:60000] master.HMaster: Unhandled exception. Starting shutdown.
org.apache.hadoop.security.AccessControlException: org.apache.hadoop.security.AccessControlException: Permission denied: user=root, access=WRITE, inode="":hdfs:supergroup:rwxr-xr-x

ซึ่งหมายความว่า HBase กำลังเข้าถึง HDFS ด้วย user root เพื่อที่จะเขียนข้อมูล แต่ไม่มีสิทธิ์ แสดงว่าเราอาจจะลืมทำขั้นตอนที่สร้าง /hbase และเปลี่ยนสิทธิ์ให้ root เป็นต้น

อ้างอิง

  1. สมชัย หลิมศิโรรัตน์, Hadoop 1.x Installation on Ubuntu
  2. The Apache Software Foundation, MapReduce Tutorial
  3. The Apache Software Foundation, HDFS Architecture Guide
  4. The Apache Software Foundation, HBase
  5. The Apache Software Foundation, The Apache HBase™ Reference Guide

วันจันทร์ที่ 20 มกราคม พ.ศ. 2557

Eclipse cannot start after upgrade Ubuntu to 13.10

สองวันมานี้ผมเสียเวลาไปกับการอัพเกรดเครื่องจาก Ubuntu 12.04 LTS ไปเป็น 13.10 เนื่องจากผมต้องการทดลองใช้งานโปรแกรมใหม่ๆ จึงคิดว่าถึงเวลาเตรียมพร้อมสำหรับ 14.04 LTS ที่จะออกมาประมาณเดือนเมษายนนี้แล้ว โดยผมใช้วิธีค่อยๆอัพเกรดเป็นขั้นๆ เนื่องจากมีโปรแกรมหลายๆอย่างที่ผมไม่อยากตั้งค่าใหม่ และอยากจะเห็นความเปลี่ยนแปลงของแต่ละรุ่นด้วย แต่ก็เสียเวลามากหน่อยเพราะต้องอัพเกรดดังนี้

  • จาก 12.04 LTS ไปเป็น 12.10
  • จาก 12.10 ไปเป็น 13.04
  • จาก 13.04 ไปเป็น 13.10
เสียเวลาไปเกือบสองวัน สุดท้ายก็ได้ทุกอย่างสมกับที่ยอมเสียเวลาเหนื่อย

แต่ก็มีปัญหากับ Eclipse ที่รันไม่ได้ ปัญหานี้มักจะเจอบ่อยๆในหลายๆครั้งที่ผมอัพเกรด Ubuntu ครั้งนี้มี Error คือ

!SESSION Mon Jan 20 21:15:05 ICT 2014 ------------------------------------------
!ENTRY org.eclipse.equinox.launcher 4 0 2014-01-20 21:15:05.091
!MESSAGE Exception launching the Eclipse Platform:
!STACK
java.lang.ClassNotFoundException: org.eclipse.core.runtime.adaptor.EclipseStarter
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:626)
at org.eclipse.equinox.launcher.Main.basicRun(Main.java:584)
at org.eclipse.equinox.launcher.Main.run(Main.java:1438)
at org.eclipse.equinox.launcher.Main.main(Main.java:1414)

มีหลายคนแนะนำให้แก้ไขไฟล์ eclipse/configuration/config.ini เนื่องจากชื่อไฟล์ org.eclipse.osgi_x.x.x.jar ไม่ตรงกับรุ่นที่ติดตั้ง ซึ่งเมื่อผมตรวจสอบ พบว่า ในเครื่องของผมไม่มีไฟล์นี้อยู่ ไม่รู้ว่าหายไปไหน และก็ไม่รู้ว่าจะเอามาจากไหนดีด้วย ค้นไปค้นมา ก็พบว่ามันอยู่ใน Package ชื่อ libequinox-osgi-java ครับ แต่ในเครื่องมันแสดงว่าผมติดตั้งไว้แล้ว ซึ่งควรจะมีไฟล์อยู่ ผมจึง reinstall ใหม่ ก็ใช้ได้เลยครับ

วันพุธที่ 15 มกราคม พ.ศ. 2557

Hadoop 1.x Installation on Ubuntu

เมื่อประมาณเกือบๆ 1 ปีที่ผ่านมา ผมได้ติดตั้ง Hadoop รุ่น 1.1.2 บน Ubuntu 12.04 LTS เรียบร้อยไปแล้ว ซึ่งตอนนั้นมีปัญหาในการติดตั้งบางอย่าง แต่ผมก็ได้แก้ปัญหาไปแล้ว แต่มาเมื่อสัปดาห์ก่อน ผมต้องการติดตั้ง Hadoop บนอีกเครื่องหนึ่ง ทำให้ผมต้องมารื้อฟื้นความจำว่าปัญหาเก่าๆที่เคยแก้ไปนั้น ผมทำอย่างไร แต่นึกเท่าไหร่ก็นึกไม่ออกสักที ไฟล์เก่าๆที่เคยบันทึก หรือ เขียนสคริปท์ไว้ ตอนนี้ก็ไม่รู้เก็บไว้ที่ไหนแล้ว เนื่องจากผมมีการ backup ไฟล์ไว้หลายที่ กระจัดกระจายกันไป ไม่มีเวลามาจัดให้เข้าที่เข้าทางสักที (ดูเหมือนจะเป็นปัญหาคลาสสิกสำหรับคนไอทีเลยก็ว่าได้ ใครมีไอเดียดีๆที่ช่วยแก้ปัญหานี้ได้น่าจะทำโปรแกรมขายได้นะครับ) ครั้งนี้ผมจึงอาศัยการบันทึกไว้ที่บล็อกนี้แทน และหวังว่าจะเป็นประโยชน์สำหรับบางคนที่ติดปัญหาเดียวกัน

Hadoop Version

รุ่นของ Hadoop ที่เมื่อก่อนผมเลือกใช้ คือ 1.1.2 ตอนนั้น น่าจะเป็นรุ่นใหม่ที่ stable ที่สุดแล้ว แต่ปัจจุบันนี้ มีรุ่นที่เป็น stable อยู่ 2 รุ่น เนื่องจากมีการแตกรุ่นออกเป็น 1.x และ 2.x โดยรุ่น stable ของ 1.x คือ 1.2.1 ส่วนรุ่น stable ของ 2.x คือ 2.2.0 ครับ
  • 1.x stable -> 1.2.1 (1 Aug, 2013)
  • 2.x stable -> 2.2.0 (15 Oct, 2013)
ทีแรกผมจะทดลองติดตั้ง 2.x แต่ก็เกรงว่า โปรแกรมเก่าจะใช้งานไม่ได้ ประกอบกับ เมื่อเข้าไปดาวโหลดไฟล์แล้ว ปรากฏว่า ไม่มี Debian Package ให้ดาวโหลด ผิดกับ 1.2.1 ที่มี Package ทั้งแบบ Debian (.deb) RedHat(.rpm) และ Binary(.tar.gz) รวมทั้งมีทั้งสถาปัตยกรรมแบบ i386 และ x86_64 ให้ครบหมด ผมจึงตัดสินใจดาวโหลด 1.2.1 มาลง ซึ่งก็พบว่าปัญหาเดิมที่พบใน 1.1.2 ยังไม่ได้รับการแก้ไข

Why Debian Package?

ระบบปฏิบัติการ Ubuntu นั้น มีพื้นฐานมาจาก Debian ดังนั้น Package สำหรับติดตั้งโปรแกรมจึงเป็นแบบ Debian ซึ่งสำหรับใครที่ติดตั้งด้วย Binary และทำตามคำแนะนำในเว็บไซต์ต่างๆ ก็ทำได้ครับ แต่จะไม่เหมือนกับ Debian Package ที่มีการวางระบบไฟล์ ไม่ว่าจะเป็น Configuration Library Script และอื่นๆไว้ในแบบของ Debian มีการกำหนดสิทธิ์ในการเข้าถึงไฟล์ต่างเหล่านี้อย่างเป็นระบบและเพื่อความปลอดภัยสูง ไม่ให้เกิดช่องโหว่ได้ ดังนั้น เราจึงควรทำตามที่กำหนดไว้จะดีที่สุด แต่สำหรับการทดลอง ทดสอบ หรือใช้พัฒนาโปรแกรมโดยเราใช้เองคนเดียว การติดตั้งแบบ Binary ก็จะสะดวกกว่ามากครับ เพราะทุกอย่างจะรวมอยู่ในโฟลเดอร์เดียวหมด แตกไฟล์มาก็แทบจะใช้ได้เลย

Users/Groups are not created during installation

เมื่อนำไฟล์ .deb มาติดตั้ง โดยปกติเมื่อ double-click ที่ไฟล์ โปรแกรม Package Installer จะทำงาน เปิดหน้าต่างมาให้เราติดตั้ง ซึ่งเป็น GUI ทำให้สะดวกมาก แต่จะทำให้เราไม่ทันได้สังเกตข้อความแจ้งเตือนต่างๆ จึงเข้าใจว่าติดตั้งเสร็จเรียบร้อยแล้ว แต่พอไปใช้งานก็จะมีปัญหา ซึ่งถ้าเราติดตั้งด้วยคำสั่ง
sudo -E dpkg -i hadoop_1.2.1-1_x86_64.deb

หมายเหตุ:

ที่กำหนด option -E ไว้ เพื่อให้มีการนำค่า JAVA_HOME ที่กำหนดไว้มาใช้ตอนติดตั้ง

ปัญหาแรกที่เราจะพบเมื่อติดตั้ง คือข้อความ
Selecting previously unselected package hadoop.
(Reading database ... 336159 files and directories currently installed.)
Unpacking hadoop (from .../hadoop_1.2.1-1_x86_64.deb) ...
groupadd: GID '123' already exists
Setting up hadoop (1.2.1) ...
chown: invalid group: `root:hadoop'
ซึ่งปกติแล้ว จะเป็นขั้นตอนการสร้าง group ชื่อ hadoop และ user ชื่อ mapred และ hdfs โดยสามารถตรวจสอบได้ ด้วยการเปิดดูไฟล์ preinst ซึ่งเป็นสคริปท์ที่จะถูกรันหลังจากที่มีการแตกไฟล์(Unpacking)เรียบร้อยแล้ว ซึ่งสคริปท์นี้เขียนไว้ตายตัวคือ
getent group hadoop 2>/dev/null >/dev/null || /usr/sbin/groupadd -g 123 -r hadoop
หมายความว่าให้สร้าง group ชื่อ hadoop โดยกำหนดให้มี GID เป็น 123 ซึ่งถ้าหากในเครื่องของเรามี GID 123 อยู่แล้ว ก็จะเกิด error GID '123' already exists นั่นเอง ข้อผิดพลาดนี้ได้มีการรายงานไว้แล้วตั้งแต่รุ่น 1.0.3 แต่ยังไม่ได้รับการแก้ไข สาเหตุของปัญหานี้เกิดจากการใช้คำสั่ง groupadd ซึ่งปกติแล้ว บนระบบ Debian จะมีอีกคำสั่งหนึ่งคือ addgroup ซึ่งทำงานคล้ายกัน groupadd จะเป็นคำสั่งระดับล่าง ส่วน addgroup จะเป็นคำสั่งระดับบน ซึ่งถ้าเราไม่กำหนด GID คำสั่ง addgroup จะหาค่าที่เหมาะสมตามระบบของ Debian มาให้แทน ดังนั้น คำสั่งนี้ควรจะแก้เป็น
getent group hadoop 2>/dev/null >/dev/null || /usr/sbin/addgroup --system hadoop
แต่เนื่องจากเราได้ติดตั้งไฟล์ไปแล้ว จึงแก้ไขด้วยการสั่งเองภายหลังจากที่แตกไฟล์เรียบร้อยแล้ว ดังนี้
/usr/sbin/addgroup --system hadoop
/usr/sbin/adduser --shell /bin/bash --no-create-home --system --ingroup hadoop --home /var/lib/hadoop/mapred mapred
/usr/sbin/adduser --shell /bin/bash --no-create-home --system --ingroup hadoop --home /var/lib/hadoop/hdfs hdfs
ซึ่งจะสร้าง group ชื่อ hadoop และ user ชื่อ mapred และ hdfs ขึ้นมา

Configuration Files

ก่อนที่เราจะเริ่ม start Hadoop จะมี config ไฟล์ที่ต้องแก้ไขต่างๆ ซึ่งตามคำแนะนำใน Document ของ Hadoop 1.2.1 จะระบุชื่อไฟล์ในโฟลเดอร์ conf แต่ Debian Package จะย้ายไปอยู่ที่ /etc/hadoop โดยมีไฟล์ต่างๆที่ต้องแก้ไขดังนี้
  1. hadoop-env.sh
    • กำหนดค่า JAVA_HOME ใหม่ให้ถูกต้อง สำหรับค่า default ที่กำหนดไว้คือ /usr/lib/jvm/java-6-sun แต่เนื่องจากบน Ubuntu จะใช้ OpenJDK เป็นหลัก ผมจึงเปลี่ยนค่าให้เป็น /usr/lib/jvm/default-java ซึ่งจะใช้ default JDK ที่ติดตั้งไว้ โดยปกติแล้ว Ubuntu 12.04 LTS จะใช้ OpenJDK 6 ซึ่งน่าจะเข้ากันได้ดี แต่เนื่องจากผมมีโปรแกรมบางตัวที่ใช้ Java 7 จึงเปลี่ยนมาใช้ OpenJDK 7 (หวังว่าจะไม่มีปัญหา)
    • หากต้องการเปลี่ยนค่า environment สามารถใช้คำสั่ง update-hadoop-env.sh ได้ โดยสั่ง
    • sudo -E bash /usr/sbin/update-hadoop-env.sh --prefix=/usr --bin-dir=/usr/bin --sbin-dir=/usr/sbin --conf-dir=/etc/hadoop --log-dir=/var/log/hadoop --pid-dir=/var/run/hadoop
  2. master/slave
    • master ไฟล์ จะเก็บรายชื่อเครื่องที่ทำหน้าที่เป็น NameNode ซึ่งจะเป็นเครื่องแม่ และสามารถเป็น JobTracker TaskTracker และ DataNode ได้ด้วย
    • slave ไฟล์ จะเก็บรายชื่อเครื่องที่ทำหน้าที่เป็น DataNode และเป็น TaskTracker ได้ด้วย
    • ถ้าติดตั้งในโหมด Single-Node ทั้งสองไฟล์จะมีค่าเป็น localhost
  3. *-site.xml
    • core-site.xml เป็นไฟล์ที่เก็บค่าเกี่ยวกับ NameNode เช่น fs.default.name เป็นค่า URL ของเครื่อง NameNode ที่ Hadoop ในเครื่องนั้นๆจะติดต่อด้วย โดยค่า default ใน Debian Package กำหนดไว้เป็น hdfs://localhost:8020 แต่เว็บอื่นๆมักกำหนดค่าตัวอย่างไว้ที่พอร์ต 9000 หรือ 9001 ซึ่งจะต้องระวังการกำหนดค่าตรงนี้ให้ดี เพราะหากชนกับบริการอื่นๆที่มีอยู่ในเครื่อง NameNode ก็จะไม่สามารถ start ได้ ค่า default ต่างๆของไฟล์นี้ สามารถดูได้จากไฟล์ core-default.xml
    • hdfs-site.xml เป็นไฟล์ที่เก็บค่าเกี่ยวกับ HDFS ว่าจะมีการจัดแบ่งข้อมูลของระบบอย่างไร โดยเฉพาะค่า dfs.replication ซึ่งเป็นจำนวนการทำซ้ำข้อมูล จำนวนนี้ไม่ควรเกินจำนวน DataNode ค่า default ต่างๆของไฟล์นี้ สามารถดูได้จากไฟล์ hdfs-default.xml
    • mapred-site.xml เป็นไฟล์ที่เก็บค่าเกี่ยวกับ JobTracker ค่า default ต่างๆของไฟล์นี้ สามารถดูได้จากไฟล์ mapred-default.xml

Operation Mode

ระบบ Hadoop จะประกอบไปด้วยบริการ 4 ประเภทคือ
  1. NameNode เป็นบริการหลัก ทำหน้าที่ค้นหาและจัดการบริการอื่นๆในระบบ
  2. JobTracker ทำหน้าที่จัดคิวการทำงาน
  3. TaskTracker ทำหน้าที่ประมวลผล Map-Reduce หรืองานอื่นๆ
  4. DataNode ทำหน้าที่จัดการข้อมูลในระบบ HDFS (Hadoop Distributed File System)
ซึ่งบริการทั้ง 4 ประเภท สามารถทำงานอยู่บนเครื่องๆเดียวได้ (Single-Node โหมด) หรือจะแบ่งเป็นเครื่อง 2 กลุ่ม คือ master ซึ่งสามารถมีบริการทั้ง 4 ประเภทในเครื่องเดียว หรือ slave ซึ่งจะมีบริการเฉพาะ TaskTracker และ DataNode เท่านั้น เพื่อแบ่งหรือกระจายการประมวลผลและข้อมูลของระบบออกไปยังเครื่องลูกจำนวนมากๆ โดยการติดตั้งระบบ เราสามารถเลือกติดตั้งให้ทำงานได้ 3 โหมดคือ
  1. Local (Standalone)
    • การทำงานจะอยู่บนเครื่องเดียว และไม่เป็นเครือข่าย
    • โหมดนี้เป็น default ของโปรแกรม หากยังไม่มีการตั้งค่าใดๆ
    • เหมาะกับการทดสอบและ debug มากกว่า
  2. Pseudo-Distributed (Single-Node)
    • การทำงานจะอยู่บนเครื่องเดียว แต่เป็นเครือข่าย
    • สามารถตั้งค่าต่างๆได้ง่ายด้วยโปรแกรม hadoop-setup-single-node.sh
    • เหมาะกับการใช้งานเบื้องต้น และพร้อมที่จะขยายไปเป็น Fully-Distributed โหมด
  3. Fully-Distributed (Multi-Node)
    • การทำงานจะเป็นเครือข่ายหลายเครื่องสมบูรณ์แบบ
    • สามารถปรับแก้ไฟล์บางไฟล์จาก Pseudo-Distributed โหมด กลายเป็น Fully-Distributed โหมดได้ทันที
    • เหมาะกับการใช้งานเต็มรูปแบบ รองรับภาระงานเพิ่มขึ้นโดยเพิ่มจำนวนเครื่องได้

Setup Single-Node Mode

หลังจากที่เราติดตั้ง Debian Package เรียบร้อยแล้ว ไฟล์ hadoop-env.sh จะถูกตั้งค่า environment ไว้เรียบร้อยแล้ว แต่หากค่าต่างๆยังไม่ถูกต้อง เช่นค่า JAVA_HOME ควรแก้ไขให้ถูกต้องเสียก่อนนะครับ จากนั้นเราจะต้องตั้งค่าไฟล์ *-site.xml ให้ครบถ้วน ก่อน ซึ่งขึ้นอยู่กับโหมดการใช้งานที่เราต้องการ โดยปกติแล้วถ้าเป็น Standalone โหมด ก็ไม่ต้องแก้ไขอะไร แต่ส่วนใหญ่แล้ว เราจะไม่ใช้โหมดนี้ เราจะใช้โหมด Single-Node มากกว่า ซึ่งค่าต่างๆที่ต้องกำหนด สามารถศึกษาได้จาก document ซึ่งผมจะไม่ขอกล่าวในที่นี้นะครับ แต่เราก็มีวิธีที่ง่ายกว่านั้น เนื่องจากมีสคริปท์ช่วยอำนวยความสะดวกให้แล้วคือ hadoop-setup-single-node.sh โดยมีลำดับของการทำงานคือ
  1. ตั้งค่า config ต่างๆ
  2. ทำการ Format NameNode (เหมือนเรา Format ฮาร์ดดิสก์นะครับ)
  3. จัดเตรียมโครงสร้างของไฟล์ในระบบ HDFS
  4. start บริการต่างๆของ Hadoop ซึ่งก็คือ NameNode DataNode JobTracker และ TaskTracker
  5. ตั้งค่าสำหรับให้บริการต่างๆ start อัตโนมัติเมื่อบู๊ตเครื่อง
เมื่อรัน hadoop-setup-single-node.sh แล้วตอบ y ทั้งหมด คือเลือกใช้ค่า default ทั้งหมด ผลปรากฏว่า มีปัญหาเกี่ยวกับ permission ของโฟลเดอร์ /var/hadoop/log/root เกิดขึ้นตอนรัน JobTracker และ TaskTracker ซึ่งมีข้อความตัวอย่างดังนี้

* Starting Apache Hadoop Job Tracker server hadoop-jobtracker                                      chown: changing ownership of `/var/log/hadoop/root': Operation not permitted
starting jobtracker, logging to /var/log/hadoop/root/hadoop-root-jobtracker-SLOffice.out
/usr/sbin/hadoop-daemon.sh: line 137: /var/log/hadoop/root/hadoop-root-jobtracker-SLOffice.out: Permission denied
head: cannot open `/var/log/hadoop/root/hadoop-root-jobtracker-SLOffice.out' for reading: No such file or directory
/usr/sbin/hadoop-daemon.sh: line 147: /var/log/hadoop/root/hadoop-root-jobtracker-SLOffice.out: Permission denied
/usr/sbin/hadoop-daemon.sh: line 148: /var/log/hadoop/root/hadoop-root-jobtracker-SLOffice.out: Permission denied
                                                                                             [fail]
สาเหตุ เกิดจาก JobTracker และ TaskTracker จะถูกรันโดย user ชื่อ mapred แต่โฟลเดอร์ /var/log/hadoop/root ถูกสร้างขึ้น เพราะเราสั่งรัน hadoop-setup-single-node.sh ด้วย root ซึ่งตอนรัน NameNode และ DataNode จะรันโดย user ชื่อ hdfs ทำให้มีการเปลี่ยนสิทธิ์ของ /var/log/hadoop/root ให้เป็นของ hdfs:hadoop ด้วย chmod มีค่าเป็น 755 พอ mapred จะมาเปลี่ยน จึงเปลี่ยนไม่ได้ ผมจึงแก้ไขด้วย chmod ให้เป็น 775 แทน

Start/Stop Service

การสั่ง start/stop บริการต่างๆของ Hadoop ความจริงแล้ว จะมีสคริปท์ให้เรียกใช้ โดยเฉพาะคำสั่ง start-all.sh และ stop-all.sh ซึ่งจะไปเรียก start-dfs.sh/start-mapred.sh และ stop-dfs.sh/stop-mapred.sh อีกทีหนึ่ง โดย dfs นั้นจะมีบริการ NameNode และ DataNode อยู่ ส่วน mapred จะมี JobTracker และ TaskTracker ครับ ดังนั้น เราจึง start ได้ด้วยคำสั่ง
sudo start-all.sh
และ stop ด้วยคำสั่ง
sudo stop-all.sh
แต่ถ้าหากเรากำหนดให้ hadoop-setup-single-node.sh ติดตั้ง service ให้ start/stop Hadoop อัตโนมัติตอน boot/shutdown เครื่อง เราก็สามารถสั่ง start ได้ด้วยคำสั่ง
sudo service hadoop-xxx start
และ stop ได้ด้วยคำสั่ง
sudo service hadoop-xxx stop
โดยมีสคริปท์สำหรับบริการต่างๆอยู่ที่ /etc/init.d/hadoop-xxx คือ
  • hadoop-namenode
  • hadoop-datanode
  • hadoop-jobtracker
  • hadoop-tasktracker
  • hadoop-secondarynamenode (ปกติจะไม่ start อัตโนมัติ)
และคำสั่งทั้งหมดนั้น จริงๆแล้วจะไปเรียกคำสั่ง hadoop-daemon.sh อีกทีหนึ่ง ซึ่งสามารถเรียกใช้ได้เช่นกัน มีรูปแบบคือ
hadoop-daemon.sh [start/stop] [ชื่อบริการ]
เช่น
hadoop-daemon.sh start namenode

หมายเหตุ:

หาก start ด้วยวิธีใด ให้ stop ด้วยวิธีที่คู่กัน

Startup Checking

จากการตรวจสอบการทำงานด้วยคำสั่ง jps จะพบว่ามีเพียง NameNode DataNode และ TaskTracker รันอยู่ แต่ JobTracker ไม่มี ผมจึงเข้าไปตรวจสอบ log ไฟล์ของ JobTracker ที่โฟลเดอร์ /var/log/hadoop พบข้อความผิดพลาดคือ
WARN org.apache.hadoop.mapred.JobTracker: Failed to operate on mapred.system.dir (hdfs://localhost:8020/mapred/mapredsystem) because of permissions.
WARN org.apache.hadoop.mapred.JobTracker: Manually delete the mapred.system.dir (hdfs://localhost:8020/mapred/mapredsystem) and then start the JobTracker.
WARN org.apache.hadoop.mapred.JobTracker: Bailing out ...
org.apache.hadoop.security.AccessControlException: org.apache.hadoop.security.AccessControlException: Permission denied: user=mapred, access=WRITE, inode="":hdfs:supergroup:rwxr-xr-x
สาเหตุก็คือ เนื่องจากขั้นตอนที่ 3 ของ hadoop-setup-single-node.sh นั้น จะสร้างโฟลเดอร์ /tmp และ /usr ไว้ใน HDFS เท่านั้น จะไม่มีโฟลเดอร์ /mapred แต่เนื่องจากใน mapred-site.xml ได้กำหนดค่า mapred.system.dir ไว้ที่ /mapred/mapredsystem จึงทำให้ JobTracker พยายามที่จะเขียนข้อมูลไปที่นั่น แต่ก็ทำไม่ได้ และจบการทำงานไป วิธีการแก้ไขที่มีคนแนะนำไว้ก็คือ ให้ตั้งค่า mapred.system.dir ไปไว้ใน /tmp แทน โดยให้อิงกับค่า hadoop.tmp.dir ซึ่งจะต้องแก้ไขในไฟล์ mapred-site.xml ดังนี้
<property>
<name>mapred.system.dir</name>
<value>${hadoop.tmp.dir}/mapred/mapredsystem</value>
<final>true</final>
</property>
เมื่อแก้ไขเสร็จแล้ว สั่ง start JobTracker ใหม่ ก็จะพบปัญหาทำนองเดียวกันกับค่า mapred.job.tracker.history.completed.location จึงแก้ไขให้เป็น
<property>
<name>mapred.job.tracker.history.completed.location</name>
<value>${hadoop.tmp.dir}/mapred/history/done</value>
</property>

Web Interface

เมื่อแก้ไขข้อผิดพลาดต่างๆ และ start บริการต่างๆได้หมดแล้ว ก็สามารถเปิดดู NameNode ผ่านเว็บได้ที่ URL http://localhost:50070 ส่วน JobTracker เปิดดูได้ที่ http://localhost:50030

อ้างอิง