จริงๆ แล้ว 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 แบบ
- ใช้แบบ 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
- 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 แรก ทางนี้เลยครับ :)
- รับลิงก์
- X
- อีเมล
- แอปอื่นๆ
- รับลิงก์
- X
- อีเมล
- แอปอื่นๆ
ความคิดเห็น
แสดงความคิดเห็น