Quản lý file docker-compose.yml cho nhiều môi trường

From: https://www.docsity.com/en/news/it/funny-aspects-related-developers/

Trong quá trình làm việc Docker của mình thì docker-compose là một phần không thể thiếu được. Compose giúp định nghĩa và chạy một application với nhiều container (multi-container application). Chỉ với 1 file yaml thiết định và docker-compose up mình có thể dễ dàng tạo và chạy toàn bộ các services phục vụ cho việc chạy ứng dụng như Mysql, Fluentd, Redis, Nginx,..

Compose là một công cụ tuyệt với không chỉ dùng cho development, testing, staging environments, mà còn ứng dụng trong CI workflows.

Cũng giống như các công nghệ khác, luôn có rất nhiều điều để tìm hiểu về Docker, thường có 2 cách tiếp cận broad strokes (học rộng) and deep dives (học sâu).

Nhưng cách tiếp cận cá nhân của mình là tìm hiểu 1 lượng kiến thức vừa đủ để tự tin bắt đầu sử dụng một công nghệ, và để môi trường thực tiễn dạy cho mình những gì cần học tiếp theo. Phần lớn của việc sử dụng Docker trong môi trường thực tế.

Hôm nay mình sẽ chia sẻ cách quản lý file docker-compose.yml cho nhiều môi trường khác nhau như thế nào.

Mình dùng docker-compose file cho môi trường làm việc ở local, và môi trường staging, production. Bài toán thường gặp ở đây là mỗi môi trường khác nhau yêu cầu thiết định khác nhau. Ví dụ:

  • Port dùng ở local thường không được dùng ở môi trường production vì lý do security,…
  • Phải chạy nhiều docker-compose trên cùng một server
  • Cần thiết định logging driver cho từng môi trường khác nhau. Ví dụ trên production dùng AWS Cloudwatch logs, …
  • Ở môi trường production thì volume path khác
  • Dùng các cloud storage như RDS trên môi trường production, …

Rất may mắn là Docker-compose hỗ trợ cho các nhu cầu này bằng định nghĩa file docker-compose.yml

Compose supports two methods of sharing common configuration:

– Extending an entire Compose file by using multiple Compose files

– Extending individual services with the extends field (for Compose file versions up to 2.1)

https://docs.docker.com/compose/extends/

Có 2 cách tiếp cận
1) Tạo 1 file gốc docker-compose.yml base và nhiều file docker-compose cho các môi trường khác nhau để override các thiết định của file gốc

2) Định nghĩa file của từng service và tạo nhiều file docker-compose cho các môi trường khác nhau, các file này dùng cấu trúc extends để tùy chỉnh thiết định của từng service cho các môi trường.

Cá nhân mình thích cách 1) hơn vì nó tổng hợp các thiết định của môi trường vào từng file nên quản lý dễ dàng hơn. Chắc có lẽ mình đã quá quen với việc sử dụng .env để tùy biến các môi trường web rồi.

Việc sử dụng nhiều docker-compose file cho phép người dùng có thể override thiết định cho các môi trường staging, production.

Khi cần override thiết định cho môi trường staging thì mình sẽ thêm 1 file docker-compose.staging.yml, và viết các thiết định cần override vào đấy

Mình sẽ chuẩn bị 2 file

docker-compose.yml

# docker-compose.yml
web:
  image: example/my_web_app:latest
  depends_on:
    - db
    - cache

db:
  image: postgres:latest

cache:
  image: redis:latest

docker-compose.staging.yml với các thiết định volume, port, biến môi trường

web:
  build: .
  volumes:
    - '.:/code'
  ports:
    - 8883:80
  environment:
    DEBUG: 'true'

db:
  command: '-d'
  ports:
    - 5432:5432

cache:
  ports:
    - 6379:6379

Sau đấy chỉ cần chạy lệnh

docker-compose \
    -f docker-compose.yml \
    -f docker-compose.staging.yml \
    up -d

Khi chạy thì compose sẽ tự động override các thiết định trong docker-compose.yml bằng các thiết định trong docker-compose.staging.yml

Thật tuyệt vời đúng không? Bây giờ, các file tùy chỉnh cho môi trường sẽ được quản lý riêng biệt, có thể được lưu trữ trong một repo git khác hoặc được quản lý bởi một nhóm khác. Như thế vừa đảm bảo các thông sensitive, các file docker-compose.staging.yml, docker-compose.production.yml sẽ được team DevOps quản lý riêng biệt với team phát triển.

Happy hacking! Happy Docker life.

Additional, Other Kubernetes related topics is here. https://tuantranf.me/tag/kubernetes/

Synchronizing files between local machine and Kubernetes Persistent Volume

How to rsync files to a Kubernetes Persistent Volume (PV)?

I need to rsync a recursive directory tree to a specific PV in a Kubernetes cluster.
Unfortunately, kubectl cp command does not support rsync behavior at the moment. So we have to do a simple hacking to do it.
rsync behavior to copy files from pod to pod, perhaps for kubectl cp · Issue #551 · kubernetes/kubectl · GitHub

TLDR;

We can use a rsync script to rsync files to a Kubernetes pod which mount specific PV as a volume and rsync is executable.

The same idea can be found here
rsync files to a kubernetes pod – Server Fault

Let’s say we have a recursive folder sample-dir and a PV named sample-pv which Persistent Volume Claim (PVC) named sample-pvc

Prepare working pod

The working pod has to (1) mount PVC sample-pvc as a volume; (2) has rsync is executable.

We will create a YAML file for working pod which mounts PVC sample-pvc as a volume. And for (2), we will use a rsync executable Docker image eeacms/rsync.

working-pod.yml

apiVersion: v1
kind: Pod
metadata:
    name: working-pod
