Django製CMSを動かすためのk8s環境をdocker for macで構築して実行する

## 前回までのあらすじ
DjangoにCodeRed CMSを投入し、同時にmarkdownも使えるようにしたコンテナ初心者。このアプリケーションを実際に使えるようにするために、コンテナの総本山、k8sでの環境の作成を開始する。

[Qiita Django + Wagtail + Puput + CodeRedCMS + markdownのGit分散型ノベルアプリケーションをつくる: https://qiita.com/GCkurabe/items/a615fff1ce0be64e58ba](https://qiita.com/GCkurabe/items/a615fff1ce0be64e58ba)

## 今回の内容
ついにk8sに着手しました。ここまで一つのVMのなかでプロセスをぶったぎりながらコンテナをつくってきましたが、これをいよいよVM間で冗長構成したりスケーリングできるようにしていきます。もともとGKEかEKSでこのCMSアプリを動かせるようにしたいと考えながらはじめてきて、ついにという感じです。

そして今回から完全にdocker for macへと移行しました。本当ならvagrantベースでのminikubeを使いたかったのですが、あまりにも動作が不安定だったためです。実際にdocker for macで非常に快適に動かせているので、このまま使っていこうとおもっています。

そして、そもそもdjangoでのk8sアプリを三階層CL-SV構成で動かすというものはドキュメントが一切ありませんでした。なのでrailsをk8sで動かすという記事や、書籍などなどを大量に参考にさせていただき、試行錯誤を繰り返しました。結局k8sで動作させるまでにえらい時間がかかってしまいました。

だいぶしんどかったので、他の人たちが楽に使えるように以下のリポジトリで公開しています。冗談みたいなSF小説を載せるためのCMSと位置付けていたので、frying_circusと名付けました。pythonの元ネタ、空飛ぶモンティパイソン(原題:Monty Python's Flying Circus)からきています。

[GitHub: https://github.com/kurawo/frying_circus](https://github.com/kurawo/frying_circus)

Docker-composeでも開発はでき、k8sで検証を行うこともできるという構成になっています。本番環境で使えるようなセキュリティの設定は今はまだしておらず、そのためにimageもローカルからしか取得しないように作っているので、ローカルの閉鎖環境でデプロイをお願いします。

システムアーキテクチャはdocker-composeだと以下のようになります。

![k8sdjango.001.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431414/487e56d6-6cea-df4d-3725-d92f6ed6bfa8.jpeg)


そして今回作成したk8sのシステムアーキテクチャは以下のようになります。GunicornとUpstreamの結合の関係で、NginxとDjangoコンテナをひとつのpodでまとめています。そしてIngressをnginxの前に立てることでスケーリングを行いやすいように準備をしています。三階層というよりは四階層という状態です。

![k8sdjango.002.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/431414/ef0a55a3-f68f-27c8-1df9-7e96ed5c62e8.jpeg)



## なぜ私がk8sを選んだか
今回k8sにしたのは完全に個人で、かつコードベースでアプリ・インフラの開発と運用を少ないコードと手数で即座に行えるようにするためです。

EC2とかのVMでもdocker-compose環境を用意すれば現状と同じ環境をリリースできるのですが、スケーリングの問題が解決できなくなることと、個人で人を雇える金はない、という背景から人を増やして対応というのが到底できそうにありません。

つまりdocker-composeでデプロイしてそのあと人月を消費するくらいなら、いっそ事前にk8sで建ててすぐいじれるようにしておこう、というわけです。

私だってk8sの要件でなければさばけないトラフィックなんてほぼないとは思ってます。いんふるえんざ（誤用）でもありませんし......

しかしそれはそれとして、インフラ周りの対応を疎かにする気にもなれません。どうせDockerでアプリを作っていくならば、いっそわかるところまで全部コンテナベースで構築しておきたい。さらに可能ならそれを誰かに改造したりしてもらいながら使ってもらえたらいい。そういう非常に軽い気持ちで、k8sでも使えるようにしました。気持ちとは裏腹に大変重い対応になりました。

バズワードなわりにk8sの情報は少なく、構築もあまりにしんどいので、社内でもすべてコンテナベースで管理しようみたいな過激な話がない限りはk8sでの導入は今のところはあまり考えてはいません。まずk8s使いどころかコンテナ使いが弊社にはいないからです。しかもLinuxベースですらない弊社ではまずコマンドを教え込むところからスタートしなければなりません。夢のまた夢レベルで大変です。なのでまずは小さな社内ツールをdocker-composeで動かしているのでそれを教材にしていろんな人にLinuxとコンテナのおいしいところを覚えてもらっているところです。Linuxを触れて定時で帰りたい人はぜひ弊社に。ゆるい環境で一緒にLinux布教しましょう。

## Docker for Macの準備
本題に入ります。今回からはdocker for macを使用することとしました。minikubeがうまく動かせなかったりしたためです。

インストールの手順は以下の記事を参考にさせていただきました。ダッシュボードもいれています。これはk8sに慣れないうちはほぼすべての情報がコマンド抜きで確認できるため大変重宝します。

[DockerをMacにインストールしてKubernetes Dashboardにアクセスしたメモ　https://qiita.com/pnpnd1111/items/2140f7941cfe3995fc17](https://qiita.com/pnpnd1111/items/2140f7941cfe3995fc17)

特によく使うURLとコマンドを書いておきます。

ダッシュボード起動コマンド

```
$ kubectl proxy
```

ダッシュボードのURL

[http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/](http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/)

権限情報を確認するコマンド

```
$ kubectl -n kube-system get secret
```

トークンを確認するコマンド

```
$ kubectl -n kube-system describe secret deployment-controller-token-xxxx
```

これでログインできただけでもとても感動しました。execも使えてlogも見えます。よくできています。

## Kompose convertを使ってみようとしたがボリュームマウントで阻まれる

ここで私は以前までカスタマイズを繰り返してきたdocker-compose.ymlを使って不精をしようとしました。具体的にはKomposeと呼ばれるものを使おうとしたんです。Kompse convertでdocker-compose.ymlを変換します。しかしここでトラブル発生。なんとvolume mountがサポートされていません。結構重大な設定はmountするようにしてきたのでこれは大変です。

```
# kompose convert
WARN Volume mount on the host "/vagrant/Distributed-Novel-Application-by-Git/django" isn't supported - ignoring path on the host
WARN Volume mount on the host "/vagrant/Distributed-Novel-Application-by-Git/puput" isn't supported - ignoring path on the host
WARN Volume mount on the host "/vagrant/Distributed-Novel-Application-by-Git/nginx" isn't supported - ignoring path on the host
WARN Volume mount on the host "/vagrant/Distributed-Novel-Application-by-Git/django/cmssite/static" isn't supported
```

警告なのでとりあえずkompose upしましたが起動しません。ファイル群を自力でつけなければ始められないことがわかりました。

ということで、フルスクラッチでk8sのymlファイルを書くことになりました。

## k8sのymlファイル作成

記事を参考に、railsをdjangoに読み替えながら作成しました。

[コピペだけでRailsをKubernetes上で最速で動かすためのガイド。GCP、AWS依存なしでDockerイメージ作成からの一部始終を紹介。 https://qiita.com/ttiger55/items/215cab36da848fba156b](https://qiita.com/ttiger55/items/215cab36da848fba156b)

悲しいことにrailsではなかったため、またdocker for desktopのためコピペまんまというわけにもいきませんでした。主にdjangoコンテナの起動時にgunicornコマンドを書き違えていて起動しないとか、nginxへのファイル引き渡しを調べないといけないとか、ingressを使えるようにする方法がdocker for desktopだと変わってしまうとか本当にいろんなことがありました。そのためひたすら地雷を踏みまくって最終的にはじめのリポジトリができています。簡単な解説はデプロイ方法と交えながら説明していきます。

## デプロイ：Docker-composeでのイメージ作成と確認

まず手始めにpycmsというフォルダ名でgit cloneします。これは後々のdockerイメージ名を一致させるためです。

```
$ git clone https://github.com/kurawo/frying_circus.git pycms
```

その後、cloneしたフォルダに入り、事前にローカルでdjango用のベースイメージを作成します。

```
$ docker build -t django2.1 -f ./django/Dockerfile.base ./django
```

完了したらdocker-composeを使用してイメージのビルドと起動確認を行います。

```
$ docker-compose up --build -d
```

無事起動できたら、以下のコマンドでdjangoコンテナの中に入ります。

```
[vm]$ docker-compose exec django /bin/bash
[ctr]$
```

その後以下のコマンドを実行して、コンテナをwebサイトとして使用できるようにします。

```
[ctr]$ cd cmssite
[ctr]$ python manage.py migrate
[ctr]$ python manage.py collectstatic
[ctr]$ python manage.py createsuperuser
```

完了したら、以下のURLから動作を確認してみてください。

```
http://localhost
```

また、admin画面は以下のURLで移動します。

```
http://localhost/admin
```

ここまで触ってみて問題ないことが確認できれば、以下のコマンドでdocker-composeを終了します。

```
$ docker-compose down
```


## デプロイ：k8sのymlファイルapply

まずsampleというnamespaceを指定します。以後はsampleというくくりで確認やコマンドの入力などが行えるようになります。逆に言えば確認の時はsampleという指定がほぼ必須となります。

```
$ kubectl apply -f k8s/namespace/
```

sample.yml

```
apiVersion: v1
kind: Namespace
metadata:
  name: sample
```

postgres用の永続化ボリュームも用意します。現在は3GBと非常に小さめです。

```
$ kubectl apply -f k8s/volumes/postgres.yml
```

postgres.yml

```
kind: PersistentVolume
apiVersion: v1
metadata:
  namespace: sample
  name: postgres-pv
  labels:
    type: local
spec:
  capacity:
    storage: 3Gi
  accessModes:
  - ReadWriteOnce
  storageClassName: standard
  hostPath:
    path: "/tmp/postgres"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  namespace: sample
  name: postgres-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi
  storageClassName: standard
```

次に、postgresやdjangoで使うconfigmapを定義します。これは以前から使っているdocker-compose.ymlと設定は同じです。

```
$ kubectl apply -f k8s/config/django.yml
```

k8s/config/django.yml

```
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: sample
  name: django-config
data:
  DJANGO_ENV: 'development'
  PSQL_HOST: 'postgres'
  PSQL_USER: 'root'
  PSQL_ROOT_PASSWORD: 'hogemojahogemoja'
```

今度はJobを登録します。Jobはpython manage.py migrateをやってもらうために用意しています。

```
$ kubectl apply -f k8s/settings/django-jobs.yml
```

k8s/settings/django-jobs.yml

```
apiVersion: batch/v1
kind: Job
metadata:
  namespace: sample
  name: setup
spec:
  template:
    metadata:
      name: setup
    spec:
      containers:
      - name: setup
        image: pycms_django
        imagePullPolicy: Never
        args:
          - sh
          - -c
          - "cd cmssite && python manage.py migrate"
        envFrom:
        - configMapRef:
            name: django-config
      restartPolicy: Never
```

nginx用のconfigも起動します。この中にはnginx.confとmime.typesを|と改行を使用して取り込んであり、これが最終的にpodにマウントされるようになっています。

```
$ kubectl apply -f k8s/config/nginx.yml
```

k8s/config/nginx.yml

```
apiVersion: v1
kind: ConfigMap
metadata:
  namespace: sample
  name: nginx-config
data:
  nginx.conf: |
  #以下省略
```


さらに、以下のディレクトリに入ってnginx用のDockerfileを実行して新たなnginxイメージを作成します。これはnginxに使用する静的ファイルを引き渡すためです。k8sは基本的にvolumeをマウントできないため、こうしてありとあらゆる方法を使用してk8sで起動できるようにする必要があるのです。

```
$ cd django/cmssite
$ docker build -t nginxstaticplus -f Dockerfilenginx .
```


ここで、ついにnginxとdjangoが同一podとなっているものを立ち上げます。Djangoではgunicornを使用して3031ポートで起動し、nginxではnginx.confをもとに3031ポートをupstreamで立ち上げて8080ポートに引き渡します。

またdjangoもnginx同様にデプロイ後のローカルのイメージをそのまま活用し、imageの名前はpycms_djangoとしています。もしもうまく取得できなかった時はdjangoのimageの名前をpycms_djangoにしてから再度実行をお願いします。


クラウドで使用する場合にはDockerHubにあげるなりなんなりする必要があります。

```
$ kubectl apply -f k8s/settings/nginxdjango.yml
```

k8s/settings/nginxdjango.yml

```
apiVersion: v1
kind: Service
metadata:
  namespace: sample
  name: nginxdjango
spec:
  selector:
    app: nginxdjango
  type: NodePort
  ports:
  - port: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: sample
  name: nginxdjango
spec:
  selector:
    matchLabels:
      app: nginxdjango
  template:
    metadata:
      labels:
        app: nginxdjango
    spec:
      containers:
      # django container
      - name: django
        image: pycms_django
        imagePullPolicy: Never
        args:
          - sh
          - -c
          - "cd cmssite && gunicorn cmssite.wsgi -b 0.0.0.0:3031"
        envFrom:
        - configMapRef:
            name: django-config
        ports:
        - containerPort: 3031
      # nginx container
      - name: nginx
        image: nginxstaticplus
        imagePullPolicy: Never
        volumeMounts:
        - mountPath: /etc/nginx # /etc/nginxにvolumesのnginx-confをmountする
          readOnly: true
          name: nginx-config
        - mountPath: /var/log/nginx
          name: log
      volumes:
      - name: nginx-config # volumeMountsで/etc/nginxにmountするやつ
        configMap:
          name: nginx-config # ConfigMapのnginx-configを/etc/nginx以下に配置する
          items:
            - key: nginx.conf # nginx-confのkey
              path: nginx.conf # nginx.confというファイル名
            - key: mime.types
              path: mime.types
      - name: log
        emptyDir: {}

```

最後にingressを用意します。ingressは以下のコマンドでインストールしますが、helmが必要なため事前に用意した後でこのコマンドを実行してください。

```
$ helm install stable/nginx-ingress
```

そして以下のコマンドでingressを起動します。現在は全ての接続を8080ポートにつなげるという仕組みになっており、これでnginxとdjangoのpodと接続することができます。必要であればここにルールを継ぎ足すことでスケーリングが可能となります。

```
$ kubectl apply -f k8s/settings/ingress.yml
```

k8s/settings/ingress.yml

```
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  namespace: sample
  name: ingress
spec:
  rules:
  - host:
    http:
      paths:
      - path: /
        backend:
          serviceName: nginxdjango
          servicePort: 8080

```

最後にローカルホストへアクセスすれば、docker-composeと同様の画面を確認することができます。

[http://localhost](http://localhost)


## おわりに

なんとかk8sでアプリケーションを動かすことができるようになりました。本当に大変でした。しかしこれをプレーンなVMで作ろうと思ったらスケーリングの段になった瞬間一切手足が出なくなっていたと思います。これからはスケーリングや冗長構成などをこのk8sのymlファイルを編集していくことでつくっていくことができます。

次回はアプリケーションの改良を行いつつ、スケーリングや冗長構成を含め、最後にクラウド上にデプロイしていきます。