Kubernetesの基礎知識

# はじめに
Kubernetes魅力的です！以下では、Kubernetesの基礎知識を書いていきます。
検証環境はAzureのAzure Kubernetes Service（以下、AKS）です。

# Kubernetesとは
- コンテナ化されたアプリケーションのデプロイメント、スケーリング、および管理を自動化するオープンソースのコンテナオーケストレーションプラットフォームです。以下、k8sと記載します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/79035826-00fc-d3db-1289-80130522c66f.png)

# Kubernetesにおける重要概念
## コンセプト
- システム構築時の手間を減らす
- 運用時の手間を減らす

上記を実現するための3つのコンセプトがある
1. Immutable Infrastructure
2. 宣言的設定
3. 自己修復機能

### Immutable Infrastructure
- 従来の発想
構築した後のサーバをバージョンアップしていく

- k8sにおける発想
一度構築したサーバは変更を加えずに破棄して、**新しく作り直す**。コンテナ技術などを使用して実現する

### 宣言的設定
- 従来の発想
命令的設定。手順書やパラメータシートに基づいて設定を行い、サーバに対して変更を加えた場合は変更履歴を管理していた。

- k8sにおける発想
システムのあるべき姿を定義ファイルに記述するとk8sクラスターが**自動的にそうなるように状態を保とうとする**。k8sのReconciliation Loopsなどを使用して実現する。

### 自己修復機能

- 従来の発想
システム管理者がリストアや再起動により障害復旧する

- k8sにおける発想
k8sがkubernetes APIにより**自動的に障害を検知、復旧する**。

## スケジューリングとディスカバリー

- スケジューリング

それぞれのコンテナを適切なサーバ上に配置する仕組み。
このサービスはGPUを使いたい、などの要件がある場合に、それを定義ファイルに定義しておけばk8sがスケジューリングの機能で適切なサーバに配置してくれる=Resource Requests。

- ディスカバリー

スケジューリングにより配置されたコンテナがどこのサーバに配置されているのかを見つけ出す仕組み。

## マニフェストファイル

### YAMLファイルの書き方

- Key-Valueは「:」と半角スペースで区切る。
- インデントは半角スペースのみでタブは使用できない。
- コメントは「#」で行う
- 整数、浮動小数点数、真偽値（true/false,yes/no）、null、日付、タイムスタンプは自動的にデータ型を判定する。上記以外と「’」か「”」で囲んだ場合は文字列として判定する。
- 「-」と半角スペースからデータを始めることで配列の要素を表すことができる。

以下はnginxのコンテナを10台起動するReplicaSetのマニフェストファイル
```yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: webserver
spec:
  # Pod数
  replicas: 10
  selector:
    matchLabels:
  # 起動しておくPodのLabel
      app: webfront
# Podのテンプレート
  template:
    metadata:
      labels:
        app: webfront
    spec:
      containers:
      - image: nginx
        name: webfront-container
        ports:
          - containerPort: 80
```

マニフェストファイルは大体以下のような構成になっている。

```yaml
apiVersion: [APIのバージョン情報]
kind: [リソースの種類]
metadata:
  name: [リソースの名前]
spec: 
[リソースの詳細]
```
## Label

kubernetesではLabelの付け外しによって簡単に本番/検証環境を切り替えたり、Nodeの役割を変更したり、スケールアップ・ダウンするので、kubernetesの管理においてLabelは思ったより重要な概念なのである。
以下はDeploymentのマニフェストファイル。

```yaml
# A. 基本項目
apiVersion: apps/v1
kind: Deployment
metadata:
  name: photoview-deployment

# B. Deploymentのスペック
spec:
  replicas: 5   # レプリカ数
  selector:
    matchLabels:
      app: photo-view   # テンプレートの検索条件

  # C. Podのテンプレート
  template:
    metadata:
      labels:
        app: photo-view
        env: stage
    spec:
      containers:
      - image: azfooacr01.azurecr.io/photo-view:v1.0   # コンテナイメージの場所
        name: photoview-container   # コンテナ名
        ports:
        - containerPort: 80   # ポート番号
```


