เตาะแตะไปกับ Docker ตอนที่ 1 Containers (Build, Ship and Run)

ผมอ่าน Get Started จาก docs.docker.com แล้วคิดว่าพอเข้าใจว่า docker ใช้งานอย่างไรมากขึ้นในแง่ความหมายของ Docker – Build, Ship, and Run Any App, Anywhere ที่เป็นจุดเด่น หลังจากอ่านจบที่ผมเขียนในตอนที่ 1 นี้ ก็น่าจะเข้าใจคำว่า Container และในตอนถัดไปก็จะนั้นจะเล่าถึงความหมายของคำว่า Service และ Stack ตามลำดับ

ในการทดสอบเพื่อเขียนบทความ ผมได้ติดตั้ง docker บน ubuntu server 64 bit Xenial 16.04 (LTS) และรุ่นของ Docker ที่ใช้คือ Docker version 17.06.0-ce, build 02c1d87 ซึ่ง Docker Software มี 2 ชนิด คือ Community Edition (CE) และ Enterprise Edition (EE) ให้เลือกใช้

[Installation]
วิธีติดตั้ง Docker บน ubuntu นั้นจะมีคำแนะนำสำหรับรุ่น docker-ce คือเริ่มต้นจากการ SET UP THE REPOSITORY เสร็จแล้วจึง INSTALL DOCKER CE อย่างคร่าว ๆ ก็ใช้คำสั่งดังนี้

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
sudo apt-get update
sudo apt-get install -y docker-ce

วิธีทำให้ไม่ต้องใส่คำว่า sudo หน้าคำสั่ง Docker ทุกครั้ง ดังนี้

sudo usermod -aG docker ${USER}
su - ${USER}
id
exit

logout แล้ว login กลับเข้ามาใหม่ พิมพ์คำสั่ง id จะเห็นว่าอยู่ใน group docker ด้วยแล้ว

ต่อไป Verify ว่า Docker CE ถูกติดตั้งสำเร็จโดยการรัน hello-world image

docker run hello-world

ตรวจสอบเวอร์ชั่น

docker --version

[Containers]
ตอนนี้ก็ได้เวลาเตาะแตะแบบ docker เราจะเริ่มกันที่ส่วนล่างสุดของ hierarchy ของการสร้าง app 3 ส่วน นั่นคือ container
Stack
Services
Container (เรากำลังอยู่ที่นี่)

การสร้าง app เราจะทำ container ขึ้นมาจากสิ่งที่เรียกว่า Dockerfile คือไฟล์ที่เขียนข้อกำหนดว่า container จะมีสภาพแวดล้อมเป็นอะไรบ้าง โดยต้องเริ่มต้นจากสร้าง directory ว่าง ๆ บน host (จะเรียก ubuntu server ที่ติดตั้ง docker ไว้ว่า host) แล้วสร้างไฟล์ชื่อ Dockerfile ด้วยเอดิเตอร์ที่ถนัด เช่น vi หรือ nano เป็นต้น และถ้าภายในไฟล์นี้อ้างถึงไฟล์อื่น ๆ ก็สร้างไว้ให้ครบด้วยนะ ผมจะใช้ตัวอย่างจาก docs.docker.com ครับ

mkdir myhello
cd myhello

สร้างไฟล์ชื่อ Dockerfile ด้วยเอดิเตอร์ vi ดังนี้

vi Dockerfile

คัดลอกเนื้อหาจากตัวอย่าง https://docs.docker.com/get-started/part2/#dockerfile

# Use an official Python runtime as a parent image
FROM python:2.7-slim

# Set the working directory to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
ADD . /app

# Install any needed packages specified in requirements.txt
RUN pip install -r requirements.txt

# Make port 80 available to the world outside this container
EXPOSE 80

# Define environment variable
ENV NAME World

# Run app.py when the container launches
CMD ["python", "app.py"]

