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

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 แรก ทางนี้เลยครับ :)

ความคิดเห็น

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

ย้าย Docker Container ข้าม Cloud Provider

ย้าย Docker Container ข้าม Cloud Provider Posted on  02/12/2014   by  Anuchit Chalothorn ปัญหาเรื่องการย้าย Application ข้าม Cloud Provider เป็นปัญหาปกติที่ต้องหาทางแก้ไข ซึ่งแต่ละ Cloud Provider มีช่องทางในการบริการที่ต่างกัน เทคโนโลยีต่างกัน เครื่องมือที่ใช้งานก็ต่างกัน เรียกได้ว่าถ้าจะย้าย instance จะทำได้ยากมาก ครั้งนี้จะให้แนวคิดเรื่องการย้าย Container จาก Cloud Provider ค่ายหนึ่งไปยังอีกค่ายหนึ่งหรือจาก Local ไปยัง Cloud Provider ก็ได้ ซึ่งทำได้หลายวิธี จากภาพแนะนำการย้ายแบบง่ายๆ ผ่าน Docker อย่างเดียวไม่ได้ใช้เครื่องมือใดๆ เพิ่มเติม ถ้าจะให้สะดวกควรมี Orchestration Service อยู่ในแต่ละ Cloud Provider ด้วย ในเบื้องต้นทำความเข้าใจแบบง่ายๆ กันก่อน การทำ Migration ทำได้ 2 แบบ คือ Import / Export ไฟล์ Container ทั้งก้อนจาก Local ไปยัง Cloud วิธีนี้จะให้ความสะดวกมากกว่าแต่การสำรองข้อมูลและการย้ายไฟล์ Export จะช้าขึ้นอยู่กับความเร็วของเน็คเวิร์ก Push / Pull ให้ Commit Container State ปัจจุบันแล้ว push ขึ้น Docker Registry อาจจะเป็น Docker Hub (Private) หรือ P...