podに対してlabelsで「app」ラベルを「photo-view」に、「env」ラベルを「stage」に定義している。
このようにラベルを使って適切にラベリングすることで、k8s内のリソースの管理を効率的に行うことができる。
各Podのラベルは以下のコマンドで表示することが可能。
```
kubectl get pods —show-labels
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/72d88c7d-a8fa-9253-e434-f671245f721d.png)

envラベルがstageであり、かつappラベルがphoto-viewでないPodだけを表示する
```
kubectl get pods -l env=stage,app!=photo-view
```
マニフェストファイルを変更してから以下のようなコマンドを使用すると徐々にPodのラベルが変更されている様子がわかる。
```
kubectl apply -f sample-deployment.yaml
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/ff33b386-9532-e017-32c0-d9946220bf70.png)
なお、kubectl label deploymentコマンドを実行することでラベルを書き換えることも可能だが、構成管理の観点からもマニフェストファイルを変更することが推奨される。

## NodeSelector
Podを動かすNodeを明示的に指定するためのKey。
以下は、serverラベルがwebapであるnodeで実行する例。
```yaml
apiVersion: v1
kind: Pod
metadata:
 name: nginx
 labels:
  env: stage

spec:
 containers:
 - name: nginx
   image: nginx
 nodeSelector:
   server: webap
```

nodeにラベルをつけるコマンド
```
kubectl label node aks-nodepool-67404889-vmss000000 server=webap
```
## Resource Requests

Podを実行するために必要最低限割り当てたいリソースを指定する。
上記を設定することによって、k8s schedulerが適切なノードを選択し、Podに十分なリソースを割り当てることができる。
以下はPodに400mのCPUと2Giのメモリを割り当てるように指定するマニフェストファイル。
```yaml
apiVersion: v1
kind: Pod
metadata:
 name: requests-pod

spec:
 containers:
 - image: busybox
   command: ["dd", "if=/dev/zero", "of=/dev/null"]
   name: main
   resources:
    requests:
     cpu: 400m
     memory: 2Gi
```

実際にkubectl describe node [割り当てられたnode名]コマンドを実行すると、指定したリソースがpodに割り当たっていることがわかる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/dce6730f-ba73-17cd-f5f9-453106ef396a.png)
Nodeのリソースを超えた値をResource Requestsで指定するとどうなるか。
1. podのstatusがpendingになる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/2081083d-a104-7678-7521-10b2bb4bf31e.png)

2. kubectl describe [podname]を実行するとnode欄がnoneになっている
3. eventログにfailedschedulingが理由でmemoryがinsufficientである旨メッセージがでているのがわかる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/587daf25-89c7-6a55-858b-6cc1387dd7e4.png)

## Resource Limits
Podを実行する上でのリソースの上限を指定する。
上記を指定することによって、特定のPodが異常な量のリソースを使用してクラスター全体の動作を不安定なものにすることを防ぐ。

```yaml
apiVersion: v1
kind: Pod
metadata:
 name: limits-pod

spec:
 containers:
 - name: main
   image: polinux/stress
   resources:
    limits:
     cpu: 400m
     memory: 1Gi
   command: ["stress"]
   args: ["--vm", "1", "--vm-bytes", "500M", "--vm-hang", "1"]
```

上限を超えた負荷をかけるとどうなるか。
stressコマンドの引数を500M→2Gにしてみると、Podが再起動を繰り返していることがわかる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/420df559-8729-85b2-9f03-bb75ea210feb.png)

kubectl describe pod [podname]コマンドを使用すると削除されており（Terminated）理由は「OOMKilled」となっている。コンテナ内のOOM Killer（Linuxカーネルの機能であり、メモリ不測の際に実行中のプロセスを強制終了させる役割を持つ）にプロセスをkillされている。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/c6594057-d977-f287-96ab-f5e08b32a948.png)

