【Mac環境】 komposeの代わりにCompose on Kubernetesを使ってdocker→kubernetes変換する方法

#記事の概要

最近業務でdocker環境をkubernetes環境に移行することなり、
試行錯誤したことの備忘録です。

docker-compose.yamlをkubernetesの各種yamlに変換するには、
メジャーな選択肢として、komposeを使う方法があるようですが、
私の環境では上手く動作するyamlが作れませんでした。（根気不足）　

そこでmacのDocker Desktopに付属のCompose on Kubernetesを
使ってみたところ、割りとスムーズにyaml変換ができました。

とりあえずkubenetes環境を動かせるyamlを手に入れられたことは、
作業の進捗とストレスの軽減に大いに寄与したので、今回はこの手順をまとめておきたいと思います。

> 7/26追記
その後、多少理解が進んで、追記を入れました。
永続ボリュームについてのyaml記述は、私の理解が不足しており、
とりあえず動かすというこの記事の目的にはそぐわないので削除しました。
まだ本番環境での運用に至っておりませんので、MySQLを永続化できてなくても間に合ってます…。
また理解が深まったら追記するか、別記事にあげようと思います。


##docker環境の準備
まずはdocker, docker-composeの環境があることが前提です。
また、mac版の[DockerDesktop](
https://docs.docker.com/docker-for-mac/install/)を使いますので、導入済みであることを前提に話を進めます。

今回は、membersedge社さんの記事から[Laravelのdocker環境](https://www.membersedge.co.jp/blog/laravel-development-environment-with-docker-compose/
)を、docker-compose.yamlのサンプルとして使用させて頂きました。（良記事のご提供をありがとうございます:pray:）
Laravel+docker環境構築手順、docker-compose.yaml記述の手順は本記事では触れませんので、ご了承ください。

###ビルド済みimageの準備
kubenetesにimage buildの機能はないため、docker側で済ませておく必要があります。

```yaml:docker-compose.yamlのビルド指定
app:
    build: ./docker/php
    volumes:
      - .:/var/www/html
```

```shell:docker-composeでimageビルド

docker-compose build
...
Successfully built 60a89dd016b0
Successfully tagged docker-compose-laravel-laravel_app:latest
```

###imageをリポジトリに入れる
今回は、ローカルリポジトリを作成して、そこにpushします。
詳しい手順は[こちらを参照](https://docs.docker.com/registry/)

```shell:ローカルリポジトリを作成し、imageをpush
docker run -d -p 5000:5000 --name registry registry:2
docker image tag docker-compose-laravel-laravel_app localhost:5000/docker-compose-laravel-laravel_app
docker push localhost:5000/docker-compose-laravel-laravel_app
#確認
curl http://localhost:5000/v2/_catalog
#作業が終わった後の消し方
docker container stop registry && docker container rm -v registry
```

ローカルリポジトリができたら、docker-compose.yaml内で
build指定をローカルリポジトリの参照に変更します。
> ※7/26追記
開発環境がローカル以外にあって、外部リポジトリサービスが使えるなら、
ローカル時点からそれを利用した方が便利でした。


```yaml:docker-compose.yamlでビルド指定をimage参照に
app:
    #build: ./docker/php
    image: localhost:5000/docker-compose-laravel-laravel_app
    volumes:
      - .:/var/www/html
```

###kubenetesの利用開始
kubnetesを使い始めるので、DockerDeskTopのGUIで、kubenetes機能を有効にしておきます。
（Preference→kubenetes→Enable Kubernetes）
しばらく待つとkubnetesが利用できるようになり、
kubectlでserviceのステータスを見るとkubernetesのノードが起動しているとわかります。

```shell:kubenetesのステータス確認
kubectl get services
NAME                 TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   3h7m
```

ここからが本題で
docker stack deployコマンドで、docker-compose.ymlからkubnetesを起動します。

> ※追記
docker stack deployコマンドの最後のappは任意のstack名です。
deploy後は、docker stack listで作成したstackを確認できます。
また、例では--namespace=testと、ネームスペースをtestに指定しています。
ネームスペースを指定する（default以外にする）場合、
docker stackコマンドでは--namespaceオプション
kubectlコマンドで-nオプションと
以後何をするにも、ネームスペースの指定が必要なことに注意してください。

```shell:
kubectl create namespace test
docker stack deploy --orchestrator=kubernetes --namespace=test -c docker-compose.yml app

app: Ready		[pod status: 1/1 ready, 0/1 pending, 0/1 failed]
web: Ready		[pod status: 1/1 ready, 0/1 pending, 0/1 failed]
mysql: Ready		[pod status: 1/1 ready, 0/1 pending, 0/1 failed]
```

続いて、service, pod, deploymentの、各構成要素を確認。

```shell:起動確認
kubectl get services,pods,deployment -n test

NAME                      TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/app               ClusterIP      None            <none>        55555/TCP        8s
service/kubernetes        ClusterIP      10.96.0.1       <none>        443/TCP          3h11m
service/mysql             ClusterIP      None            <none>        55555/TCP        8s
service/mysql-published   LoadBalancer   10.105.55.206   localhost     3306:32600/TCP   8s
service/web               ClusterIP      None            <none>        55555/TCP        9s
service/web-published     LoadBalancer   10.98.109.219   localhost     8000:31960/TCP   8s

NAME                       READY   STATUS    RESTARTS   AGE
pod/app-54b7f8b7db-btspb   1/1     Running   0          9s
pod/mysql-0                1/1     Running   0          8s
pod/web-6b4f4cc76b-xb9fn   1/1     Running   1          9s

NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.extensions/app   1/1     1            1           9s
deployment.extensions/web   1/1     1            1           9s
```

これだけで、mac環境においては、docker環境からkubenetes環境が構築できたことになります:kissing_smiling_eyes:

何だ、じゃあ今時はdocker環境さえあればすぐkubenetes環境ができるんじゃん。
動かすだけなら、<b>kubenetes覚える必要ないじゃん</b>
そんな思考に心を奪われそうになりましたが、最終的にはAmazonLinux上で動かす必要があったので調べたところ、stack deployはDocker DesktopのEnterprise版のみの提供と分かりました:astonished:
まあ、そう美味い話はないですよね。

しかしながら、今回の記事はそこを少し抵抗してみた話になります。
kubenetesは動作中の環境をkubenetes用のyamlにエクスポートする機能があるので、
これを使ってみました。

> ※7/26追記
pod.yamlは、deployment.yamlと内容が重複しており、
一旦なくても動くので、削除しました。
（pod.yamlでのみ、コンテナ初期化処理等が使えるのかも？）


```shell:起動した環境をkubenetesのyamlで取得
mkdir k8s_manifest
kubectl get service -o yaml -n test > k8s_manifest/service.yaml
kubectl get deployment -o yaml -n test > k8s_manifest/deployment.yaml
```

きちんとyamlが出力されました。
Linux環境ではstack deployが使えないといっても、kubenetes本来の環境構築マニフェストのyamlが出力がされたなら、それを使って環境構築できそうなものです。
出力された各yamlから環境起動できるかを試してみます。
その前に、stack deployした環境の終了方法から。

```shell:stackdeployしたkubenetes環境の終了
docker stack rm --orchestrator=kubernetes --namespace=test app
```

そして、各種yamlからのkubenetes環境起動。

```shell:
kubectl apply -f k8s_manifest/
```

結論をいえば上記は起動しませんでした:rage:
ここで、幾つかの箇所を修正することで起動するに至りましたので、
ご紹介しておきたいと思います。
以下のように、
deployment.yaml, serice.yaml内の
ownerReferencesブロックを全削除してください。

```yaml:deployment.yaml
    #ownerReferences:
    #- apiVersion: compose.docker.com/v1alpha3
    #  blockOwnerDeletion: true
    #  controller: true
    #  kind: Stack
    #  name: app
    #  uid: 47a61263-8eda-11ea-9393-025000000001
```

同様にservice.yamlにおいても、
ownerReferencesブロックを全削除してください。

```yaml:service.yaml
    #ownerReferences:
    #- apiVersion: compose.docker.com/v1alpha3
    #  blockOwnerDeletion: true
    #  controller: true
    #  kind: Stack
    #  name: xxx
    #  uid: 26c6de60-b890-4b41-9cac-c9bafa8a43d3
```
namespaceが「default」の場合、
service.yaml内に
マスターノードの定義が含まれるので、それも削除します。
※本記事の手順通りであればnamespaceを「test」にしているので、
マスターノードの定義は現れません。

```yaml:serice.yamlマスターノード定義例

# - apiVersion: v1
#   kind: Service
#   metadata:
#    creationTimestamp: "2020-05-05T10:59:51Z"
#    labels:
#      component: apiserver
#      provider: kubernetes
#    name: kubernetes
#    namespace: default
#    resourceVersion: "426818"
#    selfLink: /api/v1/namespaces/default/services/kubernetes
#    uid: 2312e5e3-ab09-4c1a-ae86-abd4df9876cb
#  spec:
#    clusterIP: 10.96.0.1
#    ports:
#    - name: https
#      port: 443
#      protocol: TCP
#      targetPort: 6443
#    sessionAffinity: None
#    type: ClusterIP
#  status:
#    loadBalancer: {}
```

次に、service.yaml内の
spec.typeが「LoadBalancer」の箇所で、
spec.clusterIPとspec.NodePortを削除してください。
これらを固定にしておくと、他クラスタ割当のものと衝突する場合がありました。
※spec.typeがClusterIPの箇所は削除しません。
クラスタ内でのDNS参照（headlessサービス等）に必要なようです。

```yaml:serice.yaml
spec:
    #clusterIP: 10.105.151.173（削除）
    externalTrafficPolicy: Cluster
    ports:
    - name: 8000-tcp
      #nodePort: 31427（削除）
      port: 8000
      protocol: TCP
      targetPort: 80
    selector:
      com.docker.service.id: xxx
      com.docker.service.name: xxx
      com.docker.stack.namespace: xxx
    sessionAffinity: None
    type: LoadBalancer
```

deployment.yamlとservice.yamlの各ブロックから
「resourceVersion」と「uid」も削除してください。
残しておくと、更新時の管理が難しくなりました。
固定値はなるべく消して、シンプルにすると良い感じです。

あとはEKSの開発環境ではhostPathが使えないので、とりあえずvolumesの指定ごと削除しています。

```yaml:deployment.yaml

spec.template.spec.containers以下
#volumeMounts:
#  - mountPath: /var/www/html
#    name: mount-0
#    subPath: src
#volumes:
#  - hostPath:
#    path: /Users/xxx/workspace
#    type: ""
#  name: mount-0
```


まだ他に無駄な箇所があるかもしれませんが、一旦以上です。


修正後に、改めて起動

```shell:
kubectl apply -f k8s_manifest/
```

私の環境ではこれで起動しました。
本来のkubenetesのマニフェストから動かすことができ、docker環境からkubenetes環境への移行が叶いました:joy:
後はkubectl execでpodにログインして、Laravel等の設定を行うのみです。


これからkubenetesに触れられる方にとって、この記事が少しでも手助けになることができましたら幸いです。