Kubernetesの基礎 〜Docker for Macハンズオン〜

# 概要

参考にしたハンズオン

https://www.nic.ad.jp/ja/materials/iw/2018/proceedings/h2/h2-takara-3.pdf
https://www.nic.ad.jp/ja/materials/iw/2018/proceedings/h2/h2-takara-4.pdf

# Kubernetes とは

Kubernetes は Linux コンテナの操作を自動化するOSSです。「k8s」や「kube」とも呼ばれます。
日本では「クーバネテス」「クーベネティス」などと発音されます。
Kubernetes を使用すると、アプリケーションのデプロイ、スケーリングなどを自動化できます。つまり、Linux コンテナを実行しているホストをまとめてクラスタ化できます。

### 用語集

よく登場する用語をまとめておきます。
ここで全てを覚える必要はなく、ハンズオン中にわからない用語が出てきたらこちらをご参照ください。

Kubernetes を構成する要素

* ポッド(Pod)
ポッドは複数のコンテナから構成されます。ポッド単位でコンテナが起動・破棄されます。また、ポッドは一時的な存在であるため再起動はありません。各ポッドには一意の仮想NICが割り当てられます。また、同じポッド内のコンテナ同士はIPアドレスを共有し、localhost で通信できます。
* ノード(Node)
Dockerコンテナを実行するサーバのことです。複数のポッドを持ちます。
* マスターノード(Master Node)
各ノードの管理を行うノードのことです。
* クラスター(Cluster)
コンテナやネットワークの設定などをクラスタリングしたものです。
* ネームスペース(Namespace)
ネームスペースをつけてクラスターを仮想化できるものです。あらかじめ「default」「docker」「kube-public」などのネームスペースが用意されています。例えば、WEBサーバ群、DBサーバ群、などのような分け方が想定できます。
* コンテキスト(Context)
Kubernetes の環境を分けるための定義です。 クラスター、ネームスペース、ユーザーをグループ化したものです。
* サービス(Service)
ポッドに対してクラスタ外からアクセスするためにはサービスを定義する必要があります。サービスにはクラスタの代表となるIPを指定します。

上記を制御するために登場する要素

* コントローラー
ポッドを制御するためのオブジェクトです。コントローラーには様々な種類があり、特性によって使い分けます。
* デプロイメント(Deployment)
最も良く使われるコントローラーがデプロイメントです。良くあるWEBサーバなどの用途ではこちらを使います。ポッド数をスケールしたり、ロールバックをしたり、求められる基本的な動作をします。
* Service
サービスを定義します。
* ConfigMap
コンテナで使用される設定を定義します。
* Secret
コンテナで使用される秘匿情報を定義します。
* Volume
コンテナに割り当てるストレージを定義します。
* Job
ジョブを定義します。
* CronJob
定期的に実行されるジョブを定義します。
* 

その他の用語

* マニフェスト
Kubernetes の構成を定義するための形式(yamlファイル)のことです。

# Mac で Kubernetes を使うには

Mac で Kubernetes を使うには、以下の方法があります。
それぞれメリット・デメリットがあります。

* Docker for Mac の Kubernetes を使う
  * 既に Docker for Mac がインストールされている場合は最速の方法です。 
  * Docker for Mac 自体が重いという話があり、パフォーマンスが懸念されます。
  * また、Kubernetes の構成に限りがあるらしいです。
* Minikube を使う
  * 自動で Vagrant の仮想環境をたて、Kubernetes を実行してくれます。
  * より完全に近い Kubernetes 環境を再現してくれるらしいです。


# Docker for Mac 版ハンズオン

### 概要

既に Docker for Mac がインストールされている方は、Docker for Mac の Kubernetes を使うのが早いです。

Kubernetes にはGUIでコンテナの状態を確認できるダッシュボード機能が備わっています。
視覚的に状態を確認しながら理解を進めていきましょう。

### 前提

* OSX が Docker for Mac 18.06 以上に対応していること

### インストール

以下を参考に Docker for Mac をインストールします。
https://docs.docker.com/docker-for-mac/install/