## Liveness Probe
プロセスの死活監視などとは異なる、”サービス”として正しく動いてるか、を監視してくれるk8sの監視機能。
- httpGet

HTTPリクエストの戻り値をチェックする。

```yaml
apiVersion: v1
kind: Pod
metadata: 
 name: liveness-pod

spec:
 containers:
 - name: liveness
   image: k8s.gcr.io/liveness
   args:
   - /server
   livenessProbe:
    httpGet:
     path: /healthz
     port: 8080
     httpHeaders:
     - name: Z-Custom-Header
       value: Awesome
    initialDelaySeconds: 10
    periodSeconds: 5
```

- tcpSocket

HTTP以外のリクエストの戻り値をチェックする。

- exec

コンテナ内でコマンドを実行しその結果が0で返ってくるかどうかをチェックする。

```yaml
apiVersion: v1
kind: Pod
metadata:
 labels:
  test: liveness
 name: liveness-exec-pod

spec:
 containers:
 - name: liveness
   image: busybox
   args:
   - /bin/sh
   - -c
   - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
   livenessProbe:
    exec:
     command:
     - cat
     - /tmp/healthy
    initialDelaySeconds: 10
    periodSeconds: 5
```

describeコマンドよりeventログを見てみる。
コンテナ作成後30秒は/tmp/healthyが存在するため監視に成功する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/2c62f29c-fcec-ce31-4494-62321c989967.png)

30秒経つと/tmp/healthyが削除されるため監視に失敗し、Podが削除、作成が繰り返されている
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/a4a8f582-fc14-bca3-5f23-2bc6090c6da2.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/5ed90a87-d2c1-c9d3-3f27-8de9096bfd3b.png)

## kubectlコマンド

ローカルのマニフェストファイルを用いてクラスター上にdeploymentをデプロイ
```
kubectl apply -f sample-deployment.yaml
```
### 上記のコマンドの流れ

1. クライアントにてホームディレクトリの.kube/configに記載のk8sクラスターのAPI Serverに対して同ファイルに記載の認証情報を用いて接続要求を行う。
2. 上記接続要求を受け取ったAPI Serverは認証を行い、合っていれば接続を確立する。
3. クライアントにてsample-deployment.yamlの情報を読み取り、kubectlコマンドにてAPI Serverに対してnginxのコンテナを10台起動するようにリクエストを行う。
4. リクエストを受け取ったAPI Serverは認証を行い、合っていれば要求のあった変更内容「Podを作成する」についてetcdに書き込む。
5. SchedulerがPodの情報が更新されたことを検知する
6. SchedulerがどのNodeにPodを配置するかを決定する（=スケジューリング）
7. SchedulerはAPI Serverを呼び出し、etcd上にPodの配置先のNodeの情報を書き込む。
8. Node上のkubeletが自身のNodeの情報が更新されたことを検知する
9. kubeletがコンテナランタイムに対してPodの作成を指示する。

## その他よく使うコマンド
クラスター上のサービスを取得する
```
kubectl get svc
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/320fbdeb-6f81-15fd-1d8a-812d17df74d7.png)

上記のようにexternal-IPとclusterIPを見ることができる。

Podの詳細情報・エラー情報を表示する
```
kubectl describe pods [pod-name]
```
Podを削除する
```
kubectl delete -f sample-deployment.yaml
```
Serviceを削除する
```
kubectl delete -f sample-service.yaml
```


## k8sのMasterサーバ

- 概要

管理する側のサーバ。etcdというデータベースにNodeも含めたk8s全般の設定情報を格納している。

- API Server

k8sを操作するためのAPIがここに集約されている。RESTfulなAPI。システム管理者はkubectlを使ってここにアクセスする。
k8s内のコンポーネントは何をするにもここを介して操作を行っている。
kubectlはホームディレクトリの~/.kube/configに書かれている認証情報をもとにクラスターに接続する。その認証情報をもとにAPI Serverが認証・認可を行う。

認証情報は以下のコマンドでCLI上で確認可能。
```
kubectl config view
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/faadd32d-53a8-d0f2-b680-ea82a9c6de06.png)

