qa-how-to-use-docker-in-action
qa-how-to-use-docker-in-action

ของเล่นชิ้นใหม่ที่ทําให้ QA ตื่นเต้นสุดๆในยุคนี้ก็คงหนีไม่พ้นเจ้า Docker นี้แหละ….

กรุณาเลยไม่ว่าจะเป็น Tester, QA หรือ QA automation ก็ตามที กรุณาอย่าได้มองข้ามสิ่งนี้ไปน่ะ เจ้านี้คือเทคโนโลยีแห่งอนาคตเลยล่ะ 🙂

ลองจินตนาการถึงสมัยก่อน ก่อนที่จะมี Docker น่ะ  สมมุติด้วยว่าเรากําลังพัฒนาเว็ปนึงที่ใช้ php4 อยู่ แล้วอยู่ดีๆวันนึง Boss ใหญ่สั่งให้เรานําเทรนดเปลี่ยนเป็นเทคโนโลยี php5 แต่เงื่อนไขคือ ต้อง support php4 ด้วยน่ะ ไม่ break compatibility … โอเค แล้วเราจะทํายังไงล่ะ? Apache server ไม่ได้ Allow ที่จะให้ Switch ไปมาอย่างงั้น สมัยก่อนก็คงไม่พ้น

  1. ลง Virtual Box ที่แสนดจะกินแรม และ ลําบากในการใช้งานมากๆ แล้วก็เอา php5 ไปใส่ในนั้น แล้วทดสอบกับพัฒนาระบบบนนั้นกัน
  2. บริษัทไหนรวยหน่อย ซื้อเครื่องเลยจร้าาา เคยเห็นอยู่ เอาเครื่องใหม่เพื่อทดสอบระบบนี้เพียงอย่างเดียวเลย

ฟังดูแล้วก็เจ็บปวดน่ะ แถมยุ่งยาก …. แต่ไม่ใช่สมัยนี้ ! เราจะไม่ทน Work Hard มันต้อง Work Smart อย่าเสียเวลากับอะไรให้มันมากไปเลย สมัยนี้เราเลยใช้ Docker !!! สร้าง Container ที่มี Images Apache Server PHP4 กับ PHP5 ไว้อย่างล่ะตัว แล้วก็ทํางานบนนั้นแทนเลย 🙂 ถ้าไม่รู้จักศัพท์พวกนี้อาจจะอ่าน Docker 101 ก่อนก็ได้น่ะ เพราะบทความนี้จะลงรยละเอียดในการใช้งานแทน

ทีนี้ถ้าพอรู้จัก Docker คร่าวๆจาก Docker 101 แล้ว บทความที่จะเขียนต่อไปนี้จะแบ่งเป็นสองส่วนคือ

  1. Basic Docker
    1. Docker Overview
    2. Network
    3. Persisting Data
    4. Monitoring
    5. Build Own Image
  2. Advance Docker
    1. Docker Compose
    2. Docker Services

ใบบทความนี้ก็เอาส่วนแรกก่อนล่ะกัน ไม่งั้นเดี๋ยวจะเยอะไปหมดเลย

Basic Docker

Docker Overview

Container Vs Image

ก่อนจะเริ่มไปไกลเลยน่ะ เราพูดอยู่เรื่อยๆว่าดึงเอา Images ลงมา สร้าง Container แล้วจริงๆ Container แตกต่างกับ Images ยังไง?

ถ้าอธิบายตามสายโปรแกรมมิ่ง OOP น่ะ

Images = Class

Container = Instance of that class

Image Layer = Inheritance

คือ Images น่ะสร้างมาจากหลายๆ Layer ทับซ้อนกันน่ะ (ซึ่งไอ้การทับซ้อนนั้นอะมาจากที่เราเขียนใน DockerFile แบบพวก pull base image หรือให้มันโหลดข้อมูลแบบนั้น) ทีนี้พอได้ Image แล้วการที่เราจะรันให้มันทํางานได้ เราก็จะสร้าง Container ที่มาจาก Image นั้นๆนั่นเอง 🙂 เปรียบเสมือน Class Honda ที่มีไฟหน้ารถ เบาะ เบรค เขียนไว้แล้วก็ new Honda() ออกมานั้นแหละ! อ่านทฤษฐีได้ที่นี้เลย

