EP3 Docker ทีเล่น ทีจริง

Supphachoke Suntiwichaya
NECTEC
Published in
6 min readSep 12, 2017

EP3 จะนำเที่ยวภายใน Container ในแบบต่างๆ กันเผื่อเป็นแนวทางสำหรับ Dev มือใหม่ อย่าลืมนะว่าแนวคิดการใช้งาน Docker คือ Microservice ในแต่ละ Container ควรมีแค่ Service เดียวหรือมีให้น้อยที่สุด แต่ระหว่าง Dev บางครั้งเราต้องการทดลองโน้นทดลองนี้ มีความจำเป็นจะต้องรู้วิธีการเข้าถึง Container ในระดับหนึ่งนะครับ จาก EP2 ผมทิ้งท้ายไว้กับการใช้ nginx ไว้ครั้งนี้ก็มาทำกันต่อ

ยังจำกันได้นะครับวิธี run nginx แบบง่ายๆ คือ

$ docker run -d --name web -p 8000:80 nginx

ทีนี้ก็จะมีคำถามเกิดขึ้นว่า แล้วเราจะเอาเว็บที่เขียนไว้เข้าไปได้ยังไงซึ่งมันมีอยู่สามสี่วิธีหรืออาจจะมากกว่านี้ เช่น

  • เข้าไป edit ใน container
  • copy เข้าไปไว้ใน container
  • mount volume เข้าไปใน container
  • Build Production Image

อย่าลืมอีกเรื่องคือ ก่อนที่เราจะใช้ image เราต้องรู้ก่อนว่าโครงสร้างมันเป็นยังไงซึ่งวิธีที่ดีที่สุดคือเข้าไปดูยังหน้าแรกของ image นั้นๆ ที่ hub.docker.com ถ้ามี Dockerfile ให้เราได้ดูก็จะยิ่งเข้าใจมากยิ่งขึ้น

เข้าไป edit ใน container

วิธีแรกเป็นวิธีที่ Dev หลายคนนิยมทำกันซึ่งแนะนำว่าควรใช้ในการ Dev เท่านั้น ควรหลีกเลี่ยงใน Production

หลังจากที่เราได้ทำการ run nginx ขึ้นมาแล้วก็ให้เราเข้าไปโดยใช้คำสั่ง

$ docker exec -it web bash

เมื่อเข้าไปสำเร็จก็จะได้ promt ของ container ประมาณนี้

root@4783c9d7cfe3:/#

ซึ่งประกอบไปด้วย username@container_id:PATH # ถ้าใครเข้าไปหลายๆ container แล้วกลัวสับสนตอน run เราอาจจะใส่ option hostname เข้าไปด้วย เวลาเข้าไปตรง container_id ก็จะเปลี่ยนเป็น hostname ที่เราตั้งไว้เช่น

$ docker rm -f web$ docker run -d -p 8000:80 --name web --hostname web nginx$ docker exec -it web bashroot@web:/#

วิธีนี้จะช่วยลดความสับสนได้เป็นอย่างมากนะครับ

เมื่อเข้าไปข้างในได้แล้วเราสามารถที่จะทำอะไรได้ตามต้องการเหมือนเรากำลังเล่นอยู่บน linux ตัวหนึ่ง แต่อย่างที่บอกว่ามันถูกทำมาให้เล็กที่สุดเครื่องมือต่างๆ ก็จะมีน้อยเท่าที่จำเป็นเท่านั้น พวก editor ต่างๆ อาจจะไม่มีมาด้วย เวลาเรียก vi vim pico แล้วไม่เจอก็ไม่ต้องตกใจ เราสามารถติดตั้งเพิ่มเติมได้ ตรงนี้เราก็ต้องรู้ว่า image ที่เราใช้นั้นเป็น linux distro ไหนจะได้ใช้ package manager ได้ถูก

วิธีเช็คง่ายๆ คือ

root@web:/# cat /etc/issueDebian GNU/Linux 8 \n \l

เราก็รู้ว่า nginx ของเรา run ด้วย Debian นั่นเอง ทีนี้เราก็รู้แล้วว่าใช้ apt ในการจัดการสิ่งแรกเลยที่ติดตั้งคือ text editor อันนี้แล้วแต่ถนัดเลยครับจะ vim จะ nano ขอให้ใช้เป็นก็พอ

root@web:/# apt updateroot@web:/# apt install nano

เมื่อได้ editor คู่ใจแล้วคราวนี้ก็ตามหาว่า webroot อยู่ที่ไหน วิธีง่ายสุดคือดูที่หน้า hub หรือ ดูจาก nginx config file ก็ได้ผมคิดว่าเราเลือกใช้ nginx เราก็ควรจะ config มันเป็นระดับหนึ่งใช่มะ อะตามไปดูกันเลย