ภายในไฟล์ Dockerfile มีอ้างถึงไฟล์ชื่อ requirements.txt และ app.py
สร้างไฟล์ชื่อ requirements.txt ด้วยเอดิเตอร์ vi ดังนี้

vi requirements.txt

คัดลอกเนื้อหาจากตัวอย่าง https://docs.docker.com/get-started/part2/#requirementstxt

Flask
Redis

แล้วสร้างไฟล์ชื่อ app.py ด้วยเอดิเตอร์ vi ดังนี้

vi app.py

คัดลอกเนื้อหาจากตัวอย่าง https://docs.docker.com/get-started/part2/#apppy

from flask import Flask
from redis import Redis, RedisError
import os
import socket

# Connect to Redis
redis = Redis(host="redis", db=0, socket_connect_timeout=2, socket_timeout=2)

app = Flask(__name__)

@app.route("/")
def hello():
 try:
  visits = redis.incr("counter")
 except RedisError:
  visits = "<i>cannot connect to Redis, counter disabled</i>"

 html = "<h3>Hello {name}!</h3>" \
 "<b>Hostname:</b> {hostname}<br/>" \
 "<b>Visits:</b> {visits}"
 return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname(), visits=visits)

if __name__ == "__main__":
 app.run(host='0.0.0.0', port=80)

ดังนั้นในตอนนี้ ใน directory ของเราจะมีไฟล์ 3 ไฟล์ คือ

Dockerfile requirements.txt app.py

 

ต่อไปเป็นการสร้าง image ชื่อ myhello จากไฟล์ 3 ไฟล์นั้น จะตั้งชื่อเป็นอะไรก็ได้นะ

docker build -t myhello .

จุด (.) หลังคำว่า myhello คือ อ้างถึงไฟล์ทุกไฟล์ใน current directory ในการสร้าง image

ตอนนี้เราก็จะได้ docker image ชื่อ myhello อยู่ในเครื่องของเรา เรียกว่า local Docker image registry ดูรายชื่อด้วยคำสั่งนี้

docker images

ตัวอย่างผลลัพธ์

REPOSITORY TAG IMAGE ID CREATED SIZE
myhello latest 9748e0bc3640 7 seconds ago 194MB
python 2.7-slim 4f57b96607d2 6 hours ago 182MB
hello-world latest 1815c82652c0 4 weeks ago 1.84kB

 

ต่อไปก็เป็นการสั่งทำงาน app ที่เราสร้างเป็น image แล้วนั้น คือ docker run
เช่น ระบุว่าจะใช้ port หมายเลข 4000 ของ host ไปยัง port หมายเลข 80 ของ container

docker run -p 4000:80 myhello
 * Running on http://0.0.0.0:80/ (Press CTRL+C to quit)

ผลของการทำคำสั่ง docker run คือ เราจะได้ container ที่รันให้บริการ

ตอนนี้ เครื่องที่ติดตั้ง docker นี้มี IP บน net เดียวกับเครื่องอื่น ๆ ก็จะเรียกดูข้อมูลทางหน้าเว็บจากเครื่องใดก็ได้ใน net นั้น เช่น http://server_ip:4000
จะได้ผลลัพธ์

หากในเครื่อง host ยังไม่ได้ติดตั้งพวก apache2 หรือ nginx web server อยู่ก่อน (ซึ่งพวกนี้จะมีค่า default ที่ใช้ port หมายเลข 80 สำหรับ http) แล้วหละก็ เราจะสามารถระบุว่าจะใช้ port หมายเลข 80 ของ host ไปยัง port หมายเลข 80 ของ container ได้ โดยเขียนคำสั่งเป็นแบบนี้
docker run -p 80:80 myhello
และเมื่อเรียกดูข้อมูลทางหน้าเว็บ ก็เขียนสั้น ๆ แบบนี้ http://server_ip ซึ่งไม่ต้องระบุ port

