Author: wiboon.w

  • Ubuntu server 18.04 config static IP with ifupdown not netplan

    เดิมก่อนหน้า server 18.04 จะออกมาให้ใช้งานกันนั้น จะเป็น server 16.04 เราตั้งค่า (config) static IP ให้กับ server ด้วยไฟล์นี้ /etc/network/interfaces ซึ่งก็คือ package ชื่อ ifupdown และ DNS resolver ที่ใช้ก็คือ resolvconf

    แต่ใน server 18.04 นี้ เปลี่ยนไปใช้ package ชื่อ netplan แก้ไขที่ไฟล์ /etc/netplan/01-netcfg.yaml และใช้ DNS resolver คือ systemd-resolve ซึ่งจะ connect สอบถามชื่อ DNS จาก internal DNS ที่ IP 127.0.0.53 (ตรวจดูด้วยคำสั่ง cat /etc/resolv.conf)

    (ย้ำ) บทความนี้ไม่ได้บอกเล่าให้ทุกคนต้องทำแบบนี้ เพียงแต่หากท่านมีงานเฉพาะอย่างที่ต้องการใช้แบบก่อนหน้านี้

    หลังจากติดตั้ง Ubuntu server เสร็จ

    1.แก้ไขไฟล์ /etc/default/grub ดังนี้

    sudo nano /etc/default/grub

    เพิ่มบรรทัดนี้

    GRUB_CMDLINE_LINUX="netcfg/do_not_use_netplan=true"

    หรือหากต้องการใช้ชื่อ interface เป็น ethX เช่น eth0 เป็นต้น ก็ให้เพิ่มบรรทัดแบบนี้

    GRUB_CMDLINE_LINUX="net.ifnames=0 netcfg/do_not_use_netplan=true"

    ทำการ Save และ ออก

    2.ทำคำสั่งนี้เพื่อ update grub

    sudo update-grub

    3.ติดตั้ง ifupdown package

    sudo apt install ifupdown

    4.ติดตั้ง resolvconf package

    sudo apt install resolvconf

    5.รีบูต server

    เมื่อ login กลับเข้าไปใน command prompt สั่ง ping www.google.com สำเร็จ ก็แสดงว่าใช้งานได้ (เซิร์ฟเวอร์นี้ต้องได้รับอนุญาตออกเน็ตด้วยนะ) หรือไม่ก็ใช้คำสั่งตรวจสอบชื่อโดเมน ดังนี้ host www.google.com ก็จะต้องได้รับ output ที่แสดงว่าสำเร็จ

    สำหรับวิธีการตั้งค่าแบบ netplan ก็มีเว็บเพจที่เขียนไว้มากพอสมควร เช่น Ubuntu Bionic: Netplan posted by Joshua Powers on 1 December 2017 [ https://insights.ubuntu.com/2017/12/01/ubuntu-bionic-netplan ] ลองทำความเข้าใจรูปแบบใหม่นี้ได้เลย

    Output ของค่า default เมื่อติดตั้ง Ubuntu server 18.04 เสร็จใหม่ ๆ (/etc/resolv.conf, /etc/network/interfaces และ /etc/netplan/01-netcfg.yaml)

    Output ของไฟล์ที่ใช้ในการตั้งค่า static IP เมื่อใช้ ifupdown package (/etc/resolv.conf และ /etc/network/interfaces)

    References:
    Disable netplan on Ubuntu 17.10 Posted on January 10, 2018 by ruchi [ http://www.ubuntugeek.com/disable-netplan-on-ubuntu-17-10.html ]
    Predictable Network Interface Names [ https://www.freedesktop.org/wiki/Software/systemd/PredictableNetworkInterfaceNames/ ]

  • Choose Network Type In VirtualBox

    เมื่อต้องไปจัดอบรม และต้องใช้ Oracle VM VirtualBox สำหรับสร้าง Virtual Machine (VM) จำนวนหนึ่ง (มากกว่า 1 ตัว ฮ่า ๆ) เราจำเป็นจะต้องรู้ว่า สภาพแวดล้อมของห้องบริการคอมพิวเตอร์ที่เราไปขอใช้งานนั้น จัด IP ให้กับเครื่อง Windows แบบใด เช่น ในกรณีที่มีการปล่อย DHCP IP แบบเหลือเฟือ การเลือกชนิด network ของ VM แต่ละตัว ก็ง่าย เราก็เลือกตั้งค่าเป็น Bridges ซึ่งแบบนี้ VM แต่ละตัวก็จะได้ IP อยู่ในชุดเดียวกันกับ Windows แต่หากจัด IP แบบตายตัวให้กับ MAC Address ของ PC นั้นเลย และไม่ปล่อย DHCP IP เพิ่มให้ อย่างนี้ เราก็ต้องออกแบบว่าจะให้ VM (Guest) เหล่านั้นใช้ IP อะไร จะทำงานร่วมกันได้อย่างไร และจะให้ VM เหล่านั้น ติดต่อกับ Windows (Host) ด้วยหรือไม่

    แบบแรก NAT Network

    แบบนี้ VirtualBox จะสร้าง network จำลองขึ้นมาในโปรแกรมมันเองเท่านั้น ผลลัพธ์คือ VM (Guest) ทุกตัวจะทำงานร่วมกันได้ แต่จะติดต่อกับ Windows (Host) ไม่ได้ แบบนี้ VM (Guest) ทุกตัวจะได้รับ IP อยู่ในชุดที่ใช้สำหรับ NAT นั่นคือ 10.0.2.0/24 และ VM แต่ละตัวเมื่อได้ IP จะได้ค่า IP ของ DNS server ที่เครื่อง Windows ใช้งานมาให้ด้วย ทำให้ VM สามารถติดต่อไปใช้งาน Internet ได้

    ตัวอย่างเช่น VM 2 ตัว ตั้งค่า network ชนิด NAT Network

    VM ตัวที่ 1 จะได้ IP 10.0.2.6 และได้ค่า IP DNS Server ชุดที่ใช้ใน Windows (Host) ใช้งาน Internet ได้เลย

    VM ตัวที่ 2 ได้ IP 10.0.2.15 และใช้คำสั่ง ping ไปยัง VM ตัวที่ 1 ได้

    แต่ Windows (Host) จะใช้คำสั่ง ping VM ทั้ง 2 ตัว ไม่ได้

    สำหรับวิธีการตั้งค่า NAT Network ก็เข้าไปที่ File > Preferences > Network  และเราสามารถแก้ หรือ เพิ่มใหม่ ได้

    แบบที่สอง Host-Only Adapter

    แบบนี้ VirtualBox จะสร้าง Virtual network adapter เพิ่มลงใน Windows OS ให้ด้วย ผลลัพธ์คือ VM (Guest) ทุกตัวจะทำงานร่วมกันได้ และติดต่อกับ Windows (Host) ได้ แต่แบบนี้ VM (Guest) ทุกตัวจะได้รับ IP ในชุด 192.168.56.0/24 (ค่า default ซึ่งเราจะเปลี่ยนได้) แต่ VM แต่ละตัวจะได้มาเพียง IP จะไม่ได้ค่า IP ของ DNS server ที่เครื่อง Windows ใช้งาน ส่งผลให้ VM ไม่สามารถติดต่อไปใช้งาน Internet ได้ แต่ก็แก้ปัญหานี้ได้โดยการเพิ่ม Network Adapter อันที่สอง และตั้งค่า network เป็นชนิด NAT แล้วไป config ให้มีการ start network interface อันที่สองนี้ใน VM (Guest)

    ตัวอย่าง VM 2 ตัว ตั้งค่า network ชนิด Host-only Adapter

     

    Windows (Host) ได้ IP เพิ่มขึ้นมาคือ 10.0.0.1 (ผมแก้ค่า default มาเป็นอันใหม่ เดิม 192.168.56.1)

    VM 1 ได้ IP 10.0.0.101

    VM 2 ได้ IP 10.0.0.102

    Windows ใช้คำสั่ง ping ไปยัง VM 1 ได้

    VM 1 ใช้คำสั่ง ping ไปยัง VM 2 ได้

    VM 1 ไม่ได้ค่า IP DNS server ในไฟล์ /etc/resolv.conf ทำให้ติดต่อใช้งาน Internet ไม่ได้

    ต้องปิด VM แล้ว เราต้องเพิ่ม Adapter อันที่ 2 ให้เป็น NAT

    เปิด VM จากนั้นเข้าไปใน VM ในตัวอย่างคือ Ubuntu 16.04.4 server ให้เพิ่ม Add NAT interface แล้ว reboot

    ข้อความที่เพิ่มคือ (ชื่อ interface enp0s8 จะเปลี่ยนไป อาจไม่ใช่ชื่อนี้ เช็คด้วย ifconfig -a)

    auto enp0s8

    iface enp0s8 inet dhcp

    จะเห็นว่า มี network interface 2 อัน อันที่เพิ่มมาเป็น NAT มี IP เป็น 10.0.3.15

    ตอนนี้จะได้ค่า IP DNS Server อยู่ใน /etc/resolv.conf ทำให้ใช้งาน Internet ได้

    สำหรับวิธีการเปลี่ยนค่า ชุด IP ของ Host-Only Adapter ทำดังนี้ ผมทำการเปลี่ยนตัวเลขชุด 192.168.56 ทุกแห่งที่แท็บ Adapter และแท็บ DHCP Server

    ต้องไป disable และ enable VirtualBox Host-only Network ที่ Windows ด้วย แล้วปิดเปิดโปรแกรม VirtualBox

    แบบที่สาม Internal Network

    แบบนี้ VirtualBox ไม่ได้สร้าง network จำลอง และไม่ได้สร้าง Virtual network adapter เพิ่มลงใน Windows OS แต่ได้เตรียมชื่อไว้ให้ว่า Intnet (ค่า default) ผลลัพธ์คือ แบบนี้ VM (Guest) ทุกตัว จะต้องตั้งค่า IP เองก่อนจึงทำงานร่วมกันได้ และไม่สามารถติดต่อกับ Windows (Host) ได้ และ ไม่สามารถติดต่อไปใช้งาน Internet ได้ แต่ก็แก้ปัญหานี้ได้โดยการสร้าง VM (Guest) 1 ตัว ให้ทำหน้าที่เป็น Router นั่นคือ มี Network Adapter อันที่ 1 เป็นชนิด NAT และมี Network Adapter อันที่ 2 เป็นชนิด Internal Network (ชื่อ Intnet) เมื่อสร้าง Router นี้ขึ้นมา ก็จะทำให้ VM (Guest) ทุกตัวติดต่อกับ Internet ได้ แต่ตัว Router ต้องทำหน้าที่เป็นทั้ง DHCP Server และ DNS Server เพื่อแจก IP ให้กับ VM และ แจกค่า IP ของ DNS Server ให้ด้วยตามลำดับ การตั้งค่าแบบที่สามนี้ ดู ๆ ไปก็น่าจะยุ่งยากมากในการเตรียม แต่ก็มีความสามารถที่เพิ่มมาคือ เราสามารถจำลองระบบเครือข่ายได้ เช่น สามารถสร้างโดเมนเนมให้กับ VM ที่ทำหน้าที่เป็น Web Server ได้ด้วย เช่น เราอาจจะตั้งชื่อ zone ว่า example.com แล้วเราให้ VM Web Server นี้มีชื่อโดเมนเนมว่า wordpress1.example.com อย่างนี้ เราทำได้

    ตัวอย่างเช่น VM 2 ตัวที่ตั้งค่า network แบบ Internal Network

    ผมมี myrouter.ova ให้ download ไปใช้ โดยการ import เข้าก็ใช้ได้เลย หากจะเล่าว่าต้องติดตั้งอะไรบ้างคงจะยาวมาก ๆ เอาเป็นว่า ใน myrouter.ova นี้ ผมได้ติดตั้ง DHCP Server ใช้ค่า 10.0.100.0/24, DNS Server ใช้ชื่อโดเมนคือ example.com, Apache2 Web Server และผมมี LDAP Database ou=lulu,ou=example,ou=com ไว้ให้ทดสอบ ด้วยครับ

    VM ตัวพิเศษ ทำหน้าที่เป็น Router จะมี Network Adapter 2 อัน อันแรกเป็น NAT อันที่สองเป็น Internal Network ชื่อ Intnet (ชื่อนี้เป็นค่า default เราเปลี่ยนได้)

    VM ตัวที่ 1 ตั้งค่า network ชนิด Internal Network

    VM ตัวที่ 2 ตั้งค่า network ชนิด Internal Network

    หวังว่าคงจะได้นำไปประยุกต์ใช้งานกันนะครับ

  • Windows CRLF to Unix LF Issues in Cygwin Shell Script

    เมื่อเรารัน shell script ของโปรแกรม Cygwin for Windows ซึ่งมีการเขียนคำสั่งไปตัดเอาข้อความผ่านคำสั่ง (Command Line) ของ Windows มาใส่ในตัวแปรของ shell script

    เช่น ในตัวอย่างนี้คือคำสั่ง ipconfig เมื่อได้ข้อความที่ต้องการมาเราจะได้ \r แถมมาให้ด้วยต่อท้าย เพราะ Windows style line ending จะมี CRLF (\r\n)  ในขณะที่ Linux style line ending จะมี LF (\n) เท่านั้น

    น่าแปลกใจมากว่า เราเคยรัน shell script นี้ใน Windows 7 ใช้งานได้ แต่พอเป็น Windows 10 build 1709 มันรันไม่ได้

     

    ปัญหา

    เมื่อเปิด Cygwin Terminal ขึ้นมา จะได้เป็น bash shell

    ในไฟล์ test.sh ดังตัวอย่างข้างล่างนี้ เมื่อสั่งรัน จะพบว่าพบข้อผิดพลาด ตัวแปร ZONEX จะไม่มีค่า ซึ่งจริง ๆ จะต้องได้คำว่า zone1
    $ cat test.sh

    #!/bin/bash
    
    DHCPSERVER=$(ipconfig /all | grep -i "DHCP Server" | cut -d: -f2 | xargs)
    
    MAC=$(ipconfig /all | grep -A4 -i "^Ethernet Adapter Ethernet" | tail -1 | cut -d\: -f2 | tr - : | xargs)
    
    ZONEX=$(curl -s http://${DHCPSERVER}/dhcpd.txt | grep -i ${MAC} | awk '{print $2}' | cut -d'_' -f1)
    
    echo "DHCP SERVER is ${DHCPSERVER}"
    echo "MAC is ${MAC}"
    echo "Zone is ${ZONEX}"

    สั่งรันดูผลลัพธ์ด้วยคำสั่ง bash test.sh

    $ bash test.sh
    DHCP SERVER is 192.168.6.150
    MAC is 50:7B:9D:30:2E:4B
    Zone is

    ผมก็ตรวจสอบด้วยวิธีการ debug คือ เพิ่ม -x ดังตัวอย่างนี้

    $ bash -x test.sh
     ++ ipconfig /all
     ++ grep -i 'DHCP Server'
     ++ cut -d: -f2
     ++ xargs
     + DHCPSERVER=$'192.168.6.150\r'
     ++ ipconfig /all
     ++ grep -A4 -i '^Ethernet Adapter Ethernet'
     ++ tail -1
     ++ cut -d: -f2
     ++ tr - :
     ++ xargs
     + MAC=$'50:7B:9D:30:2E:4B\r'
     ++ curl -s $'http://192.168.6.150\r/dhcpd.txt'
     ++ grep -i $'50:7B:9D:30:2E:4B\r'
     ++ awk '{print $2}'
     ++ cut -d_ -f1
     + ZONEX=
     ' echo 'DHCP SERVER is 192.168.6.150
     DHCP SERVER is 192.168.6.150
     ' echo 'MAC is 50:7B:9D:30:2E:4B
     MAC is 50:7B:9D:30:2E:4B
     + echo 'Zone is '
     Zone is

    จึงพบว่า ตัวแปร DHCPSERVER และ ตัวแปร MAC จะมี “/r” แถมมาให้ด้วย ซึ่งเป็นส่วนเกินที่ทำให้คำสั่งถัดไปทำงานผิดพลาดทำให้ได้ค่า ZONEX เป็น ว่างเปล่า

     

    วิธีแก้ไข เราต้องใส่ option ” -o igncr ” หลังคำสั่ง bash เพื่อให้ตัด “/r” ออกไป

    สั่งรันดูผลลัพธ์ด้วยคำสั่ง bash -o igncr test.sh

    ผลลัพธ์คราวนี้ถูกต้องแล้ว

    $ bash -o igncr test.sh
    DHCP SERVER is 192.168.6.150
    MAC is 50:7B:9D:30:2E:4B
    Zone is zone1

     

    วิธีแก้อีกวิธีคือ หากไม่ใส่ option ที่ว่านี้ เราก็ต้องไปแก้ไขบรรทัดคำสั่ง เพื่อเติม sed ‘s/\r$//’ ต่อท้าย เป็นการตัด /r ออกไปก่อนนำค่าที่ได้ไปใส่ในตัวแปร DHCPSERVER
    เช่น

    DHCPSERVER=$(ipconfig /all | grep -i "DHCP Server" | cut -d: -f2 | xargs | sed 's/\r$//')

    ซึ่งก็ทำได้เช่นกัน แต่ต้องแก้ไขไฟล์ shell script ซึ่งก็แล้วแต่ชอบวิธีไหน

     

    Reference
    https://stackoverflow.com/users/1010997/user1010997
    https://stackoverflow.com/questions/11616835/r-command-not-found-bashrc-bash-profile
    https://stackoverflow.com/questions/18608380/r-command-not-found

  • เตาะแตะไปกับ Docker ตอนที่ 13 Docker Machine

    Docker Machine คือ tool ที่ใช้สำหรับจัดเตรียม (Provision) docker เป็น virtual hosts บน Mac หรือ Windows รวมถึง ติดตั้งเพิ่ม docker บน Native Linux Host ที่มีอยู่แล้วในเครือข่ายของคุณ ยิ่งไปกว่านั้นยังสามารถจัดเตรียม docker ไปบน cloud providers เช่น Azure, AWS, or Digital Ocean เป็นต้น

    Docker Machine จัดการ remote docker host เหล่านี้ด้วยการใช้คำสั่ง docker-machine

    การติดตั้ง Docker Machine ขึ้นอยู่กับว่าจะใช้บนระบบปฏิบัติการอะไร ถ้าเป็น Windows ก็ให้ติดตั้ง Docker for Windows ซึ่งจะได้ Docker Engine และ Docker Machine มาด้วยเลย แต่ถ้าระบบปฏิบัติการ Windows นั้นไม่ผ่าน requirements ที่จะรัน Docker for Windows ได้ คุณก็เปลี่ยนไปติดตั้ง Docker Toolbox แทนได้ ซึ่งต้องใช้ร่วมกับ Oracle VM VirtualBox แทน Hyper-V

    การใช้งาน Docker Machine จาก Windows ที่ติดตั้ง Docker Toolbox
    Docker Machine จะสร้าง VM พร้อมติดตั้ง Docker Engine ให้ด้วย
    ตัวอย่างการใช้งาน ทำนองนี้

    docker-machine create --driver virtualbox vm1

    เมื่อจะเข้าไปใช้เครื่อง vm1 ทำดังนี้

    eval "$(docker-machine env vm1)"

    และทำคำสั่งอีก 1 คำสั่งตามที่มีข้อความแสดงขึ้นมา

    ตอนนี้เราก็สามารถใช้งาน docker ที่เครื่อง vm1 ได้แล้ว เช่น ทดสอบว่า docker ทำงานได้แล้ว

    docker run hello-world

    ออกโดยใช้คำสั่ง

    exit

    หรือหากต้องการรันคำสั่งครั้งเดียวที่เครื่อง vm1 โดยไม่ต้องเข้าไป ก็ใช้คำสั่งทำนองนี้

    docker-machine ssh vm1 "docker ps"

     

    การติดตั้ง Docker Machine บนระบบปฏิบัติการ Linux

    สำหรับการติดตั้ง Docker Machine บนระบบปฏิบัติการ Linux (ทดสอบกับ Ubuntu 16.04 server) จะต้องใช้คำสั่งดังนี้

    curl -L https://github.com/docker/machine/releases/download/v0.13.0/docker-machine-`uname -s`-`uname -m` >/tmp/docker-machine && \
    chmod +x /tmp/docker-machine && \
    sudo cp /tmp/docker-machine /usr/local/bin/docker-machine

     

    การใช้งาน Docker Machine จาก Linux เพื่อติดตั้ง docker engine ไปบน ubuntu ที่ยังไม่ได้ติดตั้ง docker ไว้ (To provision Docker hosts on remote systems)

    จะมีขั้นตอนหลายขั้นดังนี้

    1. ที่เครื่องใช้งานที่เราติดตั้ง docker-machine ไว้ ให้เราสร้าง public, private key pair ด้วยคำสั่ง “ssh-keygen -t rsa” และไม่ต้อง protect private key ด้วย password ด้วยนะ เพราะว่า docker-machine ทำงานไม่ได้

    2. ส่ง public key ด้วยคำสั่ง “ssh-copy-id” ไปยัง Linux host ที่เราจะจัดการ

    3. ที่ Linux host ที่เราจะจัดการ ต้องอนุญาตให้ใช้คำสั่ง sudo แบบ passwordless เพื่อให้ docker-machine สามารถ access เข้าไปได้ ทำด้วยคำสั่ง “sudo visudo” และเพิ่มบรรทัดนี้ต่อท้ายไฟล์
    %sudo ALL=(ALL) NOPASSWD:ALL

     

    เพิ่มเติม

    หากต้องการใช้ docker-machine จาก Windows ไป provision linux host ก็ให้ก็อปปี้ private key ไปใส่ใน Windows host ก่อนที่จะรัน docker-machine โดยตรวจสอบให้แน่ใจว่า ssh ใช้ key นี้ได้ด้วยคำสั่ง “ssh -i <private key> <userid>@<host>” ซึ่งเป็นการทดสอบว่าเรา ssh เข้าไปยัง Linux host นั้นได้โดยไม่ต้องใส่ password

     

    สำหรับการใช้ Docker Machine เพื่อติดตั้ง docker engine ไปบน cloud providers นั้นก็สามารถใช้ได้ แต่ในเอกสารของ docker แนะนำให้ใช้ Docker Cloud จะดีกว่า เนื้อหาส่วนนี้ผมจะไม่ลงรายละเอียด ให้อ่านเพิ่มเติมจากอ้างอิงด้านล่าง

     

    การใช้งานคำสั่ง docker-machine เพื่อสั่งทำงาน Swarm Cluster

    ต่อไปเป็นตัวอย่างที่ประกอบด้วยเครื่องจำนวน 2 เครื่อง เครื่องแรกเป็น swarm manager และเครื่องที่สองเป็น swarm worker ซึ่งเรื่องนี้ เราได้ทดสอบการทำงานแบบ manual ไปแล้วใน “เตาะแตะไปกับ Docker ตอนที่ 3 Swarm” ซึ่งผู้อ่านจะพบว่าในบทความตอนที่ 3 นั้น เราจะต้องแก้ไขชื่อ hostname ให้กับ ubuntu server ที่จะเป็น swarm manager และ ที่จะเป็น swarm worker แต่ตอนนี้ docker-machine จะช่วยเราตั้งชื่อ hostname ในไฟล์ /etc/hosts และ /etc/hostname ให้อัตโนมัติ และติดตั้ง Docker Engine ให้ด้วย

    ตัวอย่าง ผมทดสอบด้วย VM จำนวน 3 เครื่อง ใช้ username ชื่อ mama ดังนี้
    เครื่อง A
    – Ubuntu server
    – IP 10.0.2.9
    เครื่อง B
    – Ubuntu server
    – IP 10.0.2.10
    เครื่อง C
    – Ubuntu server ที่ได้ติดตั้ง docker-machine เพิ่มแล้ว
    – IP 10.0.2.11

     

    คำสั่งที่ทำที่เครื่อง A และ B

    sudo visudo

    เพิ่มบรรทัดนี้ต่อท้าย (ผมใช้ username ชื่อ mama ที่อยู่ใน group sudo ในการรัน docker)

    %sudo ALL=(ALL) NOPASSWD:ALL

     

    คำสั่งที่ทำที่เครื่อง C

    ssh-keygen -t rsa
    ssh-copy-id -i ~/.ssh/id_rsa.pub mama@10.0.2.9
    ssh-copy-id -i ~/.ssh/id_rsa.pub mama@10.0.2.10
    docker-machine create -d generic --generic-ip-address 10.0.2.9 --generic-ssh-user mama --generic-ssh-key ~/.ssh/id_rsa docker-vm1
    docker-machine create -d generic --generic-ip-address 10.0.2.10 --generic-ssh-user mama --generic-ssh-key ~/.ssh/id_rsa docker-vm2

    ตรวจสอบว่า เรามี machine อะไรบ้าง ด้วยคำสั่ง

    docker-machine ls

    ผลลัพธ์

    NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
    docker-vm1 - generic Running tcp://10.0.2.9:2376 v17.06.0-ce 
    docker-vm2 - generic Running tcp://10.0.2.10:2376 v17.06.0-ce

     

    เรามาดูกันว่ามันง่ายมาก ๆ เลยครับในการใช้คำสั่ง docker-machine จากเครื่อง C ไปสั่งให้เครื่อง A และ เครื่อง B ทำการรัน application ชื่อ testlab

    ที่เครื่อง C เราสั่งคำสั่งนี้ เพื่อให้เครื่อง A เป็น swarm manager

    docker-machine ssh docker-vm1 "docker swarm init --advertise-addr 10.0.2.9"

    ที่เครื่อง C เราสั่งคำสั่งนี้ เพื่อให้เครื่อง B เป็น swarm worker

    docker-machine ssh docker-vm2 "docker swarm join --token SWMTKN-1-61mjquxxkwwkc0tphusc3j98bmhnw5yeh1ykhkp6lp8l8z9em4-46ngusa5zoanprd7042avulpz 10.0.2.9:2377"

    ที่เครื่อง C เราสั่งคำสั่งนี้ เพื่อรัน application ชื่อ testlab

    docker-machine ssh docker-vm1 "cd ~/myservice ; docker stack deploy -c docker-compose.yml testlab"

    ตอนนี้เราก็ได้เปิดบริการ testlab เรียบร้อย

    เราจะตรวจสอบดูว่ามี container ถูกรันไว้ในเครื่อง A และ เครื่อง B ดังนี้

    docker-machine ssh docker-vm1 "docker ps"
    docker-machine ssh docker-vm2 "docker ps"

    และเมื่อเราต้องการจะ stop application เราก็สั่งดังนี้

    docker-machine ssh docker-vm1 "docker stack rm testlab"
    docker-machine ssh docker-vm1 "docker swarm leave --force"
    docker-machine ssh docker-vm2 "docker swarm leave --force"

    สะดวกมากจริง ๆ ครับ ลองทดสอบใช้งานดูนะครับ

     

    Output ของคำสั่งที่ใช้

    mama@ubuntu:~$ curl -L https://github.com/docker/machine/releases/download/v0.13.0/docker-machine-`uname -s`-`uname -m` >/tmp/docker-machine && chmod +x /tmp/docker-machine && sudo cp /tmp/docker-machine /usr/local/bin/docker-machine
    % Total % Received % Xferd Average Speed Time Time Time Current
    Dload Upload Total Spent Left Speed
    100 617 0 617 0 0 354 0 --:--:-- 0:00:01 --:--:-- 354
    100 25.3M 100 25.3M 0 0 99879 0 0:04:26 0:04:26 --:--:-- 221k
    [sudo] password for mama:

    mama@ubuntu:~$ docker-machine create -d generic --generic-ip-address 10.0.2.9 --generic-ssh-user mama --generic-ssh-key ~/.ssh/id_rsa docker-vm1
    Creating CA: /home/mama/.docker/machine/certs/ca.pem
    Creating client certificate: /home/mama/.docker/machine/certs/cert.pem
    Running pre-create checks...
    Creating machine...
    (docker-vm1) Importing SSH key...
    Waiting for machine to be running, this may take a few minutes...
    Detecting operating system of created instance...
    Waiting for SSH to be available...
    Detecting the provisioner...
    Provisioning with ubuntu(systemd)...
    Installing Docker...
    Copying certs to the local machine directory...
    Copying certs to the remote machine...
    Setting Docker configuration on the remote daemon...
    Checking connection to Docker...
    Docker is up and running!
    To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env docker-vm1
    mama@ubuntu:~$

    mama@ubuntu:~$ docker-machine ls
    NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
    docker-vm1 - generic Running tcp://10.0.2.9:2376 v17.06.0-ce
    docker-vm2 - generic Running tcp://10.0.2.10:2376 v17.06.0-ce

    mama@ubuntu:~$ docker-machine ssh docker-vm1 "docker swarm init --advertise-addr 10.0.2.9"
    Swarm initialized: current node (k8x7e19z0iedo3yfcokeu2jpt) is now a manager.

    To add a worker to this swarm, run the following command:

    docker swarm join --token SWMTKN-1-61mjquxxkwwkc0tphusc3j98bmhnw5yeh1ykhkp6lp8l8z9em4-46ngusa5zoanprd7042avulpz 10.0.2.9:2377

    To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
    This node joined a swarm as a worker.

    mama@ubuntu:~$ docker-machine env docker-vm1
    export DOCKER_TLS_VERIFY="1"
    export DOCKER_HOST="tcp://10.0.2.9:2376"
    export DOCKER_CERT_PATH="/home/mama/.docker/machine/machines/docker-vm1"
    export DOCKER_MACHINE_NAME="docker-vm1"
    # Run this command to configure your shell:
    # eval $(docker-machine env docker-vm1)

    mama@ubuntu:~$ docker-machine ssh docker-vm2 "docker swarm join --token SWMTKN-1-61mjquxxkwwkc0tphusc3j98bmhnw5yeh1ykhkp6lp8l8z9em4-46ngusa5zoanprd7042avulpz 10.0.2.9:2377"
    This node joined a swarm as a worker.

    mama@ubuntu:~$ docker-machine ssh docker-vm1 "cd ~/myservice ; docker stack deploy -c docker-compose.yml testlab"
    Creating network testlab_webnet
    Creating service testlab_redis
    Creating service testlab_web
    Creating service testlab_visualizer

    mama@ubuntu:~$ docker-machine ssh docker-vm1 "docker ps"
    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
    e50ac8163d9a redis:latest "docker-entrypoint..." About a minute ago Up About a minute 6379/tcp testlab_redis.1.iu0rtua66lrfrzgsdn6vu2kf9
    368530178c51 woonpsu/docsdocker:part1 "python app.py" 2 minutes ago Up 2 minutes 80/tcp testlab_web.4.njqokwoqie76knjurptkm35qu
    63c811bc1772 woonpsu/docsdocker:part1 "python app.py" 2 minutes ago Up 2 minutes 80/tcp testlab_web.6.urlvghe3yx6p6ljsg5l4x9ix8
    130e4967165a woonpsu/docsdocker:part1 "python app.py" 2 minutes ago Up 2 minutes 80/tcp testlab_web.2.020dbtitrcx0b876ryz5xf83r

    mama@ubuntu:~$ docker-machine ssh docker-vm1 "docker stack rm testlab"
    Removing service testlab_web
    Removing service testlab_redis
    Removing service testlab_visualizer
    Removing network testlab_webnet

    mama@ubuntu:~$ docker-machine ssh docker-vm1 "docker swarm leave --force"
    Node left the swarm.

    mama@ubuntu:~$ docker-machine ssh docker-vm2 "docker swarm leave --force"
    Node left the swarm.

     

    References:

  • เตาะแตะไปกับ Docker ตอนที่ 12 Clone Docker Files From GitHub

    จากบล็อกเรื่อง “เตาะแตะไปกับ Docker” ในตอนที่แล้ว เราได้เรียนรู้ขั้นตอนในการสร้างและรัน LDAP services อย่างละเอียด ก่อนที่ผมจะเขียนบทความในตอนนี้ผมได้นำไฟล์ทุกไฟล์นั้นขึ้นไปไว้บน GitHub เพื่อให้พวกเราไม่ต้องสร้างไฟล์เหล่านั้นเอง แต่จะทำด้วยคำสั่ง git clone ครับ

    เรายังคงทดสอบด้วย VM โดยตั้งค่า network เป็น NAT network เพราะว่า เราใช้ domain สมมติ คือ ldap.example.com และทดสอบในสภาพแวดล้อมที่เป็นเครือข่ายภายใน

    ขั้นตอนดังนี้

    1. ติดตั้ง Ubuntu 16.04 64 bit แล้วติดตั้ง docker 17.06.2-ce แล้วต้องเปลี่ยนชื่อ hostname เป็น ldap.example.com จากนั้นรีบูตเครื่อง

    2. ติดตั้ง docker-compose 1.8.0-2 (ถ้ายังไม่ได้ทำ) ด้วยคำสั่งดังนี้

    sudo apt install docker-compose

    3. ทำการ Clone project จาก github ด้วยคำสั่ง

    git clone https://github.com/woonpsu/ex1-openldap.git

    4. ทำคำสั่งตามนี้

    cd ex1-openldap
    docker-compose up -d

    หมายเหตุ
    หากยังไม่มี image ชื่อ ubuntu:16.04 ก็จะใช้เวลานานสักหน่อยเมื่อรันคำสั่ง docker-compose เพราะว่าจะมีการสร้าง image ubuntu:16.04 ให้ใหม่

    5. ใช้คำสั่งเหล่านี้เพื่อตรวจสอบ

    docker images
    docker ps
    docker volume ls

    6. เพิ่ม users ตัวอย่าง

    sudo apt install ldap-utils
    ldapadd -H ldap://ldap.example.com -f ./openldap/src/create-users.ldif -x -D "cn=admin,dc=example,dc=com" -w 123456

    7. ทดสอบเข้าใช้งานเว็บจากเครื่องใน NAT network เดียวกัน หรือที่เครื่อง VM server ที่ผมได้ติดตั้ง Desktop Environment ไว้แล้ว ก็ต้องเปลี่ยนจาก text เป็น graphic ด้วยคำสั่ง

    $ startx

    เข้า browser ไปที่

    http://ldap.example.com:8080/phpldapadmin/

     

    สำหรับวิธีการทำ GitHub Repository ชื่อ ex1-openldap ของผมอย่างคร่าว ๆ คือ
    1. เข้า browser ไป sign up แบบ free ที่ github.com ด้วย username ชือ woonpsu

    2. สร้าง Repository ชื่อ ex1-openldap

    3. ไปที่เครื่อง server ใช้คำสั่ง

    git clone https://github.com/woonpsu/ex1-openldap.git

    4. เตรียมไฟล์

    cd ex1-openldap
    git init

    5. สร้างไฟล์ docker-compose.yml และไดเรกทอรี openldap กับ phpldapadmin ให้เสร็จ

    6. ทำการส่งขึ้นไปไว้บน github

    git add .
    git commit -m “This is a first commit”
    git push -u origin master

    ใส่ username และ password ของเราที่ sign up กับ github

     

    หมายเหตุ

    • หากต้องการใช้ VM ที่ผมเตรียมไว้ จะไม่ต้องติดตั้ง Ubuntu server ก็ download docker-vm file size 1.2G แล้ว import เข้า Oracle VM Virtualbox ทดสอบดูได้ (เข้าด้วย mama/123456)

     

  • เตาะแตะไปกับ Docker ตอนที่ 11 Docker Compose (LDAP services)

    เราจะมาเรียนรู้ การใช้ docker compose รัน service 2 services คือ OpenLDAP และ phpLDAPadmin ซึ่ง docker จะมองว่าการใช้ docker compose คือ เรากำลังรัน project ที่ประกอบด้วย service หลาย ๆ service

    ให้แน่ใจว่าอยู่ที่ home directory ให้ใช้คำสั่งนี้

    $ cd

    สร้างไดเรกทอรีของ project สมมติตั้งชื่อว่า ex1

    $ mkdir ex1
    $ cd ex1

    สร้างไดเรกทอรี openldap ภายในมี dockerfile และไดเรกทอรีที่เกี่ยวข้อง ทำตามบล็อกเรื่อง “เตาะแตะไปกับ Docker ตอนที่ 9 Dockerfile (OpenLDAP)

    สร้างไดเรกทอรี phpldapadmin ภายในมี dockerfile และไดเรกทอรีที่เกี่ยวข้อง ทำตามบล็อกเรื่อง “เตาะแตะไปกับ Docker ตอนที่ 10 Dockerfile (phpLDAPadmin)

    สร้างไฟล์ docker-compose.yml ด้วยเอดิเตอร์ที่ถนัด เช่น vi หรือ nano ก็ได้

    $ vi docker-compose.yml
    
    version: '2'
    services:
     openldap:
      build: ./openldap
      container_name: openldap
      volumes:
       - ldapdatavol:/var/lib/ldap
       - ldapconfigvol:/etc/ldap/slapd.d
      ports:
       - "389:389"
       - "636:636"
      restart: always
    
     phpldapadmin:
      build: ./phpldapadmin
      container_name: phpldapadmin
      environment:
       HNAME: "openldap"
      ports:
       - "8080:80"
      depends_on:
       - openldap
      restart: always
    
    volumes:
     ldapdatavol:
      external: false
     ldapconfigvol:
      external: false

    อธิบายได้ดังนี้
    ไฟล์ docker-compose.yml นี้ จะมี service แรกคือ openldap จะ build image จากไดเรกทอรี ./openldap เมื่อรันเป็น container จะตั้งชื่อว่า openldap โดยมีที่เก็บข้อมูลถาวรคือ ldapdatavol จะ mapped ไปยัง /var/lib/ldap ใน container และที่เก็บคอนฟิก ldapconfigvol จะ mapped ไปยัง /etc/ldap/slapd.d ใน container เช่นเดียวกัน
    โดย Host และ container เปิด port ตรงกัน คือ เปิด port TCP 636(LDAPS) และ 389(LDAP) และ container นี้จะทำงานทุกครั้งเมื่อเปิดเครื่อง

    ต่อมา service ที่สองคือ phpldapadmin จะ build image จากไดเรกทอรี ./phpldapadmin เมื่อรันเป็น container จะตั้งชื่อว่า phpldapadmin มีการตั้งค่าตัวแปร HNAME เป็นชื่อ container อันแรก คือ openldap โดยที่ Host จะเปิด port TCP 8080 ไปยัง port TCP 80 ของ container นี้ ถัดมาคือ depends_on คือ จะรัน container นี้ได้ก็ต่อเมื่อมี container ชื่อ openldap และ container นี้จะทำงานทุกครั้งเมื่อเปิดเครื่อง

    ท่อนล่างสุด คือ การกำหนดว่า จะมีการใช้ named volume ชื่อ ldapdatavol และ ldapconfigvol ทั้งสอง volume นี้ จะถูกสร้างโดยคำสั่ง docker-compose ไม่ได้ไปใช้ volume จากที่ได้สร้างไว้ก่อนแล้ว (external: false) หากจะย้อนกลับไปอ่านเรื่อง “เตาะแตะไปกับ Docker ตอนที่ 7 Manage data” จะเข้าใจเรื่อง named volume ครับ

    ผลลัพธ์หลังจากเตรียมครบ ภายในไดเรกทอรี ex1 จะมี 1 ไฟล์ และ 2 ไดเรกทอรี ดังนี้

    $ ls 
    docker-compose.yml openldap phpldapadmin

    ตอนนี้เราก็เตรียมการต่าง ๆ เสร็จแล้ว

    ต่อไปก็ใช้คำสั่ง docker-compose เพื่อสร้าง service คือ openldap และ phpldapadmin ดังนี้

    $ docker-compose up -d

    ตรวจสอบรายการ image

    $ docker images
    REPOSITORY TAG IMAGE ID CREATED SIZE
    phpldapadmin latest d314c021fd1a 6 hours ago 282MB
    ex1_phpldapadmin latest f2feaa90c59b 9 days ago 282MB
    ex1_openldap latest e98f6bad71a7 9 days ago 259MB
    openldap latest cb8b1c0057cc 11 days ago 259MB
    ubuntu 16.04 ccc7a11d65b1 5 weeks ago 120MB

    จะพบว่ามี image ของ project ex1 คือ ex1_openldap และ ex1_phpldapadmin ส่วน image ชื่อ openldap และ phpldapadmin สองอันนั้นที่เห็นเป็น image ที่สร้างด้วยคำสั่ง docker build ตอนที่เราเรียนรู้เรื่อง “เตาะแตะไปกับ Docker ตอนที่ 9 Dockerfile (OpenLDAP)” และ “เตาะแตะไปกับ Docker ตอนที่ 10 Dockerfile (phpLDAPadmin)” ครับ

    ตรวจสอบรายการ container ที่รันอยู่

    $ docker ps
    CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
    b61e8f24802c ex1_phpldapadmin "/bin/bash /run.sh..." 3 minutes ago Up 3 minutes 0.0.0.0:8080->80/tcp phpldapadmin
    489bb2db70d3 ex1_openldap "/bin/sh -c '/usr/..." 3 minutes ago Up 3 minutes 0.0.0.0:389->389/tcp, 0.0.0.0:636->636/tcp openldap

    จะพบว่ามี container ชื่อว่า openldap และ phpldapadmin เกิดขึ้นหลังคำสั่ง docker-compose up -d

    ทดสอบการเข้าใช้งานทาง web ไปที่ http://ldap.example.com:8080/phpldapadmin/ (เนื่องจาก VM ที่ใช้ทดสอบ คือ ubuntu ที่ได้ติดตั้ง XFCE desktop environment ไว้ด้วยทำให้สามารถใช้งาน web browser ได้ด้วย)

    หลังจาก login แล้วจะเห็นข้อมูล LDAP และจัดการ LDAP database ได้

    จากความรู้ที่ได้เรียนมาหลาย ๆ ตอนจนถึงตอนนี้ เราก็น่าจะได้แนวทางในการสร้าง service ความเข้าใจอย่างง่าย ๆ คือ หากเป็น image ที่เราจะทำขึ้นเอง ไม่ได้ไป pull จาก docker hub เราก็ทำด้วย dockerfile และหากมี service ที่ใช้ร่วมกันระหว่าง service ก็เปลี่ยนไปทำต่อด้วย docker-compose จะสะดวกกว่า

    อนึ่งหาก pull หรือมี image อยู่แล้ว ก็เปลี่ยนจาก build: ./openldap เป็น image: openldap และปรับแก้ไขนิดหน่อยตามที่ผู้พัฒนา image นั้นได้แจ้งวิธีการไว้ครับ

    จากที่ได้เขียนบล็อกในชุด “เตาะแตะไปกับ Docker” ตั้งแต่ ตอนที่ 1 ถึง ตอนที่ 11 นี้ ผมคิดว่าก็น่าจะเพียงพอให้เข้าใจว่า docker ใช้งานอย่างไร หวังเป็นอย่างยิ่งว่าผู้อ่านคงได้รับความรู้กันครับ และสามารถลองทำตามได้โดยไม่ติดขัดอะไร

     

  • เตาะแตะไปกับ Docker ตอนที่ 10 Dockerfile (phpLDAPadmin)

    ในตอนที่แล้วเราใช้งาน openldap ด้วยคำสั่ง เช่น ldapsearch หรือ ldapadd ได้แล้ว วันนี้เราจะเรียนรู้การใช้ dockerfile สร้าง image ชื่อ phpldapadmin เพื่อใช้เป็น web interface ในการเข้าไปจัดการ LDAP database ของ container ชื่อ openldap โดยที่ phpLDAPadmin นี้คือการรัน php บน apache2 แล้วเราจะติดต่อระหว่าง container กันได้อย่างไร มาดูกันครับ

    เช่นเดียวกับครั้งที่แล้ว ผมจะแยกเป็น 3 ขั้นตอน คือ 1.ขั้นตอนเตรียมไฟล์ที่เกี่ยวข้อง 2.ขั้นตอนสร้างไฟล์ dockerfile และ 3.ขั้นตอนการรัน container

     

    1.ขั้นตอนเตรียมไฟล์ที่เกี่ยวข้อง

    ให้แน่ใจว่าอยู่ที่ home directory ให้ใช้คำสั่งนี้

    $ cd

    สร้างไดเรกทอรีของ image ที่เราจะสร้าง

    $ mkdir phpldapadmin
    $ cd phpldapadmin
    $ mkdir src

    เราจะเตรียมไฟล์ชื่อ apache2-foreground ให้เปิดไฟล์จากที่นี่ https://github.com/docker-library/php/blob/master/5.6/jessie/apache/apache2-foreground แล้วคัดลอกทุกบรรทัด นำมาสร้างไฟล์ ด้วยเอติเตอร์ที่ถนัด เช่น vi หรือ nano ก็ได้

    $ vi ./src/apache2-foreground

     

    2.ขั้นตอนสร้างไฟล์ dockerfile

    ตอนนี้ก็มาถึงขั้นตอนการเขียน dockerfile ด้วยเอดิเตอร์ที่ถนัดเช่น vi หรือ nano ก็ได้

    $ vi dockerfile
    
    # Composer: Wiboon Warasittichai
    
    FROM ubuntu:16.04
    
    # Change apt source
    RUN sed -i 's/\/us.archive/\/th.archive/g' /etc/apt/sources.list && \
     sed -i 's/\/archive/\/th.archive/g' /etc/apt/sources.list
    
    # Update ubuntu, then install packages
    RUN apt-get update && \
    DEBIAN_FRONTEND=noninteractive apt install -y php-ldap phpldapadmin ldap-utils
    
    # Timezone Asia/Bangkok
    RUN apt-get install -y tzdata && \
     ln -sf /usr/share/zoneinfo/Asia/Bangkok /etc/localtime && \
     dpkg-reconfigure -f noninteractive tzdata
    
    # Cleaning
    RUN apt-get autoremove -y && apt-get clean -y
    
    EXPOSE 80
    
    CMD RUN ulimit -n 1024
    
    ENV HNAME ${HNAME}
    ADD ./src/startup.sh /startup.sh
    
    COPY ./src/apache2-foreground /usr/local/bin/
    RUN chmod +x /usr/local/bin/apache2-foreground
    
    ENTRYPOINT ["/bin/bash","/startup.sh"]

     

    ภายในไฟล์ dockerfile เราได้อ้างถึงการนำไฟล์ชื่อ startup.sh ก็ให้สร้างไฟล์นี้ด้วย

    $ vi ./src/startup.sh 
    
    #!/bin/bash
    
    sed -i "s/127.0.0.1/$HNAME/g" /etc/phpldapadmin/config.php
    /usr/local/bin/apache2-foreground

    จุดน่าสนใจในไฟล์ dockerfile ในตัวอย่างนี้ คือ เราจะเขียนอย่างไรเพื่อให้เราเปลี่ยนค่า 127.0.0.1 ในไฟล์ /etc/phpldapadmin/config.php ใน container ที่รันขึ้นมาได้อัตโนมัติ เราจึงต้องใช้ ENV ในการระบุว่า container openldap ที่รันอยู่นั้นใช้ IP อะไร แล้วเราก็นำ IP ใส่ลงไปในตัวแปร HNAME เพื่อจะติดต่อไปยัง container openldap ได้ถูกต้อง ผมจึงเพิ่มบรรทัดนี้

    ENV HNAME ${HNAME}

     

    3.ขั้นตอนการรัน container

    สร้าง image ด้วยคำสั่งนี้

    $ docker build -t phpldapadmin .

    ก่อนจะรัน container phpldapadmin ต้องไม่ลืมว่า เราจะต้องรัน container openldap ด้วยคำสั่งนี้แล้ว

    $ docker run -d -p 636:636 -p 389:389 --name openldap --volume oldata:/var/lib/ldap --volume olconfig:/etc/ldap/slapd.d openldap

    รัน container และ ตรวจสอบ ด้วยคำสั่งนี้

    $ LDAPSERVER=$(docker exec -it openldap cat /etc/hosts | tail -1 | cut -f1) && docker run -d -p 8080:80 --env HNAME=${LDAPSERVER} --name phpldapadmin phpldapadmin
    
    $ docker ps

    อธิบายได้ดังนี้ container จะรันแบบ detach (-d) เปิด port 8080 ที่ Host เข้าไปยัง port 80 ที่ container ตั้งชื่อ(–name) ว่า phpldapadmin โดยกำหนดค่า ENV สำหรับตัวแปรที่ชื่อ HNAME ให้ใช้ IP จากไฟล์ /etc/hosts ใน container openldap และรันจาก image ชื่อ phpldapadmin

    ทดสอบการเข้าใช้งานทาง web ไปที่ http://ldap.example.com:8080/phpldapadmin/ (เนื่องจาก VM ที่ใช้ทดสอบ คือ ubuntu ที่ได้ติดตั้ง XFCE desktop environment ไว้ด้วยทำให้สามารถใช้งาน web browser ได้ด้วย)

    หลังจาก login แล้วจะเห็นข้อมูล LDAP และจัดการ LDAP database ได้

    โปรดสังเกตว่าคำสั่ง docker run นั้น หากต้องการให้รันทำงานทุกครั้งที่เปิดเครื่อง เราจะสามารถใส่ option นี้เข้าไปได้ด้วย คือ –restart always ใส่หลังจากคำว่า docker run ก็ได้ครับ (ในตัวอย่างข้างต้น ผมไม่ได้ใส่ไว้ เพราะกำลังทดสอบ) หากเรา reboot เครื่อง container ทั้ง 2 ก็หายไปครับ

    แต่เราก็สามารถลบ container ได้ทันที ดังนี้

    $ docker stop phpldapadmin
    $ docker rm phpldapadmin
    $ docker stop openldap
    $ docker rm openldap

    สรุปว่า ในตอนนี้ เรารู้วิธีการใช้ dockerfile สร้าง container 2 อัน อันแรกคือ LDAP database และอีกอันคือ php web program ที่จะติดต่อกับ LDAP database ด้วยการใช้ตัวแปร ENV ร่วมด้วย

    ในตอนต่อไป เราจะมาเรียนรู้ การใช้ docker compose รันทั้ง 2 services นี้แทนการแยกสร้าง dockerfile และ run แยกกัน

     

  • เตาะแตะไปกับ Docker ตอนที่ 9 Dockerfile (OpenLDAP)

    วันนี้เราจะเรียนรู้การใช้ dockerfile สร้าง image ชื่อ openldap เพื่อใช้เป็น LDAP database
    ผมทดสอบด้วย Oracle VM VirtualBox เป็น VM ที่ตั้งค่า Network adapter เป็นแบบ NAT network ที่ติดตั้ง Ubuntu 16.04 และ docker เรียบร้อยแล้ว ในตัวอย่างนี้ผมตั้งชื่อ host ว่า ldap.example.com โดยแก้ไขที่ไฟล์ /etc/hosts และ /etc/hostname ให้เรียบร้อย แล้วรีบูตเครื่องด้วย

    ผมจะขอแยกขั้นตอนออกเป็น 3 ขั้นตอนหลัก คือ 1.ขั้นตอนเตรียมไฟล์ที่เกี่ยวข้องซึ่งรวมไฟล์ ldif ด้วย 2.ขั้นตอนสร้างไฟล์ dockerfile และ 3.ขั้นตอนการรัน container

     

    1.ขั้นตอนเตรียมไฟล์ที่เกี่ยวข้อง
    สร้างไดเรกทอรีของ image ที่เราจะสร้าง

    $ mkdir openldap

    สร้างไดเรกทอรีที่เก็บไฟล์ ldif (โครงสร้าง และ ตัวอย่างข้อมูล)

    $ cd openldap
    $ mkdir src

    สร้างไฟล์ ./src/create-schema.ldif ด้วยเอติเตอร์ที่ถนัด เช่น vi หรือ nano ก็ได้

    $ vi ./src/create-schema.ldif 
    # Starter kit for create user in domain dc=example,dc=com
    # Create 1st tree OU=groups
    dn: ou=groups,dc=example,dc=com
    objectClass: organizationalUnit
    ou: groups
    
    # Create sub tree OU=execs,OU=groups
    dn: ou=execs,ou=groups,dc=example,dc=com
    objectClass: organizationalUnit
    ou: execs
    
    # Create sub tree OU=staffs,OU=groups
    dn: ou=staffs,ou=groups,dc=example,dc=com
    objectClass: organizationalUnit
    ou: staffs
    
    # Create sub tree OU=students,OU=groups
    dn: ou=students,ou=groups,dc=example,dc=com
    objectClass: organizationalUnit
    ou: students
    
    # Create 2nd tree OU=people
    dn: ou=people,dc=example,dc=com
    objectClass: organizationalUnit
    ou: people

    สร้างไฟล์ ssl.ldif

    $ vi src/ssl.ldif
    dn: cn=config
    changetype: modify
    add: olcTLSCipherSuite
    olcTLSCipherSuite: NORMAL
    -
    add: olcTLSCRLCheck
    olcTLSCRLCheck: none
    -
    add: olcTLSVerifyClient
    olcTLSVerifyClient: never
    -
    add: olcTLSCertificateFile
    olcTLSCertificateFile: /etc/ssl/certs/ldap-ca-cert.pem
    -
    add: olcTLSCertificateKeyFile
    olcTLSCertificateKeyFile: /etc/ssl/private/ldap-ca-key.pem

    สร้างไฟล์ ./src/create-users.ldif

    $ vi ./src/create-users.ldif 
    # Create 1 user in tree OU=execs,OU=groups
    dn: cn=nana,ou=execs,ou=groups,dc=example,dc=com
    objectClass: inetOrgPerson
    uid: nana
    sn: Na
    givenName: Na
    cn: nana
    displayName: Na Na
    userPassword: 123456
    mail: nana@example.com
    
    # Create 2 users in tree OU=staffs,OU=groups
    dn: cn=koko,ou=staffs,ou=groups,dc=example,dc=com
    objectClass: inetOrgPerson
    uid: koko
    sn: Ko
    givenName: Ko
    cn: koko
    displayName: Ko Ko
    userPassword: 123456
    mail: koko@example.com
    
    dn: cn=momo,ou=staffs,ou=groups,dc=example,dc=com
    objectClass: inetOrgPerson
    uid: momo
    sn: Mo
    givenName: Mo
    cn: momo
    displayName: Mo Mo
    userPassword: 123456
    mail: momo@example.com
    
    # Create 2 users in tree OU=people
    dn: cn=lala,ou=people,dc=example,dc=com
    objectClass: inetOrgPerson
    uid: lala
    sn: La
    givenName: La
    cn: lala
    displayName: La La
    userPassword: 123456
    mail: lala@example.com
    
    dn: cn=lulu,ou=people,dc=example,dc=com
    objectClass: inetOrgPerson
    uid: lulu
    sn: Lu
    givenName: Lu
    cn: lulu
    displayName: Lu Lu
    userPassword: 123456
    mail: lulu@example.com

    สร้างไฟล์ ./src/create-students.ldif

    $ vi ./src/create-students.ldif 
    # Create 2 students in tree OU=students,OU=groups
    dn: cn=5310110293,ou=students,ou=groups,dc=example,dc=com
    objectClass: inetOrgPerson
    uid: 5310110293
    sn: 5310110293
    givenName: somsak
    cn: 5310110293
    displayName: somsak somsaknaja
    userPassword: 123456
    mail: 5310110293@example.com
    
    dn: cn=5410110308,ou=students,ou=groups,dc=example,dc=com
    objectClass: inetOrgPerson
    uid: 5410110308
    sn: 5410110308
    givenName: somsri
    cn: 5410110308
    displayName: somsri somsrisiya
    userPassword: 123456
    mail: 5410110308@example.com

    ต่อไปเป็นการเตรียม self-signed certificate เพื่อให้ใช้งาน LDAPS ได้

    $ sudo apt install ssl-cert
    $ mkdir cert
    $ cp /etc/ssl/certs/ssl-cert-snakeoil.pem cert/ldap-ca-cert.pem
    $ sudo cp /etc/ssl/private/ssl-cert-snakeoil.key cert/ldap-ca-key.pem
    $ sudo chmod +r cert/ldap-ca-key.pem

     

    2.ขั้นตอนสร้างไฟล์ dockerfile
    ตอนนี้ก็มาถึงขั้นตอนการเขียน dockerfile ด้วยเอดิเตอร์ที่ถนัดเช่น vi หรือ nano ก็ได้

    $ vi dockerfile
    # Composer: Comments
    
    FROM ubuntu:16.04
    
    # Change apt source
    RUN sed -i 's/\/us.archive/\/th.archive/g' /etc/apt/sources.list && \
     sed -i 's/\/archive/\/th.archive/g' /etc/apt/sources.list
    
    # Update ubuntu, then install packages
    RUN apt-get update && \
    echo 'slapd slapd/root_password password 123456' | debconf-set-selections && \
    echo 'slapd slapd/root_password_again password 123456' | debconf-set-selections && \
    echo "slapd slapd/internal/adminpw password 123456" |debconf-set-selections && \
    echo "slapd slapd/internal/generated_adminpw password 123456" |debconf-set-selections && \
    echo "slapd slapd/password2 password 123456" |debconf-set-selections && \
    echo "slapd slapd/password1 password 123456" |debconf-set-selections && \
    echo "slapd slapd/domain string example.com" |debconf-set-selections && \
    echo "slapd shared/organization string example" |debconf-set-selections && \
    echo "slapd slapd/backend string HDB" |debconf-set-selections && \
    echo "slapd slapd/purge_database boolean true" |debconf-set-selections && \
    echo "slapd slapd/move_old_database boolean true" |debconf-set-selections && \
    echo "slapd slapd/allow_ldap_v2 boolean false" |debconf-set-selections && \
    echo "slapd slapd/no_configuration boolean false" |debconf-set-selections && \
    DEBIAN_FRONTEND=noninteractive apt install -y slapd
    
    # LDAP utils, self-signed certificates
    RUN apt-get install -y ldap-utils ssl-cert
    
    # Timezone Asia/Bangkok
    RUN apt-get install -y tzdata && \
     ln -sf /usr/share/zoneinfo/Asia/Bangkok /etc/localtime && \
     dpkg-reconfigure -f noninteractive tzdata
    
    # Cleaning
    RUN apt-get autoremove -y && apt-get clean -y
    
    # LDAP
    COPY ./src/*.ldif /tmp/
    ## Schema only, no user. 
    RUN service slapd start && \
     ldapadd -H ldapi:/// -f /tmp/create-schema.ldif -x -D "cn=admin,dc=example,dc=com" -w 123456
    ## Schema and add users.
    ## RUN service slapd start && \ 
    ## ldapadd -H ldapi:/// -f /tmp/create-schema.ldif -x -D "cn=admin,dc=example,dc=com" -w 123456 && \
    ## ldapadd -H ldapi:/// -f /tmp/create-users.ldif -x -D "cn=admin,dc=example,dc=com" -w 123456 && \
    ## ldapadd -H ldapi:/// -f /tmp/create-students.ldif -x -D "cn=admin,dc=example,dc=com" -w 123456
    
    EXPOSE 389 
    #CMD slapd -h 'ldap:///' -g openldap -u openldap -d 0
    
    #
    # LDAPS
    COPY ./cert/ldap-ca-cert.pem /etc/ssl/certs/
    COPY ./cert/ldap-ca-key.pem /etc/ssl/private/
    
    RUN chgrp ssl-cert /etc/ssl/private/ldap-ca-key.pem && \
     chmod g+r /etc/ssl/private/ldap-ca-key.pem && \
     adduser openldap ssl-cert
    
    RUN echo 'SLAPD_SERVICES="ldap:/// ldapi:/// ldaps:///"' >> /etc/default/slapd && \
     echo 'TLS_REQCERT never' >> /etc/ldap/ldap.conf
    
    RUN service slapd start && \
     ldapmodify -Y EXTERNAL -H ldapi:/// -f /tmp/ssl.ldif -v
    
    EXPOSE 636
    ##CMD slapd -h 'ldaps:///' -g openldap -u openldap -d 0
    
    CMD RUN ulimit -n 1024
    CMD /usr/sbin/slapd -h 'ldap:/// ldaps:/// ldapi:///' -g openldap -u openldap -d 0

     

    3.ขั้นตอนการรัน container
    สร้าง image ด้วยคำสั่งนี้

    $ docker build -t openldap .

    รัน container และ ตรวจสอบ ด้วยคำสั่งนี้

    $ docker run -d -p 636:636 -p 389:389 --name openldap openldap
    $ docker ps

    อธิบายได้ดังนี้ container จะรันแบบ detach (-d) เปิด port 636 และ 389 ตั้งชื่อ(–name) ว่า openldap และรันจาก image ชื่อ openldap

    ทดสอบการนำเข้าข้อมูล ซึ่งเราจะใช้เครื่องที่กำลังทำอยู่นี้เป็น ldap client ไม่ต้องไปหาอีกเครื่องก็ได้ โดยเพิ่มแพ็กเกจ ldap-utils

    $ sudo apt install ldap-utils

    ทดสอบการเพิ่มตัวอย่างข้อมูล โดยเลือกใช้ port 389 (LDAP)

    $ ldapadd -H ldap://ldap.example.com -f ./src/create-users.ldif -x -D "cn=admin,dc=example,dc=com" -w 123456

    ทดสอบการเพิ่มตัวอย่างข้อมูล โดยเลือกใช้ port 636 (LDAPS)

    $ ldapadd -H ldaps://ldap.example.com -f ./src/create-students.ldif -x -D "cn=admin,dc=example,dc=com" -w 123456

    จะพบข้อความว่า

    ldap_sasl_bind(SIMPLE): Can’t contact LDAP server (-1)

    ให้ทำคำสั่ง 2 บรรทัดข้างล่างนี้ เพื่อเพิ่ม certificate จาก LDAP server แล้วลองคำสั่งนั้นอีกครั้งจะทำได้

    $ openssl s_client -connect ldap.example.com:636 -showcerts </dev/null 2>/dev/null | openssl x509 -outform PEM | sudo tee /usr/local/share/ca-certificates/ldap.example.com.crt
    $ sudo update-ca-certificates

    ทดสอบค้นหาข้อมูล

    $ ldapsearch -xLLL -b "dc=example,dc=com" uid=lulu
    $ ldapsearch -xLLL -b "dc=example,dc=com" uid=5310110293

    ลบ container

    $ docker stop openldap
    $ docker rm openldap

     

    เรื่องสุดท้ายของบล็อกในวันนี้คือ ต้องการเก็บตัวอย่างข้อมูลที่เพิ่มไว้จะทำอย่างไร ทำได้โดยการสร้างที่เก็บ persistent data แบบ named volume โดยที่ oldata สำหรับข้อมูล และ olconfig สำหรับไฟล์คอนฟิกกูเรชัน

    $ docker volume create oldata
    oldata
    $ docker volume create olconfig
    olconfig

    เช็คดูรายการ volume

    $ docker volume ls
    DRIVER VOLUME NAME
    local olconfig
    local oldata

    รัน container (เขียนคำสั่งให้อยู่ในบรรทัดเดียว)

    $ docker run -d -p 636:636 -p 389:389 --name openldap --volume oldata:/var/lib/ldap --volume olconfig:/etc/ldap/slapd.d openldap

    อธิบายได้ดังนี้ container จะรันแบบ detach (-d) เปิด port 636 และ 389 ตั้งชื่อ(–name) ว่า openldap เก็บข้อมูลไว้นอก container เอาไว้ที่ volume ชื่อ oldata ที่ mapped ไปยัง /var/lib/ldap ใน container กับ olconfig ที่ mapped ไปยัง /etc/ldap/slapd.d ใน container และรันจาก image ชื่อ openldap

    เพิ่มข้อมูลตัวอย่าง

    $ ldapadd -H ldaps://ldap.example.com -f ./src/create-students.ldif -x -D "cn=admin,dc=example,dc=com" -w 123456

    ค้นหาข้อมูล

    $ ldapsearch -xLLL -b "dc=example,dc=com" uid=5310110293

    ลบ container เมื่อจะไม่ใช้งานแล้ว

    $ docker stop openldap
    $ docker rm openldap

    หลังจากลบ container ข้อมูลของเราจะยังคงอยู่ในที่เก็บข้อมูลแบบ volume อยู่ที่ path /var/lib/docker/volumes

    ในตอนต่อไปจะเป็นการสร้าง dockerfile เพื่อรัน phpldapadmin ใช้ในการเข้า admin ผ่านหน้าเว็บเพจ

  • เตาะแตะไปกับ Docker ตอนที่ 8 Cleanup Disk Space

    การเรียนรู้ docker เราก็จะมีการทดสอบ pull image มา แล้ว run เป็น container รวมทั้งอาจมีการสร้างพื้นที่เก็บข้อมูลที่เรียกว่า volumes (ทั้งแบบ named volume และ anonymous volume) บ่อยครั้งเมื่อเราใช้คำสั่งตรวจสอบ เราจะพบว่ามีอะไรไม่รู้หลงเหลืออยู่กินเนื้อที่ไปเยอะ

    ตรวจสอบรายการ container

    $ docker ps -a

    ลบ containers ที่ไม่ใช้งานแล้ว

    $ docker ps --filter status=dead --filter status=exited --filter status=created -aq | xargs -r docker rm -v

    หมายเหตุ คำสั่งด้านบนนี้จะลบ data containers ด้วย ถ้ามีการสร้าง container ชนิดเก็บ data โปรดตรวจสอบให้ดีนะ ปัจจุบัน data container นั้น deprecated (ไม่แนะนำให้ใช้งาน)

    ตรวจสอบรายการ images

    $ docker images

    ลบ images ที่ไม่ใช้งานแล้ว

    $ docker images --no-trunc | grep '<none>' | awk '{ print $3 }' | xargs -r docker rmi

    ตรวจสอบรายการ volumes

    $ docker volumes ls

    ลบ volume ที่ไม่ถูกใช้งานโดย container ใด ๆ เลย

    $ docker volume ls -q -f dangling=true | xargs -r docker volume rm

    หรือจะใช้อีกแบบ แต่ต้องติดตั้ง jq เพิ่มด้วย

    $ sudo apt install jq
    $ docker ps -aq | xargs docker inspect | jq -r '.[] | .Mounts | .[] | .Name | select(.)'

     

    References: