Kubernetesまとめ

# はじめに

Kubernetesについての初心者向けまとめ兼自分のメモです。なお、この記事は「Docker/Kubernetes 実践コンテナ開発入門」という技術書の内容に則っています。

# Kubernetesとは

KubernetesはGoogle社主導で開発された、コンテナの運用を自動化するためのコンテナオーケストレーションシステムです。コンテナオーケストレーションを実現・管理するための統合的なシステムであり、その操作のためのAPIやCLIツールも併せて提供されています。

# Kubernetesの概念

Kubernetesで実行されるアプリケーションは様々なリソースと協調して動作することで成立しています。Kubernetesのリソースとは、アプリケーションのデプロイ構成するための部品のようなもので以下のようなものがあります。

|リソース名|用途|
|:--|:--|
|Node|Kubernetesクラスタで実行するコンテナを配置するためのサーバ|
|Namespace|Kubernetesクラスタ内で作る仮想的なクラスタ|
|Pod|コンテナ集合体の単位で、コンテナを実行する方法を定義する|
|ReplicaSet|同じ仕様のPodを複数生成・管理する|
|Deployment|ReplicaSetの世代を管理する|
|Service|Podの集合にアクセスするための経路を定義する|
|Ingress|ServiceをKubernetesクラスタの外に公開する|
|ConfigMap|設定情報を定義し、Podに供給する|
|PersistentVolume|Podが利用するストレージのサイズや種別を定義する|
|PersistentVolumeClaim|PersistentVolumeを動的に確保する|
|StorageClass|PersistentVolumeが確保するストレージの種類を定義する|
|StatefulSet|同じ仕様で一貫性のあるPodを複数生成・管理する|
|Job|常駐目的ではない複数のPodを作成し、正常終了することを保証する|
|CronJob|cron記法でスケジューリングして実行されるJob|
|Secret|認証情報等の機密データを定義する|
|Role|Namespace内で操作可能なKubernetesリソースのルールを定義する|
|RoleBinding|RoleとKubernetesリソースを利用するユーザーを紐づける|
|ClusterRole|Cluster全体で操作可能なKubernetesリソースのルールを定義する|
|ClusterRoleBinding|ClusterRoleとKubernetesリソースを利用するユーザーを紐づける|
|ServiceAccount|PodにKubernetesリソースを操作させる際に利用するユーザー|

# KubernetesクラスタとNode

KubenetesクラスタはKubernetesの様々なリソースを管理する集合体のことを指します。NodeはKubernetesクラスタの管理下に登録されているDockerホストのことでKubernetesでコンテナをデプロイするために利用されます。Kubernetesクラスタは以下のようにMasterとNode郡によって構成されます。

![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507052/e4dfab49-fa1e-022f-ab95-e50503e3785c.png)

Master Nodeにデプロイされるコンポーネントの役割は以下のようになっています。

|コンポーネント名|役割|
|:--|:--|
|kube apiserver|KubernetesのAPIを公開するコンポーネント。kubectlからのリソース操作を受け付ける|
|etcd|高可用性を備えた分散キーバリューストアでKubernetesクラスタのバッキングストアとして利用される|
|kube scheduler|Nodeを監視し、コンテナを配置する最適なNodeを選択する|
|kube controller manager|リソースを制御するコントローラーを実行する|

# Namespace

kubernetesはクラスタの中に入れ子となる仮想的なクラスタを作成できます。これがNamespaceという概念です。クラスタを構築するとあらかじめdefault,docker,kube-public,kube-systemというNamespaceが用意されます。Namespaceごとに操作権限を用意できるのでより堅牢で細やかな権利制御を実現できます。

# Pod

## Podの概要

Podはコンテナの集合体の単位で、少なくとも１つのコンテナを持ちます。Podは以下のようにどこかのNodeに配置されるようになっています。同じPodを複数のNodeに配置する、１つのNodeに複数配置することも可能です。

![図2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507052/a03bc97b-09fc-8ed4-8d25-3d0556ca6f7a.png)


## Podの作成

作成するPodは以下のようにyamlファイル（マニフェストファイル）で定義します。ここでは例としてプロキシの役割をもつNginxとアプリケーションであるechoで構成されるPodを定義します。（イメージは既に作成済みとします）