### Kubernetes を Enable にする

Docker のメニューを開き、「Preferences」を押下します。
表示された設定メニューの中から「Kubernetes」を選択すると以下のような画面になります。


「Enable Kubernetes」「Deploy Docker Stacks to Kubernetes by default」「Show system containers (advanced)」の3つをチェックし「Apply & Restart」をします。
メニューバーに「Kubenetes is running」という項目が出るので、しばらくして緑色になることを確認します。

続いてターミナルから Context を「docker-for-desktop」に切り替えます。

```bash
$ kubectl config use-context docker-for-desktop
Switched to context "docker-for-desktop".
```

コンテキストの状態を見てみます。
指定したコンテキストになっていることが確認できます。

```bash
$ kubectl config get-contexts
CURRENT   NAME                 CLUSTER          AUTHINFO         NAMESPACE
          docker-desktop       docker-desktop   docker-desktop
*         docker-for-desktop   docker-desktop   docker-desktop 
```

クラスター、コンテキストなどが今どういう状態になっているかは以下のコマンドを打つことによって確認できます。

```bash
$ kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://kubernetes.docker.internal:6443
  name: docker-desktop
contexts:
- context:
    cluster: docker-desktop
    user: docker-desktop
  name: docker-desktop
- context:
    cluster: docker-desktop
    user: docker-desktop
  name: docker-for-desktop
current-context: docker-for-desktop
kind: Config
preferences: {}
users:
- name: docker-desktop
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED
```

ご興味があれば、以下のようなコマンドで、Docker イメージの生成状況、コンテナが起動していること、Kubernetes の Pods が出来ていることも確認できます。

```bash
$ docker images
$ docker ps -a
$ kubectl get pods --all-namespaces
```

### ダッシュボードを有効にする

ダッシュボードを有効にすると、上記コマンドで確認したような Kubernetes の状態をGUIで見ることができます。
ダッシュボードの詳細は以下を参照ください。
https://github.com/kubernetes/dashboard

まずは以下のコマンドでダッシュボードを有効にします。

```bash
$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-rc6/aio/deploy/recommended.yaml
```

続いてダッシュボードを起動します。

```bash
$ kubectl proxy
Starting to serve on 127.0.0.1:8001
```

立ち上がったら以下のリンクからダッシュボードにアクセスしてみます。
http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/

すると認証画面が表示されます。

トークンが必要になるので、ターミナルからトークンを探します。
まず「default-token」から始まる secret を探し、さらにその詳細情報を確認します。するとトークンが表示されます。

```bash
$ kubectl -n kube-system get secret | grep default-token
default-token-h5snm                              kubernetes.io/service-account-token   3      3d22h

$ kubectl -n kube-system describe secret default-token-h5snm
Name:         default-token-h5snm
Namespace:    kube-system
Labels:       <none>
Annotations:  kubernetes.io/service-account.name: default
              kubernetes.io/service-account.uid: 9e7e6f74-a435-4f90-83a1-cbca37953aa5

Type:  kubernetes.io/service-account-token

Data
====
ca.crt:     1025 bytes
namespace:  11 bytes
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlc...
```

トークンを先ほどの認証画面に入力し、ログインします。
以下のような画面が表示されれば成功です。


### Hello World を実行する

Kubernetes を構成するには通常マニフェストを記述しますが、最初は簡単に試してみたいので、Hello World を実行して様子を見てみます。
以下をコマンドを実行します。

```bash
$ kubectl run hello-world --image=hello-world -it --restart=Never
```

立ち上がったら、先ほどのダッシュボードから見てみます。
hello-world というポッドが1つ出来ていますね。「default」ネームスペースがついていて、ノードは1つしかないので「docker-desktop」を使っています。
クリックするとさらに詳細を見ることができます。

ターミナルから確認する場合は以下のコマンドで見れます。

```bash
$ kubectl get pod
NAME          READY   STATUS      RESTARTS   AGE
hello-world   0/1     Completed   0          15m

$ kubectl describe pod
Name:         hello-world
Namespace:    default
Priority:     0
Node:         docker-desktop/192.168.65.3
Start Time:   Mon, 30 Mar 2020 18:11:16 +0900
Labels:       run=hello-world
Annotations:  <none>
Status:       Succeeded
IP:           10.1.0.12
...
```

