今度はあんまりゴツくない！？「わりとゴツいKubernetesハンズオン」そのあとに

1月半ほど前、社内勉強会のおすそ分けとして、[わりとゴツいKubernetesハンズオン](https://qiita.com/Kta-M/items/ce475c0063d3d3f36d5d)の記事を書きました。
おかげさまでご好評いただき、今では1500を超えるいいねをいただきました。ありがとうございます。

今回の話とは全然関係ないですが、こちらも頑張って書いているので興味あれば見てやってください。
(マイナーなタグしか付けられなかったせいか、view数がとても少なくて悲しみに暮れています…😢)
[QRコード Deep Dive ーデータ符号化とか誤り訂正とかー](https://qiita.com/Kta-M/items/6f7049a1e78b1fe7e883)

## 開発環境の構築ができるようになろう！
さて、みなさまKubernetesについては完全理解しましたでしょうか？
主要な要素の説明はしているので、完全理解フェーズにたどり着くのも夢ではないと思います。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">自信と理解度の関係を図にしました。<a href="https://twitter.com/hashtag/%E5%AE%8C%E5%85%A8%E3%81%AB%E7%90%86%E8%A7%A3%E3%81%97%E3%81%9F?src=hash&amp;ref_src=twsrc%5Etfw">#完全に理解した</a> <a href="https://t.co/SA638Vy9UH">pic.twitter.com/SA638Vy9UH</a></p>&mdash; 廻転楕円体 (@kaitendaentai) <a href="https://twitter.com/kaitendaentai/status/1052689241744896001?ref_src=twsrc%5Etfw">2018年10月17日</a></blockquote>

ただ、ハンズオンの中では、既存のDockerイメージしか使っていません。
<img width="723" alt="スクリーンショット 2019-07-01 9.38.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39565/02fb9885-89cc-1975-e625-6ffb02057b75.png">

Kubernetesを使ってオリジナルのアプリケーションを構築するには、
Dockerを交えた開発環境の構築という、もうひと山を超えねばなりません。

ここで、前回のハンズオンの目標を見返してみてください。
>   - 構築・運用ができるような気分になる

できるようになるとは言ってない…！(どーん

ということで、前回入れようと思ったけど~~力尽きた~~ゴツくなりすぎると思ってやめておいた、
開発環境の構築について、またハンズオン形式でお伝えしようと思います！

ちなみに、本記事は福岡のKubernetes勉強会「ふくばねてす node-2」での発表資料をQiita用にコンバートしたものです。
発表資料 : https://speakerdeck.com/ktam1219/waritogotuikuberneteshanzuon-sofalseatoni

## やりたいこと
### ローカル開発環境
まずはローカルでの開発環境を作らないことには始まりません。
大まかな開発フローは以下の繰り返しになります。
1. コードを編集
2. Dockerイメージをbuild
3. ローカルのKubernetesクラスターにデプロイ
<img width="707" alt="スクリーンショット 2019-07-01 9.39.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39565/7339c071-f4a1-d10d-f210-2ec1781b609e.png">

### CI/CD環境
本番環境にリリースする際の作業はさらに手順が増えます。
1. コードを編集
2. GitにPush
3. Dockerイメージをbuild
4. コンテナレジストリにDockerイメージをPush
5. Kubernetesクラスターにデプロイ
<img width="722" alt="スクリーンショット 2019-07-01 9.40.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39565/7a6174f1-440e-6bc1-2f56-d674aa9faefa.png">

手作業でやろうとすると大変なので、CircleCIなどを使って自動化したいですね。

以上が今回やりたいことなんですが、
なんだか一気にめんどくさい気持ちになってきましたね。

でも、簡単にできるんです。
そう、Skaffoldならね！

## Skaffold
[Skaffold](https://skaffold.dev/)は、Google謹製のKubernetesの開発支援ツールです。
クラスタにアプリケーションをデプロイするまでの 一連の作業を(ローカル/リモート問わず)自動化してくれます。

- Dockerイメージのbuild
- Dockerイメージの構造テスト
- Dockerイメージへのタグ付け
- コンテナレジストリにDockerイメージをPush
- KubernetesクラスタにDeploy

先ほど挙げたやりたいことに加え、テストやタグ付けまで…！

また、各フェーズは独立していて、プラグイン的にお好みでやり方を選択できます。
![architecture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39565/3ccf5a13-3616-0fc5-aa9a-1e3bc77a3964.png)

## Skaffoldをローカル開発で使ってみよう
### ローカルのKubernetes環境を準備する
前回のハンズオンを参考に、下準備の部分まで終わらせます。
https://qiita.com/Kta-M/items/ce475c0063d3d3f36d5d#kubernetes%E3%82%92%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E3%81%A7%E8%A9%A6%E3%81%97%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86

### サンプルアプリケーションを準備する
前回と同様、[kubernetes/examples](https://github.com/kubernetes/examples)のguestbookアプリを使いましょう。

```bash
$ git clone git@github.com:kubernetes/examples.git
$ mkdir skaffold_example
$ cp -R examples/guestbook/* skaffold_example
$ cd skaffold_example
```

不要なファイルを削除しておきます。

```bash
$ rm -rf all-in-one
$ rm -rf legacy
```

PCの負担を下げるためちょっと調整 & Dockerイメージの取得先を変更します。

```bash
$ vi examples/guestbook/frontend-deployment.yaml
10行目   replicas: 3 <- これを1に変更
19行目   image: gcr.io/google-samples/gb-frontend:v4 <- これを ${dockerhubのユーザー名}/skaffold-example-frontendに変更

$ vi examples/guestbook/redis-slave-deployment.yaml
11行目   replicas: 2 <- これを1に変更
21行目   image: gcr.io/google_samples/gb-redisslave:v1 <- これを ${dockerhubのユーザー名}/skaffold-example-redis-slaveに変更
```

ingressのマニフェストファイルを作成します。

```bash
$ cat << 'EOT' >./guestbook-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: guestbook-ingress
spec:
  rules:
  - http:
      paths:
      - path: /
        backend:
          serviceName: frontend
          servicePort: 80
EOT
```

### Skaffoldのインストール & セットアップ
以下のURLを参考にインストールを行います。
https://skaffold.dev/docs/getting-started/#installing-skaffold

macであればHomebrewで入ります。

```bash
$ brew install skaffold
```

Skaffoldの設定ファイルを作成します。

```skaffold.yaml
apiVersion: skaffold/v1beta11
kind: Config

# Dockerイメージのビルドに関する設定
build:
  artifacts:
  # ビルドするイメージ
  - image: ${dockerhubのユーザー名}/skaffold-example-frontend
    context: php-redis                                            # ソースファイルがあるパスの指定
  - image: ${dockerhubのユーザー名}/skaffold-example-redis-slave
    context: redis-slave
  local:                                                          # ローカルでイメージをビルドするときの設定
    push: false                                                   # ビルドしたイメージをpushするか

# デプロイに関する設定
deploy:
  kubectl:
     # デプロイ対象のマニフェストファイルの指定
    manifests:
    - frontend-*.yaml
    - redis-master-*.yaml
    - redis-slave-*.yaml
    - guestbook-ingress.yaml
```

設定ファイルのその他の項目については、以下を参照してください。いろいろできます。
https://skaffold.dev/docs/references/yaml/

### skaffold dev
さて、ここで以下のコマンドを実行してみましょう。

```bash
$ skaffold dev
````

開発用のコマンドです。
実行後、Dockerイメージのビルド、Kubernetesクラスターへのデプロイをおこないます。
その後はファイルの変更を監視して、変更があるたびにビルド、デプロイを繰り返します。
また、ファイル変更監視中は、各Podのログも流してくれます。いたれりつくせり。

```bash
Generating tags...
 - mohri1219/skaffold-example-frontend -> mohri1219/skaffold-example-frontend:76a6cd0-dirty
 - mohri1219/skaffold-example-redis-slave -> mohri1219/skaffold-example-redis-slave:76a6cd0
Tags generated in 49.700092ms
Starting build...
Found [docker-for-desktop] context, using local docker daemon.
Building [mohri1219/skaffold-example-frontend]...
Sending build context to Docker daemon   7.68kB
Step 1/8 : FROM php:5-apache
 ---> 24c791995c1e

(中略)

Starting deploy...
kubectl client version: 1.14
deployment.apps/frontend created
service/frontend created
ingress.extensions/guestbook-ingress created
deployment.apps/redis-master created
service/redis-master created
deployment.apps/redis-slave created
service/redis-slave created

(中略)

Watching for changes every 1s...
[frontend-7df8f8b7f8-mscxk php-redis] AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 10.1.1.154. Set the 'ServerName' directive globally to suppress this message
```

前回のハンズオン同様、http://localhost にアクセスすると画面が出てきます。
<img width="663" alt="スクリーンショット 2019-07-01 15.30.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39565/f9439aef-1f88-d2cb-65c7-54ae9e73e3dd.png">

ファイルの変更検知機能を試してみるために、おもむろにhtmlファイルを書き換えてみます。

```php-redis/index.html
<h2>Guestbook!!</h2>   <!- "!!"を追加してみる -->
```

すると、ちゃんと変更を検知してDockerイメージのビルドを始めてくれます！

```bash
Generating tags...
 - mohri1219/skaffold-example-frontend -> mohri1219/skaffold-example-frontend:76a6cd0-dirty
Tags generated in 62.953504ms
Starting build...
Found [docker-for-desktop] context, using local docker daemon.
Building [mohri1219/skaffold-example-frontend]...
Sending build context to Docker daemon   7.68kB
Step 1/8 : FROM php:5-apache
 ---> 24c791995c1e
Step 2/8 : RUN pear channel-discover pear.nrk.io
 ---> Using cache
 ---> bd9823d8109d
Step 3/8 : RUN pear install nrk/Predis
 ---> Using cache
 ---> 390dd33a211f
```
 
ブラウザをリロードしてみるとちゃんと変更が反映されていることが確認できます。
<img width="634" alt="スクリーンショット 2019-07-01 15.30.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39565/00440cfe-1f83-1742-f3a8-a2458d189e56.png">

最後に、Ctrl+Cで終了してみましょう
すると、DockerイメージやKubernetesクラスター内の各種リソースを削除してくれます！

```bash
Pruning images...
untagged image mohri1219/skaffold-example-frontend:3eba4d84484d6648b4a0907db8f472e5df4439d9ba4ca012a5b96f95af4dd09b
untagged image mohri1219/skaffold-example-frontend:c4971ec-dirty
untagged image mohri1219/skaffold-example-frontend@sha256:c2f8eb06b6a9d0624c1f8ea6cba4dfaefa450f11d99c90a209617618b0687730
deleted image sha256:3eba4d84484d6648b4a0907db8f472e5df4439d9ba4ca012a5b96f95af4dd09b
deleted image sha256:42794d3d5de90049ae108429e1536b747b145918b9972edd62e958b58b09c1ec
WARN[0290] builder cleanup: pruning images: Error response from daemon: conflict: unable to delete 48d1ae3a57c7 (cannot be forced) - image is being used by running container 0b0107ee518b
Cleaning up...
deployment.apps "frontend" deleted
service "frontend" deleted
ingress.extensions "guestbook-ingress" deleted
deployment.apps "redis-master" deleted
service "redis-master" deleted
deployment.apps "redis-slave" deleted
service "redis-slave" deleted
Cleanup complete in 3.70313407s
```

### skaffold run
さて、次は以下のコマンドを実行してみましょう。

```bash
$ skaffold run
````

これは単発のデプロイコマンドで、Dockerイメージのビルド、Kubernetesクラスターへのデプロイをおこなって終了します。
終了後も各種リソースはKubernetesクラスター内に残り続けます。

```bash
Generating tags...
 - mohri1219/skaffold-example-frontend -> mohri1219/skaffold-example-frontend:76a6cd0-dirty
 - mohri1219/skaffold-example-redis-slave -> mohri1219/skaffold-example-redis-slave:76a6cd0
Tags generated in 48.802686ms
Starting build...
Found [docker-for-desktop] context, using local docker daemon.
Building [mohri1219/skaffold-example-frontend]...
Sending build context to Docker daemon   7.68kB
Step 1/8 : FROM php:5-apache
 ---> 24c791995c1e

(中略)

Starting deploy...
kubectl client version: 1.14
deployment.apps/frontend created
service/frontend created
ingress.extensions/guestbook-ingress created
deployment.apps/redis-master created
service/redis-master created
deployment.apps/redis-slave created
service/redis-slave created
Deploy complete in 1.763257066s
```

### Skaffoldでテストもしてみる
Skaffoldのテストは、[container-structure-test](https://github.com/GoogleContainerTools/container-structure-test)を使います。
その名の通り、コンテナイメージの構造をテストするものです。

できることは、

- イメージ内のコマンドの出力をチェック
- ファイルシステムのメタデータと内容を検証
- etc...

今回は、`skaffold-example-frontend`のファイル存在チェックをしてみましょう。
その他のテスト項目については
https://github.com/GoogleContainerTools/container-structure-test/blob/master/README.md を参照してください。

```structure-test/frontend.yaml
schemaVersion: 2.0.0

# 各種ファイルの存在確認
fileExistenceTests:
  - name: 'php file'
    path: '/var/www/html/guestbook.php'
    shouldExist: true
  - name: 'js file'
    path: '/var/www/html/controllers.js'
    shouldExist: true
  - name: 'html file'
    path: '/var/www/html/index.html'
    shouldExist: true
```

skaffoldの設定ファイルにテストの設定を追記しましょう。

```skaffold.yaml
apiVersion: skaffold/v1beta11
kind: Config

build:
  (略)

# テストの設定
test:
  test:
  - image: ${dockerhubのユーザー名}/skaffold-example-frontend
    structureTests:
      - ./structure-test/frontend.yaml

deploy:
  (略)
```

`skaffold run`を実行すると、以下のようにログが出てきます。
もちろん、テストが失敗するとデプロイはされません。

```bash
$  skaffold run

(中略)

Starting test...

======================================
====== Test file: frontend.yaml ======
======================================
=== RUN: File Existence Test: php file
--- PASS

=== RUN: File Existence Test: js file
--- PASS

=== RUN: File Existence Test: html file
--- PASS


=======================================
=============== RESULTS ===============
=======================================
Passes:      3
Failures:    0
Total tests: 3

PASS
Test complete in 1.455478421s
```

### お片付け
ローカルの開発環境についてはここまで。開発が進められるイメージは持てましたか？

Docker for DesktopのKubernetesを無効化してお片付けをしましょう。
<img width="830" alt="68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f33393536352f39373362343665662d636637652d616437352d623734352d6332666363653339353838652e706e67.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39565/fb6559fa-bccf-9757-f3dd-be4b625456a6.png">


## SkaffoldをCI/CD向けに使ってみよう
完成版はこちらにあります。
https://github.com/Kta-M/skaffold_example

### GitHubのリポジトリを作成
みんなできるだろうから割愛します。

### DockerHubのリポジトリを作成
DockerHubも画面からリポジトリが作れます。
![screencapture-cloud-docker-u-mohri1219-repository-create-2019-06-29-07_08_27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39565/43658dad-4834-9c2c-ee62-d43b3d57dedf.png)

### EKSでクラスタを作成
前回のハンズオンでやったやつです。
相変わらず立てるのに15分ぐらいかかります。とてもつらい。

```bash
$ eksctl create cluster \
  --name eksctl-handson \
  --region ap-northeast-1 \
  --nodes 3 \
  --nodes-min 3 \
  --nodes-max 3 \
  --node-type t2.medium \
  --ssh-public-key <キーペア名>
```

ap-northeast-1bが選択できる古いAWSアカウントはAZ指定行なってください。

### Skaffoldの設定ファイルを調整
先ほどまでローカルでskaffoldを使ってきましたが、本番環境にデプロイする際は以下の点を変えたいです。

- DockerイメージをコンテナレジストリにPushする
- ingressのマニフェストファイルは使わない

こういった、状況に応じて挙動を切り替えるための仕組みとして、profileという機能があります。
実行時にprofileを与えると、それに応じた設定をオーバーライドしてくれる機能です。

```skaffold.yaml
apiVersion: skaffold/v1beta11
kind: Config

(中略)

# 以下を追加
profiles:
- name: prd
  build:
    local:
      push: true                    # ビルドしたイメージをコンテナレジストリにPushする
  deploy:
    kubectl:
      manifests:                    # ingressのファイルを除外
      - frontend-*.yaml
      - redis-master-*.yaml
      - redis-slave-*.yaml
```

以下のように`prd`のprofileを追記しましょう。
こうすることで、 `skaffold run --profile prd`とすればprdの設定でオーバーライドしてくれるようになります。

### CircleCIの設定
CirlceCIにログインし、GitHubリポジトリを登録しましょう。
<img width="1142" alt="スクリーンショット 2019-06-29 6.47.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39565/68e209ec-b986-2e0d-ca30-856143a7e62c.png">
![screencapture-circleci-setup-project-gh-Kta-M-skaffold-example-2019-06-29-06_48_44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39565/7e029006-63ea-5668-28a1-3a61d62baa12.png)


プロジェクト環境変数を設定します。
![screencapture-circleci-gh-Kta-M-skaffold-example-edit-2019-06-30-17_48_07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39565/8790f058-0376-9685-d65e-bb1752d21753.png)

|変数名|値|
|:--|:--|
|AWS_DEFAULT_REGION|ap-northeast-1|
|CUSTER_NAME|eksctl-handson|
|DOCKER_HUB_USR|DockerHubのユーザー名|
|DOCKER_HUB_PSW|DockerHubのパスワードをbase64エンコードしたもの|

EKSにデプロイするため、AWSの認証情報を設定します。
ここで、EKSクラスタを立てるのに使ったIAMユーザーと同じものを設定しなければならないことに注意してください。
KubernetesのRBACとIAMが紐付いてしまうので。
参考: https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/managing-auth.html
![screencapture-circleci-gh-Kta-M-skaffold-example-edit-2019-06-30-17_58_50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39565/c2812f0b-36a2-9ec5-2791-3322791d258b.png)

また、orbを使えと言われてるけど今回は無視します。
orbはバージョン2.1でしか使えないけど、2.1はまだローカルでの動作確認ができないようなので…。とてもつらい。

### CircleCIのconfigを作成
以下のようになります。`name`を見ていけば何をしているのかだいたい分かるかと思います。
本来は、もろもろインストールしたDockerイメージを作ってからやるべきなんでしょうけど、
分かりやすくするためインストールもcircleciで行っています。

```.circleci/config.yml
version: 2
jobs:
  build:
    docker:
      - image: docker:18.09
    steps:
    - run:
        name: set shell and install tools
        command: |
          set -x; \
          apk add --no-cache --virtual .fetch-deps curl git python py-pip less groff

    - checkout
    - setup_remote_docker

    - run:
        name: install kubectl
        command: |
          curl -LO https://storage.googleapis.com/kubernetes-release/release/v1.15.0/bin/linux/amd64/kubectl && \
          chmod +x ./kubectl && \
          mv ./kubectl /usr/local/bin/kubectl

    - run:
        name: install container-structure-test
        command: |
          curl -LO https://storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-amd64 && \
          chmod +x container-structure-test-linux-amd64 && \
          mv container-structure-test-linux-amd64 /usr/local/bin/container-structure-test

    - run:
        name: install aws cli
        command: pip install awscli

    - run:
        name: install aws-iam-authenticator
        command: |
          curl -o aws-iam-authenticator https://amazon-eks.s3-us-west-2.amazonaws.com/1.12.7/2019-03-27/bin/linux/amd64/aws-iam-authenticator && \
          chmod +x ./aws-iam-authenticator && \
          mv ./aws-iam-authenticator /usr/local/bin

    - run:
        name: setup kubeconfig
        command: aws eks update-kubeconfig --name $CLUSTER_NAME

    - run:
        name: login to dockerhub
        command: echo $DOCKER_HUB_PSW | base64 -d | docker login -u $DOCKER_HUB_USR --password-stdin

    - run:
        name: install skaffold
        command: |
          curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/v0.32.0/skaffold-linux-amd64 && \
          chmod +x skaffold && \
          mv skaffold /usr/local/bin

    - run:
        name: skaffold run
        command: skaffold run --profile prd

workflows:
  version: 2
  workflow:
    jobs:
    - build
```

### GitHubにPush
この状態でもろもろをGitHubにPushしてあげると…
CircleCI上で`skaffold run --profile prd`が実行され、もろもろの処理が走ります。

![screencapture-circleci-gh-Kta-M-skaffold-example-10-2019-07-01-06_02_44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39565/1d2f3842-d5d0-50fa-b977-b34c75709be2.png)

```bash
$ kubectl get svc
NAME                   TYPE           CLUSTER-IP       EXTERNAL-IP                               PORT(S)        AGE
service/frontend       LoadBalancer   10.100.61.11     xxxxxx.ap-northeast-1.elb.amazonaws.com   80:31673/TCP   8s
service/kubernetes     ClusterIP      10.100.0.1       <none>                                    443/TCP        23m
service/redis-master   ClusterIP      10.100.137.217   <none>                                    6379/TCP       7s
service/redis-slave    ClusterIP      10.100.217.57    <none>                                    6379/TCP       7s
```

ELBのドメインを確認してアクセスすると、guestbookアプリが動いているのが確認できると思います！

## まとめ
- 「わりとゴツいやつ」から一歩進んで、Kubernetesを使った開発の具体的なイメージが掴めたのであれば幸いです😊
- ただ、我々はKubernetes沼に足を踏み入れたばかり
  - 今回紹介しきれなかった用語、エコシステムなどまだ膨大に…
- 俺たちの真の戦いはこれからだ！