```yaml:echo-pod.yaml
apiVersion: v1
kind: Pod    # リソースの種類の指定
metadate:
  name: echo    # リソース名
spec:    # kind属性の値次第でspec以下のスキーマが変わる
  containers:    # コンテナ郡の指定
  - name: nginx    # コンテナ名
    image: example/nginx:latest    # イメージ保存先
    env:    # 環境変数の指定
    - name: BACKEND_HOST
      value: localhost:8080
    ports:    # EXPOSEするポートの指定
    - containerPort: 80
  - name: echo
    image: example/echo:latest
    ports:
    - containerPort: 8080
```

このファイルで定義したPodをKubernetesクラスタに反映させるには以下のコマンドを実行します。

```shell
$ kubectl apply -f echo.yaml
pod "echo" created
```

以降に紹介するリソースも同様のコマンドで反映させることができます。

## Podの操作

### Podの状態の確認

Podの状態は以下のコマンドを実行することで一覧取得できます。STATUSがRunningになっていればPod内の全てのコンテナが実行状態になったことを示します。READYの分母はPodに定義されたコンテナ数、分子は実行状態になったコンテナ数を表します。

```
$ kubectl get pod
NAME         READY      STATUS     RESTARTS     AGE
echo         2/2        Running    0            11m
```

### コンテナ内でのコマンド実行

以下のコマンドでコンテナ内でコマンドを実行することができます。

```shell
# kubectl exec -it Pod名 コマンド -c コンテナ名
$ kubectl exec -it echo sh -c nginx
#
```

### コンテナの標準出力表示

```shell
# kubectl logs -f Pod名 -c コンテナ名
$ kubectl logs -f echo -c echo
```

### Podの削除

```shell
# kubectl delete pod Pod名
$ kubectl delete pod echo
# kubectl delete -f マニフェストファイル名
$ kubectl delete -f echo.yaml
```

### PodとPod内コンテナのアドレス

Podにはそれぞれ固有の仮想IPアドレスが割り振られます。Podに割り振られた仮想IPアドレスはそのPod内の全てのコンテナと共有されます。

![図3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507052/482fb8d1-6a9d-63a8-fe6c-f2951321a66d.png)

# ReplicaSet

ReplicaSetは同じ仕様のPodを複数生成・管理するためのリソースです。例としてReplicaSetを記述したマニフェストファイルecho-replicaset.yamlを示します。

```yaml:echo-replicaset.yaml
apiVersion: apps/v1
kind: ReplicaSet    # リソースの種類の指定
metadate:
  name: echo    # リソース名
  labels:
    app: echo
spec:    # kind属性の値次第でspec以下のスキーマが変わる
  replicas: 3    # Pod数
  selector:
    matchLabels:
      app: echo
  template:
    metadate:
      labels:
        app: echo
    spec:
      containers:    # コンテナ郡の指定
      - name: nginx    # コンテナ名
        image: example/nginx:latest    # イメージ保存先
        env:    # 環境変数の指定
        - name: BACKEND_HOST
          value: localhost:8080
        ports:    # EXPOSEするポートの指定
        - containerPort: 80
      - name: echo
        image: example/echo:latest
        ports:
        - containerPort: 8080
```

ReplicaSetを操作してPodの数を減らすと、減らした分のPodは削除されます。削除されたPodを復元することはできません。そのためWebアプリケーションのようなステートレスな性質を持つPodの利用に向いています。

# Deployment

ReplicaSetより上位のリソースとしてDeploymentというリソースがあります。Deploymentはアプリケーションデプロイの基本単位となるリソースです。DeploymentはReplicaSetを管理・操作するために提供されているリソースです。例としてDeploymentを記述したマニフェストファイルecho-deployment.yamlを示します。

```yaml:echo-deployment.yaml
apiVersion: apps/v1
kind: Deployment    # リソースの種類の指定
metadate:
  name: echo    # リソース名
  labels:
    app: echo
spec:    # kind属性の値次第でspec以下のスキーマが変わる
  replicas: 3    # Pod数
  selector:
    matchLabels:
      app: echo
  template:
    metadate:
      labels:
        app: echo
    spec:
      containers:    # コンテナ郡の指定
      - name: nginx    # コンテナ名
        image: example/nginx:latest    # イメージ保存先
        env:    # 環境変数の指定
        - name: BACKEND_HOST
          value: localhost:8080
        ports:    # EXPOSEするポートの指定
        - containerPort: 80
      - name: echo
        image: example/echo:latest
        ports:
        - containerPort: 8080
```

Deploymentの定義はReplicaSetと大差ありません。違いはDeploymentがReplicaSetの世代管理を可能にする点です。Deploymentのリビジョンは以下のコマンドで確認できます。

