前回までのあらすじ
DjangoにCodeRed CMSを投入し、同時にmarkdownも使えるようにしたコンテナ初心者。このアプリケーションを実際に使えるようにするために、コンテナの総本山、k8sでの環境の作成を開始する。
今回の内容
ついに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
Docker-composeでも開発はでき、k8sで検証を行うこともできるという構成になっています。本番環境で使えるようなセキュリティの設定は今はまだしておらず、そのためにimageもローカルからしか取得しないように作っているので、ローカルの閉鎖環境でデプロイをお願いします。
システムアーキテクチャはdocker-composeだと以下のようになります。
そして今回作成したk8sのシステムアーキテクチャは以下のようになります。GunicornとUpstreamの結合の関係で、NginxとDjangoコンテナをひとつのpodでまとめています。そしてIngressをnginxの前に立てることでスケーリングを行いやすいように準備をしています。三階層というよりは四階層という状態です。
なぜ私が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に慣れないうちはほぼすべての情報がコマンド抜きで確認できるため大変重宝します。
特によく使うURLとコマンドを書いておきます。
ダッシュボード起動コマンド
$ kubectl proxy
ダッシュボードのURL
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ではなかったため、また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と同様の画面を確認することができます。
おわりに
なんとかk8sでアプリケーションを動かすことができるようになりました。本当に大変でした。しかしこれをプレーンなVMで作ろうと思ったらスケーリングの段になった瞬間一切手足が出なくなっていたと思います。これからはスケーリングや冗長構成などをこのk8sのymlファイルを編集していくことでつくっていくことができます。
次回はアプリケーションの改良を行いつつ、スケーリングや冗長構成を含め、最後にクラウド上にデプロイしていきます。