ข้ามไปที่เนื้อหาหลัก

Docker ทำอะไรได้มากกว่า run container มาหาคำตอบกัน — Part 2

จริงๆ แล้ว Docker ทำอะไรได้มากกว่า run container มาหาคำตอบกัน — Part 2

8.อยาก Persist Data ต้องทำอย่างไร

อย่าง redis ที่ถ้าไม่ทำ persist data ข้อมูลที่อยู่ด้านในก็จะหายไปทันที ถ้าเกิด redis ดับไป โดยการทำ persist ข้อมูลเก็บไว้ได้โดยใช้ option -v ในการ mount volume ออกมาภายนอก
docker run -v /docker/redis-data:/data --name redis -d redis redis-server --appendonly yes
  • redis-server — appendonly yes — เป็น Mode ที่จะทำ persist data ลงในไฟล์ appendonly.aof ทุกครั้งที่มีการเปลี่ยนแปลงข้อมูลใน redis
  • เอาข้อมูลใน appendonly.aof ที่อยู่ใน /data ออกมานอก container ใน directory /docker/redis-data
From now on, every time Redis receives a command that changes the dataset (e.g. SET) it will append it to the AOF. When you restart Redis it will re-play the AOF to rebuild the state (Ref)
จากนั้นเราลอง set ค่าเข้า redis โดยใช้ redis-cli
docker exec -i redis redis-cli
> set data "hello world"
หรืออาจจะเก็บคำสั่งไว้ในไฟล์แล้วใช้ redis-cli -pipe อ่านแล้วรันทั้งหมด
cat data | docker exec -i redis redis-cli --pipe
แล้วลองดูว่าข้อมูลถูก backup ไว้ใน file จริงไหม ได้ที่
ls /docker/redis-data 
โดยเราสามารถนำข้อมูลจาก data ออกมาใช้กับ container อื่นๆ ได้ 2 แบบ
  1. ใช้แบบ option -v
docker run -v /docker/redis-data:/backup ubuntu ls /backup
2. ใช้แบบ option -volumes-from
docker run --volumes-from redis -it ubuntu ls /data
โดยปกติการ mount volume ของ docker จะเป็นแบบ full read/write access ถ้าต้องการไม่อยากให้ container ที่เราแชร์ข้อมูลไปแก้ไขข้อมูล ให้ใส่ :ro ต่อท้าย volume แบบนี้ จะลบไม่ได้
docker run -v /docker/redis-data:/data:ro -it ubuntu rm -rf /data

9. การจัดการ log ของ docker

ใครหลายคนอาจจะเคยใช้ docker แล้วเจอปัญหา container บวม เพราะโดยปกติ container จะ log สิ่งที่แสดงผลใน standard out และ standard error มาเก็บไว้ในรูปแบบ json-file
หากเราอยากดูก็ใช้คำสั่ง
docker logs <ชื่อ container|tag> 
แต่ถ้าถ้าเราเก็บใน json-file ไปเรื่อยๆ ขนาดมันจะใหญ่จนบวม เราจึงจำเป็นต้องย้ายที่เก็บ log file มาใช้ log driver ตัวอื่นอย่าง syslog แทน
โดยในตอนรันจะต้องใส่ option -log-driver เพิ่มตอนรัน container ขึ้นมา
docker run -d --name redis-syslog --log-driver=syslog redis
ซึ่งหากเราเรื่อง log-driver เป็น syslog จะไม่สามารถใช้ docker logs ดู log ได้ จะต้องเข้าไปดูผ่าน syslog แทน
หรือถ้าหากเราไม่ต้องการให้ container ทำการ log เลยก็สามารถ set ได้ โดยระบุให้ log-driver เป็น none
docker run -d --name redis-none --log-driver=none redis

10. ให้ Container สามารถ restart ตัวเองได้

โดยปกติ by default docker ถ้าเรารันด้วยคำสั่ง docker run ปกติ container ที่ทำงานอยู่แล้ว จู่ๆ ดับไป ก็จะดับไปเลย
แต่ถ้าหากต้องการให้ เมื่อ container ดับไปให้ลอง retry ตัวเองขึ้นมาใหม่ มี 2 วิธีโดยเราจะใส่ option -restart
  1. retry จนกว่าจะถึงครั้งที่กำหนด
docker run -d --name restart-3 --restart=on-failure:3 scrapbook/docker-restart-example
  • on-failure:3
    on-failure: จำนวนครั้ง