โดยปกติ nginx จะเก็บ config ไว้ที่ /etc/nginx และ ตัว site default มักจะเก็บไว้ดังนี้

root@web:/# cd /etc/nginxroot@web:/etc/nginx# ls -ltotal 36
drwxr-xr-x 2 root root 4096 Sep 24 2016 conf.d
-rw-r--r-- 1 root root 1007 Sep 13 2016 fastcgi_params
-rw-r--r-- 1 root root 2837 Sep 13 2016 koi-utf
-rw-r--r-- 1 root root 2223 Sep 13 2016 koi-win
-rw-r--r-- 1 root root 3957 Sep 13 2016 mime.types
lrwxrwxrwx 1 root root 22 Sep 13 2016 modules -> /usr/lib/nginx/modules
-rw-r--r-- 1 root root 643 Sep 13 2016 nginx.conf
-rw-r--r-- 1 root root 636 Sep 13 2016 scgi_params
-rw-r--r-- 1 root root 664 Sep 13 2016 uwsgi_params
-rw-r--r-- 1 root root 3610 Sep 13 2016 win-utf

ตรงนี้ก็ใช้ประสบการณ์เดาเอาได้ละว่ามันควรอยู่ที่ไหน ครับ น่าจะ conf.d ใช่ไหมครับ ลองเข้าไปดู

root@web:/etc/nginx# cd conf.droot@web:/etc/nginx/conf.d# ls -l
total 4
-rw-r--r-- 1 root root 1097 Sep 13 2016 default.conf

เมื่อเข้าไปดูใน file default.conf เราก็จะเห็นว่า web root อยู่ที่ไหน

root@web:/etc/nginx/conf.d# pico -w default.conf

nginx office image ก็จะเก็บ web root ไว้ที่ /usr/share/nginx/html

location / {
root /usr/share/nginx/html;
index index.html index.htm;
}

ลองเข้าไปแก้ไข /usr/share/nginx/html/index.html แล้วดูผลบนหน้าเว็บดูครับว่าถูกต้องไหม ซึ่งมันก็ควรจะถูกต้อง

หลายคนสงสัยว่าถ้าเราอยากเปลี่ยน webroot เราทำได้ไหม ? path มันจำยากปกติคุ้นเคยกับ /var/www หรือ /var/html/www ในเมื่อสงสัยก็ลองเปลี่ยนดูครับ

แก้ไข้ใน default.conf ได้เลยครับเปลี่ยนเป็น

location / {
root /var/www;
index index.html index.htm;
}

แล้วเราจะเห็นผลได้ยังไง ? ก็เหมือนเรากำลังใช้ Debian อยู่นะแหละครับขั้นแรกเรา test config ก่อน เมื่อ ok ก็ทำการ reload nginx ใหม่

root@web:/etc/nginx/conf.d# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
root@web:/etc/nginx/conf.d# nginx -s reload
2017/09/13 03:34:03 [notice] 80#80: signal process started

ลองเรียกเว็บดูครับ

พบว่าหน้าเว็บมันหายไปแล้วคราวนี้เราก็เข้าไปสร้าง webroot ให้มันซะ

root@web:/etc/nginx/conf.d# cd /varroot@web:/var# mkdir www

เรียกหน้าเว็บดูอีกที

คราวนี้ error เปลี่ยนไปครับกลายเป็น 403 Forbidden แทน ลองสร้าง index.html ให้มันดูครับ

root@web:/var# cd wwwroot@web:/var/www# echo Hello Nginx > index.html

เรียกหน้าเว็บดูอีกทีครับ

คราวนี้หน้าเว็บมาเรียกร้อย เราก็สามารถเขียนเว็บเก็บไว้ที่ /var/www ได้แล้ว

วิธีนี้เมื่อเราลบ container ทิ้งข้อมูลก็จะสูญสลายไปพร้อมกับ container ด้วยสิ่งที่ทำมาก็หายเกลี้ยงครับ

copy เข้าไปไว้ใน container

วิธีที่สองที่จะแนะนำคือการ copy web ที่เราเขียนขึ้นมาเข้าไป container แทนการเข้าไปแก้ไขโดยตรงวิธีนี้การันตีว่า เมื่อเราลบ container ทิ้งไปแล้ว web ที่เราเขียนไว้ก็ยังคงอยู่

โจทย์ยังคงเหมือนเดิมครับคือ แก้ไข config แล้วสร้างเว็บที่ /var/www

ให้เรา run nginx ขึ้นมาอีก container ที่ port 8001

$ docker run -d -p 8001:80 --name web2 --hostname web2 nginx

