kubernetesの管理を少しでも楽にしたい


Kubernetes2 Advent Calendar 2018 3日目の記事です.
# はじめに
kubernetesのマニフェストを管理するときに一人でapplyをしていたが,サービスが増えてくると管理が難しくなってきた.特にservice.yamlだけapplyするのを忘れることがあったり,どれをapplyしたかわからなくなったり...etc
その問題を少しでも解決するためにkustomizeとgitlab-ciを使う.
もっといいフローやツールも必ずあるだろう,改善すべきところもあるだろう.
今回は私が今,学生のチーム開発でkubernetesのマニフェストを管理するときにやている解決策を紹介する.

# 問題はなにか?
- kubernetesのマニフェストを複数書くのが辛い
- kubernetesへの手動デプロイが辛い

# 解決策
上の２つの問題を解決する
## kubernetesのマニフェストを書くのが辛い
この見出しは[過去記事](https://qiita.com/aeleniumfor/items/0860d6dcf445a74280fa)を少しアレンジしたものになります
kustomizeはもとのyamlからパッチを当てるように変更を加えることができるツールである
今回はyamlを抽象化したものをbaseディレクトリ配下において実際にkubernetesに反映させるものに具体的に書いていくようにしていこうと思う.

ディレクトリ構成は以下のようになる

```bash
$ tree
.
|-- README.md
|-- k8s_base
|   |-- deployment.yaml
|   |-- kustomization.yaml
|   `-- service.yaml
|-- service1
|   |-- Dockerfile
|   |-- k8s_overlay
|   |   |-- containers.yaml
|   |   |-- kustomization.yaml
|   |   `-- replicas.yaml
|   `-- service1.go
`-- service2
    |-- Dockerfile
    |-- k8s_overlay
    |   |-- containers.yaml
    |   |-- kustomization.yaml
    |   `-- replicas.yaml
    `-- service2.go
```

### ベースとなるマニフェストを作成する
k8_baseにはマニフェストを抽象化したものが入っている
以下の例ではk8s_base配下のファイルをまとめて書いている

```yaml:k8s_base/*.yaml
# kustomization.yaml
resources:
  - service.yaml
  - deployment.yaml

--
# deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: sample
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: sample
    spec:
      imagePullSecrets:
        - name: gitlab
--
# service.yaml
apiVersion: v1
kind: Service
metadata:
  name: sample
  labels:
    app: sample
spec:
  type: LoadBalancer
  ports:
    - port: 8080
      protocol: TCP
      targetPort: 80
  selector:
    app: sample
```
実際に使うときはファイルを分けて使う
ちなみに`imagePullSecrets`がgitlabになっているのは私がgitlabで開発することが多いからである
githubなどを使うときは消したほうがいいだろう

これでベースとなるファイルを作成することができた
次にservice1/k8s_overlay配下を見ていく
### 具体的なマニフェストを作成する
```yaml:service1/k8s_service/*yaml

# kustomization.yaml
bases:
  - ../../k8s_base

namePrefix: service1-

commonLabels:
  app: service1-sample

patches:
  - containers.yaml
  - replicas.yaml

configMapGenerator:
  - name: app-conf
    literals:
      - PORT=8080
--
# containers.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: sample
spec:
  template:
    spec:
      containers:
        - name: service1
          image: registry.gitlab.com/sample/service1:latest
          ports:
            - containerPort: 8080
        - envFrom:
            - configMapRef:
                name: app-conf
--
# replicas.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: sample
spec:
  replicas: 1
```
上の例のようにdeployment,service,kustomization.yamlを記述する
次にビルドをする
`$ kustomize build service1/k8s_overlay/`するとビルドされる

```yaml
$ kustomize build service1/k8s_overlay/
apiVersion: v1
data:
  PORT: "8080"
kind: ConfigMap
metadata:
  creationTimestamp: null
  labels:
    app: service1-sample
  name: service1-app-conf-httk99d7g5
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: service1-sample
  name: service1-sample
spec:
  ports:
  - port: 8080
    protocol: TCP
    targetPort: 80
  selector:
    app: service1-sample
  type: LoadBalancer
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    app: service1-sample
  name: service1-sample
spec:
  replicas: 1
  selector:
    matchLabels:
      app: service1-sample
  template:
    metadata:
      labels:
        app: service1-sample
    spec:
      containers:
      - image: registry.gitlab.com/sample/service1:latest
        name: service1
        ports:
        - containerPort: 8080
      - envFrom:
        - configMapRef:
            name: service1-app-conf-httk99d7g5
      imagePullSecrets:
      - name: gitlab
```
これでservice1はビルドすることができた.
同様にservice2の方もkustomizeのマニフェストを書いて上げれば
同じようなマニフェストを出力することができる
ちなみに`name: service1-app-conf-httk99d7g5`のランダムな文字列は`configMapGenerator`の中身を編集すると別の文字列が追加される.
そうすると`kubectl apply`するときに環境変数なども変更させることができる
多分secretもできて`imagePullSecrets:`もkustomizeで管理できると思う.
### kubernetesにデプロイする
kubernetesにデプロイするには

```bash
$ kustomize build service1/k8s_overlay/ | kubectl apply -f - --prune -l app=service1-sample
```
お好みで`-n`をつけてあげるといいと思う.
(実は--pruneの挙動をあまり理解できてないのでおまじない的な感じになっている)

これで

- コンテナの`image`を変更したいときはcontainers.yamlを変更
- 環境変数を追加,編集したいときはkustomization.yamlの`configMapGenerator`に記述
- 複数のPodの数を変更したいときはreplicas.yamlの`replicas`を変更  

をやったあとに`kustomize build`と`kubectl apply`をやれば変更することができる

### まとめ
抽象度を上げることにより複数のサービス適用させることができる.
もちろん本来の使い方である開発環境,本番環境というふうに使うこともいいだろう

## kubernetesへの手動デプロイが辛い
今までのことは[過去記事](https://qiita.com/aeleniumfor/items/0860d6dcf445a74280fa)や[kustomizeのgithub](https://github.com/kubernetes-sigs/kustomize)を見るとわかると思う
yamlの記述量は減らすことができたのではないだろうか?
ちなみにkubernetesのマニフェストは一つにまとめられるのでkustomizeを使うのであれば
service.yamlだけapplyするのを忘れることは無くなると思う.
それでもサービスが多くなるとapplyするのが辛くなってくるだろう
そこでCI時にapplyしてしまえば解決するのではないだろうか?
これは[kustomizeのworkflows](https://github.com/kubernetes-sigs/kustomize/blob/master/docs/workflows.md)に例が書いてある.
今回はこちらを参考にデプロイ作業をできるだけ楽になるようにしてみた
gitlab-ciを使用してci時にデプロイするようにしたい

### 今回使うもの
- gitlab
- gcloud
- gke
以上の3つを使う

gcloudとgkeに関してはあらかじめapplyできる権限をgitlabのuserに与えておく必要がある.

### .gitlab-ci.yamlでCIを作成する
```yaml:.gitlab-ci.yaml
stages:
  - test
  - deploy
k8s-deploy:
  image: sasenomura/gcloud-kustomize
  stage: deploy
  script:
    - echo $GKE_SERVICE_KEY > key.json
    - gcloud auth activate-service-account $GKE_SERVICE_MAIL --key-file=key.json
    - gcloud config set project $GKE_PROJECT_ID
    - gcloud container clusters get-credentials $GKE_CLUSTER_NAME --zone $GKE_CLUSTER_ZONE
    - kubectl config current-context
    - kustomize build service1/k8s_overlay/ | kubectl apply -f - --prune -l app=service1-sample
  only:
    - master
```
imageに関してはkustomizeが入ったimageをあらかじめ登録しておいて`docker pull`するようにしている
gcloudにアクセスするときに使用するものをあらかじめgitlabの変数に設定しておく
と基本的なことはこんな感じで作成する.
`only:master`でmasterにマージされたときのみ実行されるようになっている

### もう少し応用的なことをやる
実際にはサービスによってプロジェクトが別れているので

```bash
$ tree
.
|-- k8s_base
|   |-- .git
|   |-- deployment.yaml
|   |-- kustomization.yaml
|   `-- service.yaml
|-- service1
|   |-- .git
|   |-- Dockerfile
|   |-- k8s_overlay
|   |   |-- containers.yaml
|   |   |-- kustomization.yaml
|   |   `-- replicas.yaml
|   `-- service1.go
`-- service2
    |-- .git
    |-- Dockerfile
    |-- k8s_overlay
    |   |-- containers.yaml
    |   |-- kustomization.yaml
    |   `-- replicas.yaml
    `-- service2.go
```
のようにそれぞれに
k8s_baseに`.git`もあり,service1,2にもそれぞれ`.git`があるようにしてservice1,2には,.gitlab-ci.yamlがあると便利になると思う.

そうするとservice1,2配下にある.gitlab-ci.yamlは以下のようになった

```yaml:.gitlab-ci.yaml
stages:
  - test
  - deploy
k8s-deploy:
  image: sasenomura/gcloud-kustomize
  stage: deploy
  script:
    - echo $GKE_SERVICE_KEY > key.json
    - gcloud auth activate-service-account $GKE_SERVICE_MAIL --key-file=key.json
    - gcloud config set project $GKE_PROJECT_ID
    - gcloud container clusters get-credentials $GKE_CLUSTER_NAME --zone $GKE_CLUSTER_ZONE
    - kubectl config current-context
    - git clone <k8s_baseのリモートリポジトリ>
    - kustomize build service1/k8s_overlay/ | kubectl apply -f - --prune -l app=service1-sample
  only:
    - master
```
やってることは
1. gcloudにログインしてkubectlを使えるようにする
2. `git clon`eでk8s_baseをもってくる
3. `kustomize build`と`kubectl apply`

# まとめ(考察)
コードの記述量が少なくなると考えたがそれはそれぞれのサービスと,抽象度により変わるので一概に記述量が少なくなるとは言えないと考える

普段コードを書く人はcontainers.yamlだけ見て編集することが可能で,インフラとバックエンド,フロントを分けることができたと考える.

ただkustomizeが想定した使い方とは少し外れているので
今後どんなデメリットがあるのかは考える必要があるだろう

# 追記
ちなみに
ciのユーザがほぼ全ての権限をもっているなら以下のようにしてもいいのではないだろうか?

```yaml
k8s-init:
  image: sasenomura/gcloud-kustomize
  stage: test
  script:
    - echo $GKE_SERVICE_KEY > key.json
    - gcloud auth activate-service-account $GKE_SERVICE_MAIL --key-file=key.json
    - gcloud config set project $GKE_PROJECT_ID
    - gcloud container clusters get-credentials $GKE_CLUSTER_NAME --zone $GKE_CLUSTER_ZONE
    - kubectl config current-context
    - |-
      if test $(kubectl get namespaces | grep $CI_PROJECT_NAME | wc -l) -eq 0; then
        kubectl create namespace $CI_PROJECT_NAME
        kubectl create secret docker-registry gitlab --docker-server=https://registry.gitlab.com/ --docker-username=$CI_REGISTRY_USER --docker-password=$CI_REGISTRY_PASSWORD --docker-email=$CI_REGISTRY_MAIL --namespace=$CI_PROJECT_NAME
      fi
```
ciでnamespaceがあるかどうか確認して,もしなければnamespaceを作成したあとにgitlabレジストリをsecretで登録しておくような処理を行うとnamespaceを作成する手間も省ける.
これを使うかどうかは難しいところ....理由としてはgitlab userにnamespaceを作る権限があるからだ,つまりciのyamlを編集できる人は任意のnamespaceを作成することができる
権限には注意したほうがいい(2回目)

# 参考
- https://github.com/kubernetes-sigs/kustomize
- https://dev.classmethod.jp/cloud/aws/kustomize-yaml-how-to/

ありがとうございました.