なんとなくポッドとダッシュボードが見れたところで、このポッドは削除しておきます。

```bash
$ kubectl delete pod hello-world
```

### マニフェストを記述する

では、次はマニフェストを記述してみましょう。
ここからはハンズオン用のリポジトリを使って確認していきます。
https://github.com/larable/grpc-go-handson-for-mac

以下のようにリポジトリをクローンしておきます。

```bash
git clone git@github.com:larable/grpc-go-handson-for-mac.git
```

マニフェストの基本的な要素は以下です。

```yaml
apiVersion: v1 # APIのバージョン
kind: Pod # APIの種別
metadata:
  name: test # オブジェクト名
spec: # オブジェクトの仕様
  containers:
  - name: ubuntu
    image: ubuntu:18.04
```

これを理解した上で、デプロイメントのマニフェストを記述していきたいところですが、今回は先ほどのリポジトリの以下のマニフェストを使いましょう。
k8s/dev-bgg-deployment.yaml

こちらのマニフェストも先ほどの基本の構造をベースに記述されています。
新しく登場した項目にはコメントをつけてあります。

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bgg-deployment
spec:
  replicas: 1 # レプリカ数
  selector:
    matchLabels:
      app: backend
  template: # ポッドを生成する際のテンプレート
    metadata:
      labels:
        app: backend # デプロイメントのポッドと対応づける
    spec:
      containers:
        - image: boilerplate-grpc-go
          imagePullPolicy: IfNotPresent # ローカルになければ Docker Hub から pull する
          securityContext:
            capabilities:
              add:
                - SYS_PTRACE
          name: bgg-container
          env:
            - name: GRPC_PORT
              value: "50051"
          ports:
            - containerPort: 50051
            - containerPort: 40000
          readinessProbe:
            exec:
              command: ["/bin/grpc_health_probe", "-addr=:50051"]
            initialDelaySeconds: 1
            timeoutSeconds: 1
          livenessProbe:
            exec:
              command: ["/bin/grpc_health_probe", "-addr=:50051"]
            initialDelaySeconds: 1
            timeoutSeconds: 1
          resources:
            limits:
              cpu: 100m
              memory: 32Mi
            requests:
              cpu: 100m
              memory: 32Mi
```

### デプロイメントからポッドを起動する

上記のファイルからオブジェクトの生成をしてみます。
その前に、ローカルに Docker イメージが作成されていないとエラーになるので、起動しておきます。

```bash
$ # おまじない
$ export KUBECONTEXT=minikube
$ export IMAGE=boilerplate-grpc-go
$ sh build.sh
```

では起動してみます。

```bash
$ kubectl apply -f k8s/dev-bgg-deployment.yaml
deployment.apps/bgg-deployment created
```

デプロイメントとポッドの状態を確認してみます。

```bash
$ kubectl get deploy
NAME             READY   UP-TO-DATE   AVAILABLE   AGE
bgg-deployment   1/1     1            1           74s

$ kubectl get pods
NAME                              READY   STATUS    RESTARTS   AGE
bgg-deployment-85449c8b86-w4ltj   1/1     Running   0          79s
```

立ち上がっていることが確認できました。
ちなみに、オブジェクトを削除したいときは以下のコマンドでできます。

```bash
$ kubectl delete -f k8s/dev-bgg-deployment.yaml
```

### サービスを追加する

ポッドが起動しましたが、このままでは外部からアクセスできません。
アクセスするためにはサービスを追加する必要があります。
サービスとは、IPアドレスの定義、DNS定義、負荷分散などをしてくれるものです。

サービスもマニフェストで記述します。
では、サンプルリポジトリの以下のファイルを使って試してみましょう。
k8s/dev-bgg-service.yaml

```yaml
apiVersion: v1
kind: Service
metadata:
  name: bgg-headless-svc # 内部DNSへ登録する名前