上記の例ではユーザのトークン、クライアントキー、クライアント証明書にて認証を行っている。

https://kubernetes.io/docs/tasks/access-application-cluster/access-cluster/

- Scheduler

PodをどのNodeで動かすかを決めるコンポーネント。

- Controller Manager

k8sクラスター全体を監視し、Podやコンテナ上のアプリケーションがetcd内の定義ファイル上で定義された内容と一致しているかを監視してそれを維持しようとするコンポーネント。
Node Controller, ReplicaSet Controller, Service Controllerなど様々なControllerなどのkube-controller-managerとクラウド独自のコントローラであるcloud-controller-managerがいる。

- データベース（etcd）

Podなどの状態を定義ファイルの形で保持しておくコンポーネント。
Masterから切り離されることもある。

# k8sのリソース

以下では代表的なリソースのみ紹介する。

## Node

- 概要

実際にコンテナ、アプリケーションが動くサーバ群。

- kubelet

Masterからの指示に従って実際にコンテナを実行する機能を持つコンポーネント。また、Nodeの状態を監視し、定期的にAPI Serverに通知する。

- kube-proxy

ネットワークプロキシのコンポーネント。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/2f7a79d9-4b70-aa05-d6d2-5e8978e0dab0.png)

https://kubernetes.io/ja/docs/concepts/overview/components/

## Pod

コンテナアプリケーションをk8sで管理する上での最小単位。役割が似ている複数のコンテナのセット。
複数のコンテナを1つのPodの中に含めることが可能。
1つのPodの中に入っているコンテナは必ず1つのノードの中に配置される。
Pod内はlocalhostで通信が可能。ストレージも共有する。
同じPod内にどのようなコンテナをいれるのかは機能要件・非機能要件によるが、以下のようなパターンがよくある。

1. プロキシの役割をするコンテナを同じPodにいれる。
例えばHTTPのWebコンテナの前にSSLプロキシーコンテナをいれるなど。Pod内はローカルホストでアクセス可能なため、既存アプリケーションに手が入らないで済む。

2. 認証機能（OAuthなど）をもつコンテナを同じPodにいれる。

## ReplicaSet

マニフェストファイルで指定された数のPodを起動してその状態を保ち続けるための仕組み。
LabelSelectorの機能を使用し、該当するラベルのPod数があるべき数と一致しているかを確認している。
以下はReplicaSetのマニフェストファイルの例（再掲）。

```yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: webserver
spec:
  # Pod数
  replicas: 10
  selector:
    matchLabels:
  # 起動しておくPodのLabel
      app: webfront
# Podのテンプレート
  template:
    metadata:
      labels:
        app: webfront
    spec:
      containers:
      - image: nginx
        name: webfront-container
        ports:
          - containerPort: 80
```

上記のReplicaSetはappラベルがwebfrontであるPodを10台起動しておくよう定義している。
Podのテンプレートではappラベルをwebfrontにしており、ReplicaSetの下に一緒にPodの定義もかけるようになっている。

仮に元々クラスター内にappラベルがwebfrontであるPodが2台存在している場合は、上記マニフェストファイルをapplyしてから新規に作成されるPodの数は8台である。

あくまでも、最終的に10台になるように制御されるということなのである。

describeコマンドで状態を確認すると、Replicasのところが10台起動すべきところが10台起動していることがわかる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/1a419aa3-1137-ad8d-72d8-3649a16ea3fa.png)

ReplicaSet Controllerは、ReplicaSet APIの状態を常に監視している。
以下はReplicaSet APIであるため、ReplicaSet Controllerは内部的に以下のコマンドを実行している可能性が高いと思っている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/d8a3f3c3-a3f7-e84c-5c1e-c3fa1357939f.png)


DESIREDとCURRENTのPod数に差分が出たとき、ReplicaSet Controllerは以下の処理を行う。

1. Podの数が想定より少ない
Pod APIを呼び出して、ReplicaSetのマニフェストファイルのspecに記載されたスペックのPodを差分の数だけ作成する。

