Tag: dockerfile

  • เตาะแตะไปกับ 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 ผ่านหน้าเว็บเพจ

  • เปลี่ยน ubuntu sources.list ก่อนสร้าง image ด้วย dockerfile

    การใช้งาน docker นั้นเราสามารถใช้ image จาก docker hub หรือเราจะสร้าง image ของเราเอง ซึ่งมีหลายวิธีในการสร้าง image แบบของเราเอง (custom) วิธีหนึ่งคือการใช้ dockerfile อย่างคร่าว ๆ คือ

    mkdir ~/mydocker
    cd ~/mydocker
    touch dockerfile
    docker built -t test_app:20170713 .
    docker images

    ในไฟล์ชื่อ dockerfile นี้จะมีไวยกรณ์ประมาณนี้

    # Image tag: test_app:20170713 <– บรรทัดนี้คือ comment
    FROM ubuntu:16.04 <– บรรทัดนี้คือ ไปเอา image ชื่อ ubuntu:16.04 จาก docker hub
    RUN apt-get update <– บรรทัดนี้คือ คำสั่งบอกว่าจะติดตั้ง หลังคำว่า RUN นั่นเอง
    RUN apt-get dist-upgrade -y
    RUN apt-get install -y apache2 libapache2-mod-php7.0 php7.0
    COPY …
    ADD …
    EXPOSE …
    CMD …
    และยังมี command อื่น ๆ อีก

    ทีนี้จากการที่ต้องลองผิดลองถูกบ่อย ๆ จึงพบว่า หากเราเพิ่มคำสั่ง 2 บรรทัดนี้เข้าไปก่อนบรรทัด RUN apt-get update ก็จะทำให้เราได้ใช้ ubuntu repository ที่ต้องการแทนค่า default ที่ archive.ubuntu.com เช่นต้องการให้มาใช้ th.archive.ubuntu.com ก็เขียนคำสั่งดังนี้

    RUN sed -i 's/\/us.archive/\/th.archive/g' /etc/apt/sources.list
    RUN sed -i 's/\/archive/\/th.archive/g' /etc/apt/sources.list

    ผลลัพธ์คือ หลังจากทำคำสั่ง docker built -t test_app:20170713 . จะเห็นว่าในขั้นตอนการ build นั้นจะดาวน์โหลดไฟล์ได้รวดเร็วกว่าเดิมมาก

    จึงนำความรู้มาบอกกันครับ อ้อลืมบอกว่าบทความที่เขียนนี้ ผมทดสอบกับ docker version 17.06.0-ce ครับ

    อยากแนะนำความรู้เกี่ยวกับ docker ที่อ่านมา พบว่าน่าสนใจ ลองอ่านดูครับ อ่านง่าย

     

    บทความในต่างประเทศ

    1. How To Install and Use Docker on Ubuntu 16.04 (https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-16-04)
    2. How to Build an Image with the Dockerfile (https://www.sitepoint.com/how-to-build-an-image-with-the-dockerfile/)
    3. Dockerfile reference (https://docs.docker.com/engine/reference/builder/)
    4. Best practices for writing Dockerfiles (https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/)
    5. How to update Docker image to maintain your containers secure  (https://bobcares.com/blog/update-docker-image/2/)
    6. How to upgrade docker container after its image changed
      (https://stackoverflow.com/questions/26734402/how-to-upgrade-docker-container-after-its-image-changed)
    7. Manage data in containers (https://docs.docker.com/engine/tutorials/dockervolumes/)