แต่คราวนี้ให้เรา copy config file ออกมาจาก container ก่อน ซึ่งเรารู้แล้วว่า file อยู่ที่ไหน

$ docker cp web2:/etc/nginx/conf.d/default.conf .

จากคำสั่งด้านบนเราสั่งจากเครื่อง host หรือ เครื่องเรานะครับ โดยใช้คำสั่ง docker cp หรือคำสั่ง copy โดยระบุว่าให้ copy /etc/nginx/conf.d/default.conf จาก container ชื่อ web2 มาเก็บไว้ที่ directory ปัจจุบัน คือ . นั่นเอง

$ ls -l
total 8
-rw-r--r-- 1 mrchoke admin 1097 Sep 13 2016 default.conf

ก็ให้ทำการแก้ไข file default.conf เปลี่ยน webroot เป็น /var/www ซึ่งคราวนี้สามารถใช้เครื่องมือบนเครื่องได้ตามชอบ จะเป็น edit+ VS Code หรือ อะไรก็ได้ เมื่อแก้ไขเสร็จก็ให้ยัดกลับเข้าไปครับ

$ docker cp  default.conf  web2:/etc/nginx/conf.d/default.conf

เมื่อได้แล้วก็ให้ตรวจสอบความถูกต้องของ config เหมือนเดิม

$ docker exec web2 nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

ถ้าไม่มีอะไรผิดพลาดก็ให้ทำการ reload nginx ใหม่

$ docker exec web2 nginx -s reload
2017/09/12 20:59:27 [notice] 11#11: signal process started

เมื่อเรียกหน้าเว็บก็จะขึ้น 404 เหมือนกับวิธีแรก ให้เราเขียนเว็บที่ฝั่งเครื่อง host ของเราเก็บไว้ใน directory www

$ mkdir www$ echo Hello Web2 >www/index.html

เมื่อได้เว็บที่ต้องการแล้วก็ให้ copy เข้าไปยัง container ครับ

$ docker cp  www  web2:/var/

เมื่อ copy เสร็จแล้วก็เรียกหน้าเว็บทดสอบดูครับ

วิธีนี้ถึงแม้ข้อมูลจะไม่หายตอนลบ container แต่ก็คงไม่ค่อยสะดวกนักที่ต้องคอย copy ข้อมูลเข้าไปทุกครั้งที่แก้ไขข้อมูล

mount volume เข้าไปใน container

มาถึงวิธีที่ค่อนข้างสะดวกที่สุด และ dev นิยมใช้กันมาก นั้นคือการ mount volume จากเครื่อง host เข้าไปยัง container โดยใช้ option -v จากตัวอย่างที่สอง เรามี file default.conf และ directory www อยู่แล้วเราก็เอาสองอันนี้ mount เข้าไปยัง container ใหม่ที่เราจะสร้างขึ้นมาดังนี้

$ docker run -d -p 8002:80 --name web3 --hostname web3 -v `pwd`/default.conf:/etc/nginx/conf.d/default.conf:ro -v `pwd`/www:/var/www:ro nginx

การใช้ -v แนะนำว่าให้ใช้ full path นะครับในตัวอย่างผมใช้ `pwd ` ใช้ backquote คร่อมอยู่คือคำสั่ง path ปัจจุบันนั่นเองอันนี้บน mac กับ linux จะใช้ได้ แต่บน windows ไม่แน่ใจครับ แต่เพื่อความไม่สับสนก็ให้ใส่ path ยาวไปเลยเช่น

-v /home/mrchoke/docker/abc.conf:/etc/abc.conf-v c:/data:/data --> windows

สังเกตว่าผมมี :ro ปิดท้ายไว้ด้วย ซึ่งหมายความว่า mount เข้าไปแบบ read only นั่นเองซึ่งถ้าเราเข้าไปใน container เราจะแก้ไขไม่ได้ เช่น

$ docker exec web3 bash -c "echo web3 > /var/www/index.html"
bash: /var/www/index.html: Read-only file system

การใช้แบบ or หรือ read only ก็จะช่วยป้องกันในกรณีถ้าเรานำไปใช้ใน production หากมีการโจมตีแล้วเข้ามายังระบบก็จะช่วยได้ระดับหนึ่งที่ทำให้ผู้บุกรุกไม่สามารถเขียน config เราได้ แต่ถ้าโดนเข้ามาขนาดนี้แล้วก็นะ ฮาๆ

ถ้าเราไม่ได้ระบุ :ro ค่าจะเป็น :rw อัตโนมัตินะครับ

ตอนนี้ถ้าเราเรียกหน้าเว็บก็จะเห็นข้อมูลเก่าคือ

ลองแก้ไข index.html ดูครับ