```shell
$ kubectl rollout history deployment echo
deployments "echo"
REVISION   CHANGE-CASE
1          kubectl apply --filename=echo-deployment.yaml --record=true
```

# Service

Serviceはkubernetesクラスタ内において、Podの集合(主にReplicaSet)に対する経路やサービスディスカバリを提供するためのリソースです。例として２つのReplicaSetを記述したマニフェストファイルecho-ReplicaSet-with-label.yamlを示します。

```yaml:echo-ReplicaSet-with-label.yaml
apiVersion: apps/v1
kind: ReplicaSet    # リソースの種類の指定
metadate:
  name: echo    # リソース名
  labels:
    app: echo
    release: spring    # ラベル
spec:    # kind属性の値次第でspec以下のスキーマが変わる
  replicas: 1    # Pod数
  selector:
    matchLabels:
      app: echo
      release: spring
  template:
    metadate:
      labels:
        app: echo
        release: spring
    spec:
      containers:    # コンテナ郡の指定
      - name: nginx    # コンテナ名
        image: example/nginx:latest    # イメージ保存先
        env:    # 環境変数の指定
        - name: BACKEND_HOST
          value: localhost:8080
        ports:    # EXPOSEするポートの指定
        - containerPort: 80
      - name: echo
        image: example/echo:latest
        ports:
        - containerPort: 8080

---
apiVersion: apps/v1
kind: ReplicaSet    # リソースの種類の指定
metadate:
  name: echo    # リソース名
  labels:
    app: echo
    release: summer    # ラベル
spec:    # kind属性の値次第でspec以下のスキーマが変わる
  replicas: 2    # Pod数
  selector:
    matchLabels:
      app: echo
      release: summer
  template:
    metadate:
      labels:
        app: echo
        release: summer
    spec:
      containers:    # コンテナ郡の指定
      - name: nginx    # コンテナ名
        image: example/nginx:latest    # イメージ保存先
        env:    # 環境変数の指定
        - name: BACKEND_HOST
          value: localhost:8080
        ports:    # EXPOSEするポートの指定
        - containerPort: 80
      - name: echo
        image: example/echo:latest
        ports:
        - containerPort: 8080
```

release=summerを持つPodだけにアクセスできるようなServiceを定義したマニフェストファイルを以下のように作成します。

```yaml:echo-service-summer.yaml
apiVersion: v1
kind: Service
metadate:
  name: echo
spec:
  selector:
    app: echo
    release: summer
  ports:
    - name: http
      port: 80
```

このServiceとラベル付で区別されたPodとの関係を表しているのが次の図です。

![図4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507052/21e2a18b-3c2c-3197-d6cd-0db55628164e.png)

# Ingress

kubenetesクラスタの外にServiceを公開するためにはServiceをNodePortで公開することで可能ですが、この手法はあくまでL4層レベルまでしか扱えないため、HTTP/HTTPSのようにパスベースで転送先のServiceを切り替えるといったL7層レベルの制御までは行えません。これを解決するためのリソースがIngressです。ServiceのKubernetesクラスタの外への公開と、パスベースでの高度なHTTPルーティングを両立します。例として以下のServiceを考えます。

```yaml:echo-service.yaml
apiService: v1
kind: Service
metadata:
  name: echo
spec:
  selector:
    app: echo
  ports:
    - name: http
      port: 80
```

このServiceをKubernetesクラスタの外へ公開するためのIngressは以下のように定義できます。

```yaml:echo-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: echo
spec:
  rules:
  - host: example.local
    http:
      paths:
      - path: /
        backend:
          serviceName: echo
          servicePort: 80
```

# PersistentVolumeとPersistentVolumeClaim

kubernetesではストレージを確保するためにPersistentVolumeとPersistentVolumeClaimというリソースが提供されています。これらはクラスタが提供されているプラットフォームnに対応した永続ボリュームを作成するためのリソースです。PersistentVolumeはストレージの実体（GCPではGCEPersistentDisk）で、PersistentVolumeClaimはPersistentVolumeに対して必要な容量を動的に確保できます。PersistentVolumeClaimのマニフェストファイルのイメージは次のようなものです。

```yaml:pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-example
spec:
  accessModes:    # ストレージへのマウントポリシー
    - ReadWriteOnce
  storageClassName: ssd    # StorageClassリソースの名前
  resources:
    requests:
      storage: 4Gi
```

# StorageClass

StorageClassはPersistentVolumeが確保するストレージの種類を定義できるリソースです。StorageClassのマニフェストファイルのイメージは次のようなものです。