ทีนี้น่ะเวลาเราใช้คําสั่ง

  • docker images มันก็จะหมายถึงการโชว์ template/class ออกมาว่าเรามีอะไรบ้าง (สังเกตุได้ว่ามันจะมีแค่ create date ไม่มีแบบ running เหมือนไรเหมือนเวลาเรากด docker ps เพราะมันเป็น class ไม่ได้ไว้รัน)
  • แต่ถ้าจะดูว่ามีอะไร running อยู่เราก็ต้องใช้ docker ps เพื่อดู instance ของ class หรือของ container นั้นเอง (จะมี running time เพราะเราสามารถดูได้ว่า instance ที่เราสร้างขึ้นมานานแค่ไหนล่ะ)

Docker Hub & Layer

มาเริ่มจากอะไรง่ายๆๆอย่างมารู้จัก Docker Hub กับ Layer ดีกว่า ว่าจริงๆแล้ว Docker Hub ไม่ได้บอกแค่ว่ามี Image อะไรให้ Download บ้างน่ะ มันยังบอกรายละเอียดด้วยว่า Image นี้สร้างมายังไง มี History ของมันเอง

qa-how-to-use-docker-hub
qa-how-to-use-docker-hub (https://hub.docker.com)

หน้านี้เป็นแหล่งรวม Images ทั้งหลายแหล่ที่เราทําการ pull มานั้นเอง ซึ่งถ้าเป็น Images ที่เป็น Official ก็จะเห็นว่ามันจะไม่มีชื่อของ repository นําหน้า จะเป็นชื่อเดียวๆอย่าง ngixn เป็นต้นนั้นเอง

ทีนี้รู้มั้ยว่าเวลามันกลายมาเป็น Images ได้เนี่ยมันจะมีสิ่งนึงเรียกว่า Layer ! เจ้า Layer นี้มองได้อีกแบบคือ Steps ที่ทําให้เกิดมาเป็น Images ที่เราต้องการ ซึ่งสามารถดูได้ใน Hub เนี่ยแหละ (ถ้าอยากดูจาก cli ดูได้จาก docker images history น่ะ)

docker-layer-history-qa
docker-layer-history-qa (https://hub.docker.com/r/library/nginx/tags/latest/)

จะเห็นได้เลยน่ะว่าแต่ละ Layer มันทําอะไรบ้าง 🙂 ซึ่งจริงๆแล้ว Layer พวกนี้มันก็คือ “Dockerfile” นั้นเอง

dockerfile-layer-steps
dockerfile-layer-steps (https://github.com/nginxinc/docker-nginx/blob/master/mainline/stretch-perl/Dockerfile)

ทีนี้มันมีสิ่งที่น่าสนใจคือ Docker มีการทํา Image Cache ด้วยน่ะ ซึ่งมีประโยชน์มากเวลา Build Image เลย:)

docker-image-cache-layer
docker-image-cache-layer

โดยเวลาเราสร้าง Images น่ะ เราไม่ได้สร้างแบบรูปที่ 1 หรอกน่ะ เวลาสร้าง Image น่ะ Docker ไม่ได้ pull แล้วสร้างใหม่ทุกครั้ง

แต่เราจะสร้าง Images แบบรูปที่ 2 คือ ถ้ามี layer ที่เหมือนกันใน DockerFile เช่น Ubuntu version เดียวกันแบบในรูป … Docker จะฉลาดพอที่จะใช้ Image Cache น่ะ ไม่ได้ Pull หรือ Download ลงมาใหม่ 🙂 (รู้ไว้ก็ดี เพื่อเวลาออกแบบระบบ CI,CD จะได้ Performance เป็นหลัก)

Docker Tag

จริงๆแล้ว Docker มันไม่มีชื่อให้เรียกหรอกน่ะ สิ่งที่ระบุแล้วชี้ไปที่ Image จริงๆก็มีแค่ Image Id ซึ่งค่ามันก็จะเป็น ase231fv3 เป็นต้น ซึ่งไม่มีใครจําหรอก! ปกติแล้วที่เราจะใช้กันก็คือ


<user>/<repo>:<tag>

ใช่มั้ยล่ะ? เวลาที่เราจะ pull อะไรลงมา เราก็จะไป docker pull nginx เป็นต้น (official account ไม่ต้องมี user นําหน้า) เพราะฉะนั้นสิ่งที่เราใช้เพื่อระบุความเป็น identity ของ Docker ก็คือ Tag นั้นเอง 🙂

Docker Tag ไม่ใช่ version หรือ branch น่ะ มันเป็นเหมือนกัน Git Tags มากกว่าอ่ะ คือมันจะระบุเจาะจงไปที่ commit นั้นๆมากกว่า … เพราะฉะนั้นถ้าเรา Pull Image เดียวกัน แต่คนละ Tag ลงมา จะสังเกตุได้เลยว่า มันได้ Docker Image Id เดียวกัน แต่คนละ Tags

image-same-id-diff-tag
image-same-id-diff-tag

ที่นี้ถ้าเราจะติด Tag ของเราเองบ้าง ก็จะใช้คําสั่งข้างล่างนี้


docker image tag source_repo:[tag] target_repo:[tag]

คําถาม …. รู้เรื่อง Tag ทําไม? เพราะว่าลองจิตนการถึง เวลาเราออกแบบ CI/CD ขึ้นมาน่ะ แล้วเราจะสร้างระบบให้มันทํางานบน Test Data ที่เราออกแบบไว้บน Docker ทีนี้ถ้าเราไม่รู้เรื่องติด Tag มันก็จะดึง Latest Tag image ออกมาเสมอ ซึ่งหมายความว่าถ้าทีมเราเขียนอะไรใหม่ แล้วแก้บางอย่างที่ทําให้ Break compatibility ไป รับรองได้เลยว่าพังยาว …. ดังนั้นใช้ Tag เพื่อไปที่ Sanpshot ของช่วงเวลานั้นๆไว้จะดีที่สุด

Docker Network

จบเรื่องง่ายๆล่ะ มาดูของน่าสนใจกันดีกว่า Network in Docker! คือเราก็รู้อ่ะน่ะว่า Docker สร้าง Container ออกมาจาก Images ที่เราระบุไว้ ถ้ามี Container เดียวก็ไม่เป็นไรหรอก แต่ถ้าเรามี 2,3,4,etc ขึ้นไปล่ะ? มันจะคุยกันยังไงหรอ? Container แต่ละตัวจะรู้จักกันมั้ย? นี้แหละน่าสนใจว่า Docker จัดการ Network กันยังไง

เริ่มจากรู้ก่อนว่าเรามีคําสั่งๆหลักๆดังนี้

  1. docker network ls
  2. docker network inspect
  3. docker network create –driver
  4. docker network connect (ไว้จับ container แปะเข้าไป network นึง)
  5. docker network disconnect  (ไว้ disconnect network ของ docker)

ซึ่งถ้าเราลอง docker network ls

docker-network-ls
docker-network-ls

จะเห็นเลยว่า default ของ docker จะออกมายังงี้ ซึ่งจะมีสิ่งที่เรียกว่า bridge อยู่ 🙂 ซึ่งเจ้า bridge นี้มันก็คือ NAT firewall ที่ไปหา physical address ของ host นั้นเอง …. เช่ือว่ามีหลายคน งงๆ ซึ่ง งงจริงในตอนแรกถ้าไม่รู้ว่าคืออะไร NAT concept

** เกร็ดเรื่อง NAT แบบสรุป

ปัจจุบันหลายที่บนโลกนี้ยังใช้สิ่งที่เรียกว่า IPv4 อยู่ หรือ IP ต่างๆที่อยู่บนเครื่องเราเนี้ย มันมีน้อยกว่าจํานวนคนบนโลกนี้เยอะยิ่งมี มือถือ,ipad, notebook ออกมาในปัจจุบันยิ่งทําให้ IP ไม่พอสําหรับคนทั้งโลก

ทีนี้ถ้าเราอยู่ในบ้านเนี้ย การจะต่อเนตไป google ได้ …. ก็จะมีอุปกรณ์นึงเรียกว่า Routerใช่มั้ยล่ะ? ซึ่งด้วยหลักการณ์ที่ว่า IP ไม่พอสำหรับคนทั้งโลกเลยออกมาเป็น private ip address ที่เรียกว่า 192.168.X.X หรือ 10.X.X.X ถ้าใคร setup ระบบในบ้านจะพอนึกออกว่า IP พวกนี้ไว้เข้าเครื่อง router เรา

ซึ่งเจ้า IP พวกนี้น่ะ มันไม่สามารถให้ข้างนอกเรียกมาได้หรอกน่ะเพราะเป็น private สิ่งที่ Router ทําก็คือแปลง IP พวกนี้ไปเป็น IP จริงๆ ที่ไว้ใช้สําหรับวิ่งไปที่ต่างๆหรือ public IP address นั้นเอง!!! และไอ้สิ่งที่ convert พวกนี้น่ะ เค้าเรียกว่า NAT นั้นเอง -> Network Address Translation 🙂 นี้แหละ concept ของ NAT

กลับมาเรื่องของ docker แล้วทําไมต้องมี bridge มี NAT concept? เพราะว่า docker สร้าง virtual network ขึ้นมาให้เราใช้นั้นเอง และใช้หลักการเดียวกัน แยก host กับ virtual network นั้นๆ แล้วมี docker NAT ให้นั้นเองล่ะ…. ซึ่งเราสามารถสร้าง docker network create ขึ้นมาเพื่อให้เป็น network ของเราเองได้ ง่ายๆ เพื่อออกแบบระบบให้มันคุยกันเองได้

docker-network
docker-network

ทีนี้ก็จะได้ออกมาแบบตามรูปข้างล่างได้เลย

docker-network-custom
docker-network-custom
docker-network-verify-1
docker-network-verify-1
docker-network-verify-2
docker-network-verify-2

ถ้าลองสังเกตุดีๆจะเห็นว่า แต่ละ network ที่ inspect ดูก็จะเจอชื่อ Name ของ container แตกต่างกัน เพราะเราไป connect มันคนละตัวนี้นเอง 🙂 ทีนี้ก็จะได้ตามรูปบนล่ะ ว่าแยก network กันแบบนี้เลย

แล้วเราก็สามารถทดสอบได้ด้วยการเข้าไป ping container ได้เลยจะได้ตามรูปด้านล่าง

docker-network-testing-ping-nginx-alpine
docker-network-testing-ping-nginx-alpine

การที่เราจะทํา Network กับ Docker ขอให้จําไว้อย่างนึงเลยว่า …. อย่าได้ใช้ IP Address ในการเ reference ระหว่างกันเป็นอันขาด เพราะลองคิดดูน่ะ ถ้ามีการ stop ของ image ไป IP เปลี่ยนไป เรานี้ตายเลยอะ แนะนําให้ใช้ DNS เป็นหลัก! หรือก็คือตั้งค่า –net-alias ให้ network ด้วยตอนที่เรา attach เข้าไปแล้ว docker dns

Persisting Data

โดยปกติแล้ว Docker ถูกออกแบบมาให้เป็น Immutable infrastructure น่ะ คือหมายถึงเราจะไม่ทํางานหรืออยู่ดีๆไปอัพเกรดมันในขณะที่มันทํางานอยู่ได้ สิ่งที่เราจะทําก็คือ redeploy มันใหม่แค่นั้นเลย

ถ้างั้นแล้ว unique data ที่ app สร้างขึ้นมาล่ะ? ถ้าเรา re-deploy อ่ะ???? เพราะปกติแล้วมันจะเก็บข้อมูลไว้ในตัว app มันเองเลย จึงเกิดออกมาเป็น concept “seperation of concern” หรือก็คือเราจะแยก data ออกมาให้สามารถเข้าถึงได้ outside of container นั้นเอง โดยแบ่งออกเป็น 2 อย่าง

  1. Data Volume (outside container )
  2. Bind Mounts (เข้ากับ host machines directory)

โดยมีคําสั่งหลักดังนี้

  1. docker volume ls
  2. docker volume inspect [volume]
  3. docker volume prune เพื่อ clean unused data ที่ named ไว้
  4. docker run -v [name]:[image volume] (ด้วยวิธีนี้จะมี volume ชื่อของเราเอง และสามารถ reuse ได้ตลอดเวลาเมื่อมี container ใหม่มาใช้) ยกตัวอย่างก็ docker run -d –name mysql -v my_sql:/var/lib/mysql เป็นต้น คําสั่งนี้แปลว่า pull mysql ลงมาและรัน volume ไว้ชื่อด้วย my_sql

Data Volume

เจ้าตัว Volume เนี้ยมันก็เป็นคําสั่งนึงใน Dockerfile นั้นแหละ ไว้สร้างที่เก็บข้อมูลนั้นเอง 🙂 เช่น ของ MySQL DockerFile

docker-data-volumn
docker-data-volumn (https://hub.docker.com/r/library/mysql/tags/latest/)

 

ก็จะระบุชัดเลยว่ามันจะไปสร้าง Volume อยู่ที่ directory นี้น่ะ เวลาเรา delete docker แล้วยังไงเราต้องใช้ docker volume prune ไปเครียล์มันเอง

ทีนี้เราสามารถใส่ named volume ให้มันได้ เพื่อที่มันจะได้ระบุชื่อง่ายๆไม่ใช่เลข SHA


docker run -d --name mysql -v mysql-db:/var/lib/mysql

อย่างตัวนี้ก็จะเก็บข้อมูล mount ขึ้นมาชื่อ mysql-db เข้ากับ container ที่ระบุไว้ตรง /var/lib/mysql นั้นเอง

Bind Mounts

อันนี้เข้าใจง่ายเหมือน Maps drive นั้นแหละ คือเอา host directory map เข้ากับ continaer file แตกต่างกับ Data Volume โดยการเราไม่ได้ใส่ชื่อนะ เราใส่ Directory path ลงไปเลย เช่น

docker container run -d –name ngix:alpine -p 80:80 -v /Users/HowToAutomate:/usr/share/nginx/html ngix

เห็นม่ะ มันจะไป map ไปที่ directory ของ host หรือเครื่องเราเลย ต่างกันแค่นั้นแหละ ทีนี้แปลว่า Data ก็จะมาอยู่ใน folder นั้นๆได้เลย

รายละเอียดตัวเต็มอ่านที่นี้เลย แต่อาจจะเยอะไปนิดนึง

Monitoring

มาถึงตัวง่ายๆล่ะ อันนี้ไม่มีไรมากแค่อยากให้รู้จักคําสั่งที่ต้องใช้เวลาทํางานเท่านั้นเอง เพราะ Docker มันเป็น Container เนอะ การที่เราจะ Access เข้าไปได้เนี่ยมันก็ต้องมีวิธีเฉพาะของมัน ที่ไม่ใช่ SSH น่ะ 🙂 อันนั้นแปลกไปปปป

  1. docker container run -it  (ส่งคําสั่งไป interact กับ shell ใน contaienr นั้น โดยถ้ายังไม่มี image ก็จะ pull ลงมาและรันคําสั่ง)
  2. docker contaienr exec -it (ส่งคําสั่งไป shell ใน container นั้นกับ container ที่รันอยู่)
  3. docker contaienr top (list process ที่อยู่ใน container)
  4. docker container stats (ไว้ดู memory usage ของใน contaienr)
  5. docker contaienr inspect (ไว้ดูข้อมูลของ config ใน container)

Build Own Image

การสร้าง Image เองเป็นเรื่องที่ง่ายมากเลย หลังจากอ่านมาจากทั้งหมดข้างบนแล้วน่ะ 🙂 อย่างที่รู้กันว่าการสร้าง Images นึงมันเกิดจากการสร้าง Layer ซ้อนทับกันหลายๆตัวบน Dockerfile ทีนี้สิ่งที่เราต้องทําก็คือสร้าง DockerFile ขึ้นมา แล้วก็ไล่ใส่ steps ที่ต้องการลงไป

มาดูตัวอย่างจาก Mysql DockerFile กัน จะได้เข้าใจ

dockerfile-mysql-example
dockerfile-mysql-example

ถ้ากดลิ้งเข้าไปดู อาจจะตกใจนิดนึงว่าอะไรเยอะแยะมากมาย บางคนก็อาจจะมึนและกดปิดไปเลยน่ะ 🙂 แต่เดี๋ยวก่อนๆ มันไม่ได้ยากขนาดนั้น แบ่งคําสั่งหลักๆออกเป็น

  1. FROM
  2. ENV
  3. RUN
  4. EXPOSE
  5. VOLUME
  6. CMD

บอกได้เลยว่าไม่เกินนี้อะ คือหลักการณ์ง่ายๆคือเราต้องเลือก parent image เช่น os ต่างๆแบบ ubuntu,debiean ยังงี้ออกมา (อารมณ์สร้างพื้นก่อนว่าจะทํางานบนอะไร) ด้วยคําสั่ง FROM แล้วหลังจากนั้นก็ค่อยๆไล่ใส่ Enviroment variable ที่จําเป็นกับ Image เราโดยจะใช้ apt-get ก็ได้ถ้าเป็น ubuntu เพื่อโหลดแอพต่างๆเพิ่ม เช่น curl เป็นต้น

แล้วก็ตั้ง port ที่จะให้ host เข้ามา connect ได้, ตั้ง volume แบบข้างบนที่อธิบาย และจบท้ายด้วย CMD เพื่อเป็น default เวลารันครั้งสุดท้ายเมื่อสร้าง contaienr เสร็จนั้นเอง Ref.

ในกรณีของ mysql มันจะรัน CMD [mysqld] เพื่อสั่งให้ทํางานนั้นเอง 🙂

Tips: ถ้าเคย run docker แล้วคําสั่งครั้งหน้าไม่ใช่ run แล้วน่ะ แค่สั่ง docker start “docker-name” มันจะ run port และอื่นๆที่เคย set ไว้ตอน run ทันทีเลย

สรุป

docker เบื้องต้นหลักการณ์เวลาทํางานจริงๆแล้วมีแค่นี้แหละ ไม่มากเลย ลองอ่านวนดูจริงๆ แล้วจะเข้าใจว่า docker ง่ายมาก และมีประโยชน์มากสําหรับ QA เวลามาสร้าง env. แยก เพื่อทําการทดสอบ คือเราควรจะโฟกัสที่การทดสอบจริงๆ มากกว่าเจอปัญหา Env. นั้นเอง ถึงต้องใช้ Docker ให้คล่องๆๆ 🙂

ไว้คร่าวหน้าจะมาพูดถึง Docker Compose เพื่อใช้กับ Multiple Container ให้ทํางานร่วมกันได้แบบ Seamless และก็ Docker Cluster หรือ Docker Swarm นั้นเอง ว่าหลักการณ์มันคืออะไร!!!

[update]

3/3/2018

  • common mistake เลยเวลาคนจะเอา container เราไป link กับ services อื่นๆคือ ชอบใช้ localhost ในการผูก แต่มันอยู่คนละ network กัน การใช้ Localhost จะ refer ไปที่ตัวเองมากกว่า ทําให้ failed

3/6/2018

  • เหตุผลที่ Docker เวลาเรา grap network มาแล้วแต่ไม่มี ipaddress เป็นไปได้ว่ามันใช้ host ip เหมือนกับ host ของเรา แทนที่จะเป็น private network เพราะฉะนั้นบางที ที่เราออกแบบระบบเราก็ใช้ host network จะดีกว่า เพื่อทํา clustering ของ docker : อ่านต่อที่นี้เลย

Leave a Reply

avatar

This site uses Akismet to reduce spam. Learn how your comment data is processed.