$ echo Hello Web3 >www/index.html

Build Production Image

แนะนำมาสามวิธีแล้วพอจะมี idea ต่อยอดบ้างหรือยังครับ วิธีสุดท้ายจะยกระดับความมันขึ้นมาเล็กน้อย แต่ก็ยังเบาะๆ นะครับไม่ลงลึกมากแค่พอเป็นกระษัย วิธีนี้จะแนะนำการสร้าง image เพื่อ deploy เป็น production เบื้องต้นให้นะครับซึ่งไม่ได้ยากเย็นอะไรมากมายเริ่มกันเลย

จากตัวอย่างก่อนหน้านี้เรามี nginx config ที่เราแก้ไขไว้แล้ว เรามีเว็บที่เขียนไว้แล้วคราวนี้เราก็จะเอาทั้งหมดนี้ยัดเข้าไปเป็น image ของเรา แล้วนำ image นี้ไป run เป็น production ได้ทันที

แก้ไขเว็บหน่อยหนึ่งครับ

$ echo Hello Production >www/index.html

ขั้นแรกเราต้องรู้ก่อนนะว่า image ต้นแบบเราชื่ออะไร tag อะไร เมื่อรู้แล้วก็สร้าง file ชื่อว่า Dockerfile ขึ้นมาครับ

$ pico -w Dockerfile

แล้วก็ร่ายมนต์ด้วยคำสั่งดังนี้

FROM nginx:latestADD default.conf /etc/nginx/conf.d/default.confADD www /var/www

จาก Dockerfile ด้านบนอธิบายได้ว่า

FROM nginx:latest เป็นการอ้างอิงว่าเราจะใช้ image ต้นแบบชื่ออะไร รุ่นอะไร ในที่นี้คือ nginx รุ่นล่าสุด

ADD คือการนำ file ที่อยู่ในระดับเดียวกับ Dockerfile บนเครื่องเราเข้าไปยัง image โดยระบุต้นทางปลายทาง เหมือนคำสั่ง copy ทั่วไปแต่ไม่ต้องใส่ -r -a สำหรับ directory นะครับ

เสร็จแล้วทำการสั่ง build image

$ docker build -t mrchoke/weblnw:1.00 .Sending build context to Docker daemon  5.632kB
Step 1/3 : FROM nginx:latest
---> ba6bed934df2
Step 2/3 : ADD default.conf /etc/nginx/conf.d/default.conf
---> 877fe1b032b5
Removing intermediate container 7aca0dbba46d
Step 3/3 : ADD www /var/www
---> 781ecf1a7771
Removing intermediate container 31996532e7a7
Successfully built 781ecf1a7771
Successfully tagged mrchoke/weblnw:1.00

การสร้าง image ง่ายๆ โดยสั่ง docker build แล้วระบุ tag หลัง -t แล้วใส่ . เพื่อบอกว่า Dockerfile อยู่ที่ตรงนี้นะ

ถ้าไม่มีอะไรผิดพลาดก็จะได้ตามตัวอย่างลอง check ดูว่ามี image ที่เราสร้างหรือยัง

$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
mrchoke/weblnw 1.00 781ecf1a7771 4 minutes ago 181MB

ถ้าเห็น image ใน list ก็แสดงว่าเราสามารถใช้งานได้แล้วครับลอง run กันเลย

$ docker run -d -p 8003:80 --name production mrchoke/weblnw:1.00

ลองเรียกเว็บดูครับ

ว้าว!! การสร้าง docker image เองมันช่างง่ายจริงๆ นะครับ ใครบอกว่ายากนี่โดนหลอกละ

เสร็จศึกก็จัดการโละที่ run มาทั้งหมดครับ

$ docker rm -f web web2 web3 production$ docker rmi mrchoke/weblnw:1.00
Untagged: mrchoke/weblnw:1.00
Deleted: sha256:6e55e969e487c0b058b8cb944012a8606694d384e1ca008a5ec49ccf9e1b59d0
Deleted: sha256:ac455f658a0a21d606dfec91598d36ffaab3e9ab0ef556695945c88439bce482

ร่ายยาวมาถึงสี่วิธีเอาไปทำมาหากินได้ระดับหนึ่งละ พอจะมองออกนะครับว่าวิธีไหนควรจะใช้เมื่อไหร่ และวิธีไหน ง่ายยากกว่ากัน ส่วน EP4 ก็คงหนีไม่พ้นเรื่องการนำ docker container มากกว่าหนึ่งมาทำงานร่วมกัน คงแนะนำแบบชิลๆ ไม่ยากเย็นเช่นเคยครับแล้วพบกันใหม่เมื่อมีเวลาว่างหรือนอนไม่หลับ

--

--