Open12

Docker Desktopでk8sとcompose V2

thaimthaim

Docker Desktop on Macでk8s機能とDocker Compose V2機能を利用してみる。

macOS Big Sur: 11.6.5
Docker Desktop: 4.7.1 (77678)

thaimthaim

kubernetes機能検証は基本的には公式ドキュメントに従う。

最新のDocker Desktopでは k8s v1.22.5がサポートされている。
これは、kubernetes release pageGitHubリポジトリによるとマイナーバージョンはサポートされている(1.22は2022-10-28がEOL)が最新ではなく(1.23.0が2021-12-08にリリース)、パッチバージョンも最新ではない(1.22.9が2022-04-21にリリース済)。

Docker Desktopから機能を有効化するとkubectlコマンドがインストールされて利用できるようになる。

$ which kubectl
/usr/local/bin/kubectl

$ kubectl version
Client Version: version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.5", GitCommit:"5c99e2ac2ff9a3c549d9ca665e7bc05a3e18f07e", GitTreeState:"clean", BuildDate:"2021-12-16T08:38:33Z", GoVersion:"go1.16.12", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.5", GitCommit:"5c99e2ac2ff9a3c549d9ca665e7bc05a3e18f07e", GitTreeState:"clean", BuildDate:"2021-12-16T08:32:32Z", GoVersion:"go1.16.12", Compiler:"gc", Platform:"linux/amd64"}

また、 contextが docker-desktopに向いていること、ノードが参照できることが確認できる。

$ kubectl config get-contexts
CURRENT   NAME             CLUSTER          AUTHINFO         NAMESPACE
*         docker-desktop   docker-desktop   docker-desktop 

$ kubectl get node           
NAME             STATUS   ROLES                  AGE   VERSION
docker-desktop   Ready    control-plane,master   19m   v1.22.5
thaimthaim

k8s上へのデプロイはk8s ドキュメントに従い確認する。

まずはpodのデプロイ。
Podのページに従いデプロイする。

$ cat pod.yaml 
apiVersion: batch/v1
kind: Job
metadata:
  name: hello
spec:
  template:
    # これがPodテンプレートです
    spec:
      containers:
      - name: hello
        image: busybox
        command: ['sh', '-c', 'echo "Hello, Kubernetes!" && sleep 3600']
      restartPolicy: OnFailure
    # Podテンプレートはここまでです

$ kubectl apply -f pod.yaml 
job.batch/hello created

$ kubectl get pod
NAME             READY   STATUS    RESTARTS   AGE
hello--1-wchm4   1/1     Running   0          3m2s

$ kubectl logs hello--1-wchm4
Hello, Kubernetes!

ちなみに、k8s上にデプロイしたpod(コンテナ)は docker ps コマンド等でも確認できる。

$ docker ps
CONTAINER ID   IMAGE     COMMAND                   CREATED         STATUS         PORTS     NAMES
6264ff18b2d3   busybox   "sh -c 'echo \"Hello,…"   5 minutes ago   Up 5 minutes             k8s_hello_hello--1-wchm4_default_2132fc5f-45e6-49df-b4de-45bab636f100_0

$ docker logs k8s_hello_hello--1-wchm4_default_2132fc5f-45e6-49df-b4de-45bab636f100_0
Hello, Kubernetes!
thaimthaim

続いてDeployment

$ cat deployment.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

$ kubectl apply -f deployment.yaml 
deployment.apps/nginx-deployment created

こちらも問題なく動作することを確認。

$ kubectl get deployments
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     3            3           2m52s

$ kubectl get rs         
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-66b6c48dd5   3         3         3       3m14s
thaim@tadegg:~/work/intro-docker-k8s
$ kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
hello--1-wchm4                      1/1     Running   0          12m
nginx-deployment-66b6c48dd5-n6xkh   1/1     Running   0          3m29s
nginx-deployment-66b6c48dd5-sf5b5   1/1     Running   0          3m29s
nginx-deployment-66b6c48dd5-tgr54   1/1     Running   0          3m29s

続いてdeploymentの更新。
nginx を 1.16.1にアップデートしてデプロイする。こちらも問題なくアップデートされている。

$ sed -i -e "s/nginx:1.14.2/nginx:1.16.1/" deployment.yaml

$ kubectl apply -f deployment.yaml
deployment.apps/nginx-deployment configured

$ kubectl rollout status deployment.v1.apps/nginx-deployment
Waiting for deployment "nginx-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "nginx-deployment" rollout to finish: 1 old replicas are pending termination...
Waiting for deployment "nginx-deployment" rollout to finish: 1 old replicas are pending termination...
deployment "nginx-deployment" successfully rolled out