1. Podの数が想定より多い

Pod APIを呼び出して、差分の数だけPodを削除する。

## Deployment

複数のReplicaSetに1つのDeploymentを紐づけることでローリングアップデートすることが可能になる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/26df941b-0d32-494d-0597-46a2b321efc2.png)


全てのPodでバージョンアップ後にOld ReplicaSetのReplica数を0から3に戻すことで切り戻しが可能。

## DaemonSet

k8sではどのノードにどのPodが配置されるかk8sが動的に決定するが、全てのノード上に1つ特定のPodを配置したい場合もある。そのようなときにDaemonSetを使用する。

DaemonSetもReplicaSetと同じように配置したPodを監視しているため、特定のノードでDaemonSetによって配置されるべきPodが配置されていない状態になった場合は、DaemonSetによってそれが検知され状態を保とうとする機能をもつ。

ちなみに、kube-proxyもDaemonSetの仕組みによって配置されている。

## StatefulSet

基本的にコンテナアプリケーションは、ステートレスでありスケールダウン時にどのPodを落とすかはアルゴリズムに従って自動的に決められてしまうが、データベースなどのステートフルな状態を維持したいときは、これを使用する。

# ネットワーク関連

## Service

以下のような種類がある。

Cluster IP：クラスター内のPod同士で通信するためのプライベートIP
External IP：クラスターの外部に公開するIP。
LoadBalancer：L4のロードバランサ。

## Ingress

クラスター外部からのHTTP、HTTPSの通信をk8sクラスター内にL7のルーティング、TLS/SSL終端（暗号、復号）、L7のロードバランシングなどをするリソース。

### アプリケーションの設定情報の管理

アプリケーションに必要な環境変数をコンテナのイメージの中に入れてしまうと都度コンテナが生き死にを繰り返すk8sにおいては不便。なので各コンテナで共有しておきたい情報については、以下のようなリソースにて一元管理される。

# ConfigMap

アプリケーションの設定情報や構成ファイル（apacheの設定ファイルなど）などをPodから参照するためのリソース。

ここに格納されたデータは各コンテナに対してボリュームとしてマウントすることができ、コンテナから見ると普通のファイルとしてみえる。

## Secrets

ConfigMapと同じように各コンテナ間で共有しておきたい情報の中でもパスワードなど秘匿性の高い情報を格納するためのリソース。

# バッチジョブの管理

ジョブを実現するためのリソース。普通Podの停止は異常終了を表すが以下のリソースにおいてPodの停止はジョブの終了を表す。

## Job

1回限りの実行が想定されるジョブを実行するためのリソース。

## CronJob

ストレージのバックアップなど定期的に実行されることが想定されるジョブを実行するためのリソース。

# その他のリソース

## namespace

namespaceによって、一つのk8sクラスターの名前空間を論理的に区分けすることができる。

また、区分けしたnamespaceごとにロールベースの権限管理が可能。

よって、複数のプロジェクトのリソースを一つのクラスター内で管理することが可能。

namespaceを一覧表示する
```
kubectl get namespace
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/c88bc27f-8811-8d79-e8a5-62deaaec7391.png)

デフォルトで作成されるnamespaceはk8sのバージョン1.26.3時点では以下の4つである。
defalut：namespaceを明示的に指定しない場合のデフォルト
kube-public：全ユーザーが利用可能なConfigMapなどのリソースが入っている
kube-system：k8sクラスターが内部で利用するリソースが入っている
kube-node-lease：v1.14以降でデフォルトで作成されるようになった。

Nodeのハートビートを監視するためのleaseオブジェクトというリソースが入る。他のコンポーネントはこのleaseオブジェクトの状態を監視することでNodeの正常性を確認する。

kube-systemのnamespaceであるPodのみを表示する
```
kubectl get pods —namespace kube-system
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2475719/f1e63bcf-4d73-c46b-087a-1da724ef5bc5.png)

# おわりに
良きk8sライフを！