2. retry จนกว่า container จะสามารถรันตามปกติได้
docker run -d --name restart-always --restart=always scrapbook/docker-restart-example

11. ใส่ label ให้ container

เราสามารถใส่ label ให้ container ได้ในจังหวะที่เราจะ run container โดยใช้ option -l
docker run -l user=12345 -d redis
  • user=12345
    <ชื่อ label>=<value>
เราสามารถใส่ label หลายๆ อันพร้อมกันได้โดยใช้ option -label-file
docker run --label-file=labels.txt -d redis
หรือตอนสร้าง dockerfile สามารถใช้ คำสั่ง LABEL ในการใส่ label ให้ตั้งแต่ตอนเป็น image
LABEL author=golfapipol
โดยสามารถดู label ที่เราใส่ลงไปใน container ได้โดยใช้ docker inspect
docker inspect <ชื่อ container|id>
แต่ถ้าต้องการ filter ดูเฉพาะข้อมูลที่เราสนใจให้ใช้ option -f ในการกรองข้อมูล
docker inspect -f "{{json .Config.Labels}}" <ชื่อ container|id>
แต่ถ้าหาจาก image ต้องเปลี่ยนจาก .Config ไปเป็น .ContainerConfig
docker inspect -f "{{json .ContainerConfig.Labels}}" <ชื่อ image>
แล้วตั้ง label มาเอามาทำอะไร พอเราใช้ docker หลายๆ project เริ่มเกิดความสับสนเราสามารถ list detail ของ container ทั้งหมดได้โดยใช้ในการจัดกลุ่มของ container แล้วใช้ label ในการ filter หาเอา
// filter containers
docker ps --filter "label=author=golfapipol
// filter images
docker images --filter "label=author=golfapipol"

12. ทำ Load Balance Container

หากเราต้องการลองทำ load balance ใน container โดยมี
  • 1 Container เป็นตัว Load balance
  • 2 Containers เป็นตัว application
เราก็สร้าง config ของ nginx ขึ้นมา
# file: default.conf
# If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the
# scheme used to connect to this server
map $http_x_forwarded_proto $proxy_x_forwarded_proto {  
   default $http_x_forwarded_proto;  
   ''      $scheme;
}
# If we receive X-Forwarded-Port, pass it through; otherwise, pass along the
# server port the client connected to
map $http_x_forwarded_port $proxy_x_forwarded_port {
   default $http_x_forwarded_port;
   ''      $server_port;
}
# If we receive Upgrade, set Connection to "upgrade"; otherwise, delete any# Connection header that may have been passed to this server
map $http_upgrade $proxy_connection {
   default upgrade;
   '' close;
}
# Apply fix for very long server names
server_names_hash_bucket_size 128;
# Default dhparamssl_dhparam /etc/nginx/dhparam/dhparam.pem;
# Set appropriate X-Forwarded-Ssl header
map $scheme $proxy_x_forwarded_ssl {
   default off;
   https on;
}
gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
log_format vhost '$host $remote_addr - $remote_user [$time_local] '                
                 '"$request" $status $body_bytes_sent '                
                 '"$http_referer" "$http_user_agent"';
access_log off;
resolver 8.8.8.8;
# HTTP 1.1 support
proxy_http_version 1.1;
proxy_buffering off;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
# Mitigate httpoxy attack (see README for details)
proxy_set_header Proxy "";
server {
        server_name _; # This is just an invalid value which will never trigger on a real hostname.
        listen 80;
        access_log /var/log/nginx/access.log vhost;
        return 503;
}
# proxy.example
upstream proxy.example {
                                ## Can be connected with "bridge" network
                        # cranky_borg
                        server 172.18.0.4:80;
                                ## Can be connected with "bridge" network
                        # nifty_snyder
                        server 172.18.0.3:80;
}
server {
        server_name proxy.example;
        listen 80 default_server;
        access_log /var/log/nginx/access.log vhost;
        location / {
                proxy_pass http://proxy.example;
        }
}
จากนั้นสร้าง container nginx ขึ้นมาโดยใช้ config นี้
docker run -d -p 80:80 -e DEFAULT_HOST=proxy.example -v /var/run/docker.sock:/tmp/docker.sock:ro --name nginx jwilder/nginx-proxy
จากนั้นสร้าง container ที่เป็น application ขึ้นมา
// application #1
docker run -d -p 80 -e VIRTUAL_HOST=proxy.example katacoda/docker-http-server
// application #2
docker run -d -p 80 -e VIRTUAL_HOST=proxy.example katacoda/docker-http-server
โดยถ้าเราลอง request ไปยัง container จะทำการรับ request จาก container application เป็นแบบ round-robin