```yaml:storage-class.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: ssd
  annotation:
    storageclass.kubernetes.io/is-default-class: "false"
  labels:
    kubernetes.io/cluster-service: "true"
provisioner: kubernetes.io/gce-pd    # GCPの永続ストレージであるGCEPersistentDiskに対応したVolumePlugin
parameters:
  type: pd-ssd
```

# StatefulSet

Deploymentは定義されたPod仕様に基づきPodを作成するリソースで、一意性を持つPodや永続かデータを持つ必要がないステートレスなアプリケーションをデプロイするのに向いています。対して、StatefulSetはデータストアのように継続的にデータを永続化するステートフルなアプリケーションの管理に向いたリソースです。DeploymentではPodにランダムな識別子が付与されますが、StatefulSetではpod-0,pod-1,pod-2のような連番で一意な識別子でPodを作成します。マニュフェストファイルの定義はReplicaSetとほとんど同じです。

# Job

Jobは１つ以上のPodを作成し、指定された数のPodが正常に完了するまでを管理するためのリソースです。Jobによる全てのPodが正常に終了しても、Podは削除されずに保持されるため、終了後にPodのログや実行結果を分析できます。そのため、Webアプリケーション等の常駐型アプリケーションではなく大規模な計算やバッチ指向のアプリケーションに向いています。Jobのマニフェストファイルのイメージは次のようなものです。

```yaml:job.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: pingpong
  labels:
    app: pingpong
spec:
  parallelism: 3    # 同時に実行するPod数
  template:
    metadata:
      labels:
        app: pingpong
    spec:
      containers:
      - name: pingpong
        image: example/alpine:bash
        command: ["/bin/sh"]
        args:
          - "-c"
          - |
            echo ['date'] ping!
            sleep 10
            echo ['date'] pong!
      restartPolicy: Never    # Pod終了時の再実行の設定
```

Podの状態を確認すると終了したPodはSTATUS=Completedとして表示されます。

# CronJob

Jobは一度きりのPodの実行ですが、CronJobリソースを利用するとスケジューリングして定期的にPodを実行できます。CronJobのマニフェストファイルのイメージは次のようなものです。

```yaml:cron-job.yaml
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: pingpong
spec:
  schedule: "*/1 * * * *"    # Jobのスケジュール（cron記法）
  jobTemplate:
    spec:
      template:
        metadata:
          labels:
            app: pingpong
        spec:
          containers:
          - name: pingpong
            image: example/alpine:bash
            command: ["/bin/sh"]
            args:
              - "-c"
              - |
                echo ['date'] ping!
                sleep 10
                echo ['date'] pong!
          restartPolicy: OnFailure    # Pod終了時の再実行の設定
```

# Secret

KubernetesではSecretリソースを定義すると、機密情報の文字列をBase64エンコードした状態で扱えます。Secretのマニフェストファイルのイメージは次のようなものです。

```yaml:secret.yaml
apiVersion: v1
kind: Secret
matadata:
  name: example-secret
type: Opaque
data:
  .htpasswd: Base64エンコードした機密情報の文字列
```

Secretリソースを確認すると、Base64を複合した状態のファイルとしてダッシュボードからその内容を確認できます。

# (Cluster)Roleと(Cluster)RoleBinding

## KubernetesにおけるRBACの仕組み

RBACでの権限制御は、Kubernetes APIのどの操作が可能であるかを定義したロールと、認証ユーザー・グループ・ServiceAccountとロールの紐付けの２つの要素で成立します。２つの要素の関係は以下のような図で表すことができます。

![図5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/507052/e46e57b5-16f6-f1d0-198d-878d5dfb2a23.png)

RBACでの権限制御を実現するために、次のようなKubernetesリソースが提供されています。

|リソース名|内容|
|:---|:---|
|Role|Kubernetes APIへの操作許可のルールを定義し、指定のNamespace内でのみ有効|
|RoleBinding|認証ユーザー・グループ・ServiceAccountとRoleの紐付けを定義する|
|ClusterRole|Kubernetes APIへの操作許可のルールを定義し、クラスタ全体で有効|
|ClusterRoleBinding|認証ユーザー・グループ・ServiceAccountとClusterRoleの紐付けを定義する|

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

ClusterRoleとClusterRoleBindingのマニフェストファイルは以下のように記述する。

```yaml:cruster-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list"]
```

```yaml:cruster-role-binding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: pod-reader-binding
subjects:    # Roleを紐付ける対象の認証ユーザー
- kind: ServiceAccount
  name: user
  namespace: default
roleRef:    # Role
  kind: ClusterRole
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io
```