イメージビルドせずにKubernetes機能だけでがんばる

# イメージビルド(Dockerfile)の何がいやなのか
* Dockerfile自体の管理をしないといけない
* Dockerfileとイメージの対応を管理しないといけない
* イメージにアップデートが必要な場合、そのイメージをBASEにしているイメージもビルドしなおす必要が出てくる
  * イメージ継承が多いと、再ビルド作業が連鎖で発生していく

# Kubernetesの機能でだいたい出来るんじゃないか？疑惑
* Dockerfileの次のような使い方をKubernetesの機能で置き換えていきます
  * RUNでアプリの追加インストール(apt, yum)
  * ADD,COPYでスクリプトを入れる
  * CMD, ENTRYPOINTで起動時のコマンド変更
  * USERで実行ユーザー・グループを変える 
* 次の使い方は置き換え断念します
  * RUNでコンパイル
      * Kubernetesの機能でやるとPod起動時にコンパイルすることになります。起動が遅くなります
      * コンパイル済みのアプリを組み込んだイメージを作成するしかなさそうです

## 「RUNでアプリの追加インストール」の置き換え
* イメージ内にインストールするのではなく、インストール済みのイメージを組み合わせるといけそうです
  1. KubernetesのPodには複数のコンテナを含めることが出来ます
      * [サイドカーパターン](https://qiita.com/MahoTakara/items/03fc0afe29379026c1f3)と呼ばれるものです。
      * この機能を使えば、 アプリのインストールをコンテナの組み合わせに置き換え出来そうです
          ![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/b14c07c2-f2cf-d036-e7e4-f130d46962a0.png)
  2. KubernetesのInit Containers機能を使うと、コンテナAで処理実行→コンテナBで処理実行→・・・といったフローを組むことが出来ます
      * サーバー起動の前処理で何かコマンド実行したいけど、そのコマンドがイメージに入っていない、という場合に使えそうです
      * 実行結果の受け渡しには、EmptyDirという機能が使えます。
          * EmptyDirはPod作成時に作られ、Podを消すと消える一時ボリュームです。
          * Pod内の各コンテナでマウントすることでコンテナ間のデータ受け渡しに使えます。 
        ![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/d29fb793-4e2d-1cc7-99f7-7aaef0663460.png)

                 
### サイドカーパターンの補足

![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/d29788e0-5dcb-e505-f4bc-06a1a0144224.png)

* 別々のコンテナがネットワークスタック(NIC、loopback interface等)を共有します
   * 各コンテナのアプリの待ち受けポートは、localhostの待ち受けポートとして相互通信できます
   * container1がポート80で待ち受ける場合、container2から`localhost:80`でcontainer1にアクセスできます
       * Pod内のコンテナ同士で待ち受けポートが被らないようにしないといけません   
       * これだけだとファイルシステムが分離されていてコンテナ間でファイル共有ができません。
          * EmptyDirを作ることで共有ボリュームを持てます
                     
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/a3064475-6072-a36c-349f-cc8aa943ec37.png)

## 「ADD,COPYでスクリプトを入れる」の置き換え
* configMapでスクリプトファイルを作ってしまいましょう
  * configMap自体はその名の通り、設定ファイルを配布するための機能です
  * 実態としてはテキストファイルを配布する機能なので、スクリプトを配布するのにも使えます 

## 「CMD, ENTRYPOINTで起動時のコマンド変更」の置き換え
* これはPodのYAMLでコマンドを指定すればいいでしょう

## 「USERで実行ユーザー・グループを変える」の置き換え
* これは`securityContext`の`runAsUser`, `runAsGroup`, `supplementalGroups`を使えばいけそうです
  * 注意： Dockerの仕様上、イメージの中に`runAsGroup`で指定したグループが存在しない時はrootグループで実行されます
    * その場合は起動時に、`groupadd`してから`runuser`コマンドを実行してやればいいと思います 