spec:
  clusterIP: None
  ports:
    - name: headless
      port: 50051
      protocol: TCP
      targetPort: 50051
  selector:
    app: backend # デプロイメントのラベルを指定する
```

ではサービスを生成してみます。

```bash
$ kubectl apply -f k8s/dev-bgg-service.yaml
```

起動したサービスを見てみると、指定したポートで立ち上がっていることが確認できます。

```bash
$ kubectl get services
NAME               TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)     AGE
bgg-headless-svc   ClusterIP   None         <none>        50051/TCP   8m28s
kubernetes         ClusterIP   10.96.0.1    <none>        443/TCP     8
```

この状態でダッシュボードを見てみましょう。
デプロイメント、ポッド、レプリカセットが生成されていることが確認できます。


ここまでで作ったものは一旦削除しておきます。

```bash
$ kubectl delete -f k8s/dev-bgg-service.yaml
service "bgg-headless-svc" deleted

$ kubectl delete -f k8s/dev-bgg-deployment.yaml
deployment.apps "bgg-deployment" deleted
```


### skaffold の導入

これまでで kubernetes の基本動作はわかりました。
しかしローカル開発で頻繁に環境を更新している場面だと、docker イメージ作成 〜 k8s 起動 までの手順はやや煩雑です。
さらに、複数のサービスを立ち上げようと思うとより大変になります。
そこで登場するのが skaffold です。
skaffold は docker build 〜 kubectl apply までを手順化して簡単に実行できます。また、ファイルを監視し変更があった場合に自動で反映してくれる機能もあります。

まずはインストール

```bash
$ brew install skaffold
```

skaffold の設定は skaffold.yaml に記述します。
今回はサンプルリポジトリの skaffold.yaml を参考に試してみます。

```yaml
apiVersion: skaffold/v2alpha4
kind: Config
build:
  artifacts:
  - image: boilerplate-grpc-go
    custom:
      buildCommand: ./build.sh
      dependencies:
        paths:
          - main.go
          - service
          - go.mod
          - go.sum
          - build.sh
          - Dockerfile
  local:
    useDockerCLI: true
    useBuildkit: true
    concurrency: 1
profiles:
  - name: local
    activation:
      - kubeContext: minikube
    build:
      local:
        push: false
    deploy:
      kubectl:
        manifests:
          - k8s/dev-*.yaml
  - name: stg
    activation:
      - kubeContext: arn:aws:eks:us-west-2:113269943850:cluster/eks-sandbox
    build:
      artifacts:
        - image: 113269943850.dkr.ecr.us-west-2.amazonaws.com/boilerplate/grpc-go
    deploy:
      kubectl:
        manifests:
          - k8s/stg-*.yaml
```

では起動してみます。
初回はしばらく時間がかかります。

```bash
$ kubectl config use-context minikube
$ skaffold dev
```

これで起動が確認できました。
`skaffold dev` ではアプリケーションの変更を検知してビルド〜デプロイを実行しますが、一度のみ実行したい場合は以下のコマンドを実行します。

```bash
$ skaffold run
```

以上で完了です。

# Minikube 版ハンズオン

余力があれば記載します。

# トラブルシューティング

### docker コマンド、kubectl コマンド の応答が返ってこない

docker コマンドや kubectl コマンドの応答が返って来ない場合や、タイムアウトと表示される場合は、docker desctop を再起動します。
メニューバーの docker から restart するか、それすらも固まっている場合はアクティビティモニタなどから再起動します。

### kubectl apply でオブジェクトが生成されないときの調査方法

以下のコマンドでオブジェクトの詳細やログを確認し、原因を突き止めます。

```bash
$ # 詳細を確認
$ kubectl describe [オブジェクト名] [名前]
$ # ログを確認
$ kubectl logs [名前]
```

### dockerイメージが見つからないエラーが出る

kubectl apply でローカルの docker イメージを使ってほしいのに、見つからないエラーが出る場合の対処方です。

* `image pull policy` を `IfNotPresent (ローカルになければpull)` にする
* docker イメージのタグを latest にしない