13. run docker หลายๆ containers พร้อมๆ กันด้วย docker-compose

กรณีเรามี project เว็บแล้วต้องใช้ redis ด้วย แต่ไม่อยากรันแยกกัน เราสามารถเขียนไฟล์ docker-compose.yml ขึ้นมาสำหรับ run หลายๆ containers พร้อมๆ กัน
web:
    build: .
    links:
        - redis
    ports:
        - "3000"
        - "8000"
redis:
    image: redis:alpine
    volumes:
        - /var/redis/data:/data
จากนั้นสั่งคำสั่ง docker-compose up เพื่อ run container ทั้งหมดขึ้น
docker-compose up -d // daemon mode: run on background
หากต้องการดูว่ามี container อะไร run อยู่บ้านให้ใช้
docker-compose ps
สามารถดู logs ของทุก container ได้จาก
docker-compose logs
หากต้องการ scale project web ออก ใช้คำสั่ง
docker-compose scale web=3 // scale up
docker-compose scale web=1 // scale down
หลังใช้เสร็จแล้วต้องการลบ container ทิ้งให้ใช้
docker-compose stop // เพื่อหยุดการทำงานของ container
docker-compose rm // เพื่อลบ container ทั้งหมดออก

14. ดูการใช้งาน resources ของแต่ละ container

เราสามารถดูว่าแต่ละ container ใช้ cpu, memory, I/O ไปเท่าไหร่ได้โดยใช้คำสั่ง
docker stats <ชื่อ container| id>
หรือต้องการดูว่า container ที่รันอยู่ทั้งหมดอันไหนใช้เท่าไหร่บ้างได้ด้วย
docker ps -q | xargs docker stats

15. Optimize ขนาดของ image ให้เล็กลงด้วย multi-stage build

หากเราต้องการจะ deploy application ของเราที่เขียนด้วย golang ขึ้น แต่ตอน build docker จะต้องทำการ build เป็นไฟล์ binary ออกมาจึงจะใช้งานได้ แต่จริงๆ แล้วหลังจากที่เราได้ binary file แล้วก็ไม่มีความจำเป็นที่ต้องใช้ golang แล้วเพราะ binary สามารถ run ได้ในตัวเอง
# First Stage
FROM golang:1.6-alpine
RUN mkdir /app
ADD . /app/
WORKDIR /app
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
เราจึงใส่ Second stage โดยไปหยิบ image ของ alpine ที่มีขนาดเล็กกว่ามาใช้ โดยจะทำไฟล์ binary ที่ได้จากการ build ใน stage แรกมาเก็บไว้ใน stage 2
# file name: Dockerfile.multi
# First Stage
...
# Second Stage
FROM alpine
EXPOSE 80
CMD ["/app"]
# Copy from first stage
COPY --from=0 /app/main /app
จากนั้นลองสั่ง
docker build -f Dockerfile.multi -t golang-app .
เมื่อเราลอง
docker images
ดูจะพบ image ใหม่เกิดขึ้น 2 images
  • image ที่ไม่มี tag คือของ stage 1
  • image ที่ชื่อ tag เป็น golang-app คือของ stage 2
ซึ่งจะเห็นได้ว่าขนาดเล็กลงมากเลย

16. เราสามารถเปลี่ยนรูปแบบแสดงผลของ docker ps ได้

ปกติ docker ps เราจะได้ format ที่จะมีอยู่ด้วยกัน 6คอลัม (container id, image, command, created, status, ports) แต่จริงๆ แล้วเราสามารถกำหนด format ได้โดยใช้ option -format
docker ps --format '{{.Names}} container is using {{.Image}} image'

docker ps --format 'table {{.Names}}\t{{.Image}}'
และนอกจาก docker ps เรายังสามารถใช้ option -format ในการจัดการแสดงผลของ docker inspect ได้อีกด้วย
docker ps -q | xargs docker inspect --format '{{ .Id }} - {{ .Name }} - {{ .NetworkSettings.IPAddress }}'
สำหรับใครที่ยังไม่ได้อ่าน part แรก ทางนี้เลยครับ :)

ความคิดเห็น

โพสต์ยอดนิยมจากบล็อกนี้