# 実際にやってみる
## Tensorboardにbasic認証をつけてみる
* TensorboardはDeep Learningの結果をグラフで表示してくれるwebサーバーです 
* [Tensorboardは認証をサポートしません](https://github.com/tensorflow/tensorboard/issues/267) ので、basic認証するにはnginx等が必要です
* もしイメージビルドでやるなら、TensorboardイメージへのNginxインストールをRUNでした後に、設定ファイルをcopyすることになります
* イメージビルドの代わりに、解説したサイドカーパターンを使ってみます
    * TensorboardイメージとNginxイメージを使います
    * あとはKubernetesのyamlで実装してみます

### 各YAML
```yaml:nginx-config.yml
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-config
data:
  auth.conf: |
    server {
     listen 80 default_server;
    
     location / {
         auth_basic              "Restricted";
         auth_basic_user_file    auth.htpasswd;
    
         proxy_pass                          http://localhost:6006;
         proxy_read_timeout                  900;
     }
    }
  auth.htpasswd: |
    foo:$apr1$odHl5EJN$KbxMfo86Qdve2FH4owePn.
```
```yaml:tensorboard-basic-auth.yml
apiVersion: v1
kind: Pod
metadata:
  name: tensorboard-basic-auth
  labels:
    app: tensorboard-basic-auth
spec:
  volumes:
    - name: config-volume
      configMap:
        name: nginx-config
  containers:
  - image: tensorflow/tensorflow:1.12.0-py3
    name: tensorflow
    command: ["/usr/local/bin/tensorboard", "--logdir", "path/to/logs"]
  - image: nginx:alpine
    name: nginx
    ports:
      - name: nginx
        containerPort: 80
    volumeMounts:
      - name: config-volume
        mountPath: /tmp/config
    command:
      - sh
      - "-c"
      - |
          cp /tmp/config/auth.conf /etc/nginx/conf.d/auth.conf
          cp /tmp/config/auth.htpasswd /etc/nginx/auth.htpasswd
          rm /etc/nginx/conf.d/default.conf
          nginx -g "daemon off;"
```
```yaml:tensorboard-service.yml
apiVersion: v1
kind: Service
metadata:
  name: tensorboard-basic-auth
  labels:
    app: tensorboard-basic-auth
spec:
  type: NodePort
  ports:
  - name: nginx
    port: 80
    nodePort: 30036
  selector:
    app: tensorboard-basic-auth
```

* 備考
  * [ここ](https://github.com/beevelop/docker-nginx-basic-auth/)を参考に作りました
  * Nginxの設定ファイルの解説は[ここ](https://qiita.com/kotarella1110/items/be76b17cdbe61ff7b5ca) にあります
  * 上記のyamlの場合は「foo/bar」でログインできます
  * configMapで2つの設定ファイル`auth.conf`、`auth.htpasswd`を定義しています
    * それらを`/tmp/config`にマウントしたのち、cpで本来置くべき場所に配置しています
      * configMapのマウントはReadOnlyなので、cpをmvにすると失敗します
      * 直接配置先にマウントさせることもできるはずですが、YAMLの記述が増えるので今回はやっていません
      * `localhost:6006`の6006はTensorboardコンテナのデフォルト待ち受けポートです
  * Tensorboardについて、本来はyaml中にある「path/to/logs」の部分に学習ログを置く必要がありますが、今回は省略しています 
  * その他いろいろ省略しています
      * https対応
      * 認証情報部分はConfigMapよりSecretの方がいい  

### 作成
* `kubectl create -f nginx-config.yml`
* `kubectl create -f tensorboard-basic-auth.yml`
* `kubectl create -f tensorboard-service.yml`

### 動作確認
![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/f69b547a-28e4-9b60-cd28-b232e1c9f7eb.png)

* basic認証画面が出てきます
* foo/bar以外を入れると認証失敗します

![image.png](https://qiita-image-store.s3.amazonaws.com/0/304012/7b274bf9-df06-079b-da5e-86a12ce0a385.png)

* foo/barを入れるとtensorboardが表示されます

## その他の使い方の参考
* init containers
  * [Kubernetes 初期化専用コンテナで、ポッド内共有ボリュームにデータを取り込む](https://qiita.com/MahoTakara/items/200362276a7cc1942e13) がInitContainerの参考になります
      * InitContainerとgit cloneは[公式](https://kubernetes.io/docs/concepts/storage/volumes/#gitrepo)でも言及されている使い方です
      * [gitのコンテナ](https://hub.docker.com/r/alpine/git/)はここでも拾えます
  * [mysqlクラスタの初期化](https://kubernetes.io/docs/tasks/run-application/run-replicated-stateful-application/)  
* ConfigMapのスクリプト実行
  * [Kubernetes Log Management using Fluentd as a Sidecar Container and preStop Lifecycle Hook- Part IV](https://blog.powerupcloud.com/kubernetes-pod-management-using-fluentd-as-a-sidecar-container-and-prestop-lifecycle-hook-part-iv-428b5f4f7fc7)
      * configMapでprestop.shというスクリプトが出てきます 
* ユーザー変更 
  * [Kubernetes security context, security policy, and network policy – Kubernetes security guide (part 2).](https://sysdig.com/blog/kubernetes-security-psp-network-policy/)

# まとめ
* コンパイル以外のDocker build作業はだいたいなくせそうです
* Docker buildをなくしたいかどうかは個人の趣味によりそうですが、私はあまりビルドしたくありません
  * 冒頭に書いた理由
  * kubernetes上で設定ファイルの確認・変更がしやすいです
      * 設定ファイル変更のたびにビルドとかしなくて済みます
* デメリットとしてイメージを探す作業は生じますが。。