spec:
    volumes:
    - name: sample-volume
      persistentVolumeClaim:
        claimName: sample-pvc # mount pvc as volume
    containers:
    - name: operator
      image: eeacms/rsync # rsync executable Docker image
      command: ['sh', '-c', 'sleep 3600'] # sleep 3600 secs for files copy
      volumeMounts:
      - mountPath: /var/files # mount volume to path /var/files
        name: sample-volume

Prepare rsync bash script

We will create a bash script name rsync-helper.sh for execute rsync on working pod.

#!/bin/bash
pod=$1;shift;kubectl exec -i $pod -- "$@"

Make sure rsync-helper.sh is executable.

$ chmod +x ./rsync-helper.sh

Deploy working pod to Kubernetes cluster

In next step, we will deploy working pod to Kubernetes cluster.

$ kubectl apply -f ./working-pod.yml

Execute rsyn command on remote working pod

After status of wroking-pod pod become running, we will execute rsync-helper.sh script on it remotely.

$ rsync -av --delete-after --progress -e './rsync-helper.sh' sample-dir working-pod:/var/files/

building file list ...
 0 files...
19 files to consider
sample-dir/
sample-dir/new-file
           6 100%    0.00kB/s    0:00:00
           6 100%    0.00kB/s    0:00:00 (xfer#1, to-check=16/19)
sample-dir/updated
           5 100%    4.88kB/s    0:00:00
           5 100%    4.88kB/s    0:00:00 (xfer#2, to-check=15/19)
deleting sample_dir/deleted

sent 689 bytes  received 70 bytes  1518.00 bytes/sec
total size is 98957  speedup is 130.38

Note, you can add –dryrun option for debuging

$ rsync -av --dry-run --delete-after --progress -e './rsync-helper.sh' sample_dir working-pod:/var/files/

Teardown

We will delete working pod by bellow command

$ kubectl delete --grace-period=1 -f ./working-pod.yml

Conclusion

In this blog, we have a work around solution for synchronizing files and directories between local machine and PV on Kubernetes cluster.

Goodluck, happy Hacking!

Additional, Other Kubernetes related topics is here. https://tuantranf.me/tag/kubernetes/

Kubernetes ConfigMap recursive directory issues

A workaround solution for using Kubernetes Configmap to map a recursive directory.

First, about ConfigMap recursive directory issues, you can find from below topics

or

However, unfortunately, it isn’t possible to build a Kubernetes ConfigMap from a directory, recursively [1] currently.

For example,

conf/
└── integrator
    └── conf
        ├── axis2
        │   └── axis2.xml
        ├── carbon.xml
        ├── datasources
        │   └── master-datasources.xml
        ├── registry.xml
        └── user-mgt.xml

Therefore, we will use a work-around to solve this issue.

Prepare a Helm template

First, let prepare a Helm template for Kubernetes deployment with configmap.yaml for ConfigMap and deployment.yaml for Deployment

$ tree -L 2 helm_templates/your-template
helm_templates/your-template
├── Chart.yaml
├── templates
│   ├── NOTES.txt
│   ├── _helpers.tpl
│   ├── configmap.yaml  <--- ConfigMap
│   ├── deployment.yaml <--- Deployment
└── values.yaml

For instance, Configmap yaml for you Helm template

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ template "your-template.fullname" . }}-config
data:
# mapping configMaps from values.yml to ConfigMap
{{- range $key, $value := .Values.configMaps }}
  {{ $key }}: |-
{{ $value.content | indent 4 }}
{{- end }}

After that, a Deployment.yaml which mount all configMap data to volume using subPath. Please note that we using path properties for mount path setting.

spec:
      volumes:
        - name: {{ template "your-template.fullname" . }}-config
          configMap: { name: {{ template "your-template.fullname" . }}-config }
      containers:
        - volumeMounts:
            {{- range $key, $value := .Values.configMaps }}
            - name: {{ template "your-template.fullname" $ }}-config
              # note that we using path properties for mount path setting
              mountPath: /config/{{ $value.path }}
              subPath: {{ $key }}
            {{- end }}

Generate your Helm values.yaml by Python code

After that, we will prepare a simple Python code to read recursive directory and pass file’s content to values.yaml. For loading recursive directory from config_dir.

def get_config_maps(config_dir) -> dict:
         config_maps = {}
         filenames = [y for x in os.walk(config_dir) for y in glob.glob(os.path.join(x[0], '*')) if os.path.isfile(y)]

         for filename in filenames:
             # remove parent dir path
             relative_path = filename.replace(config_dir + os.path.sep, "")
             # replace path separator by "__" to prevent Kubernetes syntax error
             # currently file path doesn't validate '[-._a-zA-Z0-9]+'
             key = relative_path.replace(os.path.sep, "__")
             with open(filename, 'r') as file:
                 config_maps[key] = {
                     "path": relative_path, # a path that will be used for mount ConfigMap to Pod's volume
                     "content": file.read()
                 }
         return config_maps

The next step is, saving file’s content to values.yaml

 # load value template
 confif_dir = 'path/to/your/conf'
 with open('helm_templates/your-template/values.yaml', 'r') as file:
      deploy_values = yaml.safe_load(file)
      deploy_values['configMaps'] = get_config_maps(config_dir)

Deploy using your Helm template

helm install [your-helm-template-path] -f values.yaml

In conclusion, we can build a ConfigMap from a recursive directory by using a simple Python code with Helm template. Good luck & happy Kubernetes! :beer:

Additional, Other Kubernetes related topics is here. https://tuantranf.me/tag/kubernetes/

Original work is here https://gist.github.com/tuantranf/1faf61d0864353ed617af1f90f3ee453