แต่ตอนนี้สังเกตดูว่า เราต้องกด Ctrl + C เพื่อออกจาก app ที่รันอยู่เพื่อกลับไป command prompt เพราะว่า app ของเรารันอยู่ใน foreground

 

ต่อไปเป็นการสั่งรัน app ใน background เหมือนกับการสั่ง service ทั่วไปรันรอให้บริการ
(run the app in the background, in detached mode) ทำดังนี้

docker run -d -p 4000:80 myhello

จะได้ container ID เลขยาว ๆ หลาย ๆ ตัว และกลับออกมาที่ command prompt เลย ตัวอย่างเช่น
773ddb78be11ec557405817ad871650e18f2ef82fbb62b512d47179de9c4486a

ตรวจสอบว่าเรามี container ใดรันอยู่บ้าง ด้วยคำสั่ง

docker ps

ผลลัพธ์

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
773ddb78be11 myhello "python app.py" About a minute ago Up About a minute 0.0.0.0:4000->80/tcp romantic_goldberg

ตอนนี้ให้เราหยุดรัน app โดยการอ้างถึง CONTAINER ID แบบนี้:

docker stop 773ddb78be11

 

เมื่อมาถึงตรงจุดนี้ เราได้ทำขั้นตอน “Build” เรียบร้อยแล้ว ยังเหลือ “Ship” และ “Run” มาดูกันต่อ

Ship ก็คือการที่เราจะนำ image ที่เราสร้างขึ้นนี้อัปโหลดไปไว้บน Docker’s public registry เพื่อ share image ให้ใคร ๆ ก็นำไปใช้ได้จากทุกที่และทุกเวลา จากนั้นเมื่อเราต้องการจะรัน app นี้ เราก็ไปหาเครื่อง Host มาสักตัว ติดตั้ง docker แล้ว run image ที่เราอัปโหลดไปไว้นั้น

เรามาดูขั้นตอนการ share image ซึ่งเราจะต้องมี User ID ที่ cloud.docker.com จากนั้นใช้คำสั่ง

docker login

ก่อนที่เราจะอัปโหลด image ของเราขึ้นไป ให้เราทำการ Tag the image เพื่อเป็นข้อมูลสั้น ๆ ในรูปแบบ
username/repository:tag เพื่ออธิบายหรือใส่เวอร์ชั่นของ image ก็ได้ และมีรูปแบบคำสั่งคือ
docker tag image username/repository:tag
ตัวอย่างเช่น

docker tag myhello woonpsu/docsdocker:part1

ตรวจสอบด้วยคำสั่ง

docker image

อัปโหลด image (เรียกวิธีการนี้ว่า Publish the image) ในรูปแบบคำสั่ง
docker push username/repository:tag
ตัวอย่างเช่น

docker push woonpsu/docsdocker:part1

 

เมื่อมาถึงตรงจุดนี้ เราได้ทำขั้นตอน “Build“, “Ship” เรียบร้อยแล้ว เหลือขั้นตอน “Run” มาดูกันต่อ

จากนี้ไป เราก็สามารถใช้คำสั่ง docker run เพื่อดึง และ รัน app จาก image ที่ฝากไว้บน Public Registry เราจะรันที่เครื่องใด ๆ ก็ได้ (Pull and run the image from the remote repository) ด้วยคำสั่งนี้

docker run -p 4000:80 woonpsu/docsdocker:part1

เมื่อมาถึงตรงจุดนี้ เราได้ทำขั้นตอน “Build“, “Ship” และ “Run” เรียบร้อยแล้ว

 

ในตอนถัดไป เราจะเรียนรู้ว่าวิธีการเพิ่มจำนวน application ของเราโดยการรัน container ในสิ่งที่เรียกว่า service
โปรดติดตามตอนต่อไป

 

References:
Get Started https://docs.docker.com/get-started/
Get Docker CE for Ubuntu https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/
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