$ kubectl get deployments
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     3            3           10m

$ kubectl get rs         
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-559d658b74   3         3         3       66s
nginx-deployment-66b6c48dd5   0         0         0       10m

$ kubectl describe deployments
...
Pod Template:
  Labels:  app=nginx
  Containers:
   nginx:
    Image:        nginx:1.16.1
    Port:         80/TCP
...
thaimthaim

続いてDocker Desktopのk8sでは上手くいかない例として、マルチノードにpodをデプロイするdeploymentを作成する。

スケジューリングの同じNodeに共存させない場合を参考に、同一ホストでは同じpodを稼動させないように設定する。
この設定でapplyすると、podAntiAffinityの制約により複数podが起動しない。これは意図した通り。

$ cat multinode.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-server
spec:
  selector:
    matchLabels:
      app: web-store
  replicas: 3
  template:
    metadata:
      labels:
        app: web-store
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - web-store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: web-app
        image: nginx:1.16-alpine

$ kubectl apply -f multinode.yaml
deployment.apps/web-server created

$ kubectl get deployments            
NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     3            3           49m
web-server         1/3     3            1           5m38s

$ kubectl describe -f multinode.yaml
Name:                   web-server
Namespace:              default
CreationTimestamp:      Wed, 04 May 2022 19:19:29 +0900
Labels:                 <none>
Annotations:            deployment.kubernetes.io/revision: 1
Selector:               app=web-store
Replicas:               3 desired | 3 updated | 3 total | 1 available | 2 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=web-store
  Containers:
   web-app:
    Image:        nginx:1.16-alpine
    Port:         <none>
    Host Port:    <none>
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      False   MinimumReplicasUnavailable
  Progressing    True    ReplicaSetUpdated
OldReplicaSets:  <none>
NewReplicaSet:   web-server-778bd9d76d (3/3 replicas created)
Events:
  Type    Reason             Age    From                   Message
  ----    ------             ----   ----                   -------
  Normal  ScalingReplicaSet  5m41s  deployment-controller  Scaled up replica set web-server-778bd9d76d to 3
thaimthaim

最後にserviceをデプロイしてpodにアクセスできるようにする。
以下のmanifest の通りtype: LoadBalaner を利用してデプロイすることで、http://localhost にアクセスすればいつものnginx画面にアクセスできる。

$ cat service.yaml 
apiVersion: v1
kind: Service
metadata:
  name: webapp
spec:
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 80
  type: LoadBalancer

$ kubectl apply -f service.yaml 
service/webapp created

$ kubectl get svc
NAME         TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP      10.96.0.1       <none>        443/TCP        125m
webapp       LoadBalancer   10.102.75.174   localhost     80:31441/TCP   15s

$ curl localhost
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
thaimthaim

続いてDocker Compose V2について。
リリースノートの通り、Docker Desktop 4.7.1では、docker v20.10.14 および Compose v2.4.1 が利用できる。

$ docker version
Client:
 Cloud integration: v1.0.23
 Version:           20.10.14
 API version:       1.41
 Go version:        go1.16.15
 Git commit:        a224086
 Built:             Thu Mar 24 01:49:20 2022
 OS/Arch:           darwin/amd64
 Context:           default
 Experimental:      true

Server: Docker Desktop 4.7.1 (77678)
 Engine:
  Version:          20.10.14
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.16.15
  Git commit:       87a90dc
  Built:            Thu Mar 24 01:46:14 2022
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.5.11
  GitCommit:        3df54a852345ae127d1fa3092b95168e4a88e2f8
 runc:
  Version:          1.0.3
  GitCommit:        v1.0.3-0-gf46b6ba
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

$ docker compose version
Docker Compose version v2.4.1
thaimthaim

Compose V2の仕様変更については Docker Compose V2で変わったdocker-compose.ymlの書き方 がわかりやすい。
この記事に加えて、Compose SpecificationOverview of Docker Composeを参照して確認する。

Docker ComposeCompose Specのリファレンス実装だと述べられているので基本的には両者の仕様は一致するはず?
ただし、dockerのドキュメントを見てもdocker-composeの記載がまだ残っているのでドキュメント含めたCompose V2対応は未完了っぽい。

thaimthaim

Composeファイルの名前

ファイル名として docker-compose.yml などより compose.yaml が優先されるが、もしデフォルトで対応する4つのファイルのうちいずれか複数が存在する場合は警告を出してくれる。
以下では 4つのファイルが存在する場合の実行結果で、4つのファイルを検出したこと、最終的には compose.yaml が選択されたことが警告されている。
一方で、 compose.yaml の替わりに compose.ymldocker-compose.yml のみを配置・利用した場合には警告等は表示されない。

$ docker compose ps
WARN[0000] Found multiple config files with supported names: /Users/thaim/work/compose-v2/compose.yaml, /Users/thaim/work/compose-v2/compose.yml, /Users/thaim/work/compose-v2/docker-compose.yml, /Users/thaim/work/compose-v2/docker-compose.yaml 
WARN[0000] Using /Users/thaim/work/compose-v2/compose.yaml 
NAME                 COMMAND                  SERVICE             STATUS              PORTS
compose-v2-redis-1   "docker-entrypoint.s…"   redis               running             6379/tcp
compose-v2-web-1     "flask run"              web                 running             0.0.0.0:8000->5000/tcp
thaimthaim

version

versionの項目は無視されるので、Compose V1(従来のdocker-compose)ではエラーになる version: "10.0"version: "latest" の指定があっても警告もなく無視される。

$ head -1 compose.yaml
version: "latest"

$ docker compose ps
NAME                 COMMAND                  SERVICE             STATUS              PORTS
compose-v2-redis-1   "docker-entrypoint.s…"   redis               running             6379/tcp
compose-v2-web-1     "flask run"              web                 running             0.0.0.0:8000->5000/tcp

compose.yamlでversionが指定できなくなり、替わりにdocker compose CLIは常に最新のCompose Specバージョンとして扱わなければならず(but prefer the most recent schema at the time it has been designed)、意図的に古いCompose Specバージョンを指定できなくなった。docker compose CLIが扱うCompose Specよりも新しいバージョンのcompose.yamlが渡された場合など、未定義の項目が存在する場合は警告を出すべき(SHOULD)であり、実行モードを指定してエラー扱いにしたり無視したりしてもよい(MAY)となっている (Requirements and optional attributes)。

Dockerでは実行モードを切り替えるオプションは見当たらず、未定義のCompose Specを指定するとエラーになるのでstrictモードで動作しているように見える。これはCompose Specの仕様とは合致しない動作だがよいのだろうか?

$ head -1 compose.yaml
myspecversion: "1.0"

$ docker compose ps
(root) Additional property myspecversion is not allowed
thaimthaim

configs

おそらく、AWS ECS FireLensにおける設定ファイルのS3マウントみたいな事がしたいのだと思う。

Docker Desktopでの挙動について、マウントしたファイルモードは書き込み権限は無視される (Writable bit MUST be ignored)とあるが、実際には以下の動作であることがわかった。これが意図した挙動なのか、不具合なのか、外部のプラットフォームではどうなるかは不明。

  • 表示はホスト上の権限(ファイルモード)1と同じ
  • composeで指定のmodeやホストのファイルモードによらず、ファイル更新は不可
  • ファイルの実行権限はホストのファイルモード依存、composeのmode指定は無視される(ホストで実行権限を付与するとコンテナ上でも実行できる)
$ cat compose.yaml
services:
  web:
    build: .
    configs:
      - source: my_config
        target: /my_config.txt
        mode: 0555
    ports:
      - "8000:5000"
  redis:
    image: "redis:alpine"
configs:
  my_config:
    file: ./my_config.txt

$ ls -al my_config.txt 
-rw-r--r--  1 thaim  staff  11  5  5 14:12 my_config.txt

$ docker compose exec web sh
/code # ls -al /my_config 
-rw-r--r--    1 root     root            11 May  5 05:12 my_config.txt

$ docker compose exec web sh
/code # echo test >> /my_config.txt 
sh: can't create /my_config.txt: Read-only file system

/code # /my_config.txt 
sh: /my_config.txt: Permission denied
thaimthaim

extends

extendsの元となるサービスは別ファイルを利用することもできる。
その場合はfileで指定する。このような構成の場合、 docker compose up -dしても起動するのは webとmyredisだけで、myredisが参照するredisは起動しない。これを利用すれば共通設定だけを定義するcommonサービスを別ファイルに定義して利用する(commonは起動しない)こともできる。

compose.yaml
services:
  web:
    build: .
    configs:
      - source: my_config
        target: /my_config.txt
        uid: "0"
        gid: "0"
        mode: 0555
    ports:
      - "8000:5000"
  myredis:
    extends:
      file: base.yaml
      service: redis
    environment:
      TZ: utc
configs:
  my_config:
    file: ./my_config.txt
base.yaml
services:
  redis:
    image: "redis:alpine"

その他、depends_onのような依存関係を定義したserviceをextends元には利用できない、循環参照は定義できない、fileは相対パスでも絶対パスでも未定義でもよい、extendsするときに上書きするのか追記するのか、といった補足もあり、このあたりは実際に利用するときに検証するなど必要。