case-kの備忘録

日々の備忘録です。データ分析とか基盤系に興味あります。

Kubernetesの概要と実践

Kubernetesについて調べたことを備忘録としてまとめてみました。

Code

github.com

Kubernetesの概要

KubernatesはGoogle社が開発したOSSオーケストレーションツールで、複数のDockerを扱うためのツールで現在のデファクトスタンダードになっています。Kubernetesの概要を説明する上で、その背景となるコンテナ技術やKubernatesの構成について記載できればと思います。

DevOpsとマイクロサービス

DevOPSとは開発と運用のギャップを埋める概念で、開発と運用を高速に行い、ビジネス要件の急速な変化に対応します。近年は変化が激しいため、新しいビジネスやデータを使った機能開発が頻繁に行われています。この変化に対応するためにはプログラムの依存関係をできるだけ無くし、修正しやすく、新しい技術を取り込み安い構成にする必要があります。これには各モジュールを疎結合にする必要があり、このオペレーションや改善しやすい構成を「マイクロサービスアーキテクチャ」と呼ばれています。このアーキテクチャのメリットとしては、ビルドやデプロイがサービス単位でシンプルになり、新しい機能やサービス、技術を取り込み安くなります。しかし、管理対象が増えてしまうといったデメリットもあります。

Dockerとコンテナ技術について

コンテナ技術はDockerの登場がきっかけとなり、本格的に活用が始まりました。Dockerの最大のメリットとしては「環境依存しないポータビリティ」ことが挙げられます。アプリケーションとそのライブラリなど依存関係にある機能をパッケージングし、1つのユニットとして管理します。マイクロサービスアーキテクチャはこのDokcerコンテナ1つ1つの集合体です。アプリケーションとその依存性がまとまっているため、開発環境や本番環境で差分が小さくなり、同一性の高い環境を構築することが可能になります。
その他にVMと比較した場合「リソース使用量の少なさ」や「軽量で起動速度が早い」といったメリットがあります。VMは コンピュータ自体 を仮想化するのに対して、Dockerは プロセス の仮想化を行います。VMは低レイヤーであるOSから(Mac OS上でWindowsを使う)仮想化を行うのに対して、DockerはホストOSのリソース(Mac OS上ならMAC OSのリソース)を使用することができます。そのため、「軽量化」と「高速化」を実現することができます。

Docker Composeの課題とKubernetesが解決すること

Dcoker社は復数のDockerを扱うための技術(オーケストレーションツール)を提供しています。docker-composeはローカルでDockerのオーケストレーションを行うためのツールです。しかし、Dockerは1コンテナ1プロセス(マイクロサービス:1機能)に設計することを推奨しているため、(Dockerfileのベストプラクティス - 入門 Docker )複数のコンテナ間の依存関係や順序、コンテナ障害が発生した場合や、アプリケーションのアップグレード方法など本番環境で運用する上で様々な「管理」の課題が出てきます。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のリソースを実際に動かしてみます。ローカル環境でKubernetes環境を作り、Kubernetesにデプロイされているコンテナを確認できるツールをインストールします。ローカルPCに「Docker for Mac」をインストールし、「Preferences」より、「Enable Kubernetes」と「Show system containers」を選び「Apply」します。
https://hub.docker.com/editions/community/docker-ce-desktop-mac
次にKubernetesにデプロイされているコンテナを確認できるダッシュボードをインストールします。

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v1.8.3/src/deploy/recommended/kubernetes-dashboard.yaml
secret/kubernetes-dashboard-certs created
serviceaccount/kubernetes-dashboard created
role.rbac.authorization.k8s.io/kubernetes-dashboard-minimal created
rolebinding.rbac.authorization.k8s.io/kubernetes-dashboard-minimal created
deployment.apps/kubernetes-dashboard created
service/kubernetes-dashboard created

STATUS=Runningになっていることを確認する。

$  kubectl get pod --namespace=kube-system -l k8s-app=kubernetes-dashboard
NAME                                    READY   STATUS    RESTARTS   AGE
kubernetes-dashboard-54f679684c-zrtgx   1/1     Running   0          86s

ダッシュボードをブラウザで閲覧するために、プロキシサーバを立ち上げます

$ kubectl proxy
Starting to serve on 127.0.0.1:8001

http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/
f:id:casekblog:20200222183419p:plain

KubernetesクラスタとNode

KubernetesクラスタKubernetes上のリソースを管理する集合体のことです。クラスタ内のリソースでもっとも大きな概念がNode(ノード)となります。
NodeはDockerのホストのことでコンテナのデプロイ対象として利用されます。Kubernetesクラスタには必ず1つMasterノードが配置されています。このMasterノードがクラスタ全体の制御をする役割をになっています。

Namespace

Kubernetesクラスタ内に仮想的なクラスタを作成できます。これはNamespaceと呼ばれる概念で、用途としては開発者それぞれにNamespaceを用意してメインのNamespaceが散らかるのを防いだりできるようです。Namespaceごとに操作に必要な権限を付与することも可能です。

Pod

Podはコンテナを管理する最小単位で少なくとも1つのコンテナを持ちます。コンテナは基本的に1コンテナ1機能が良いとされていますが、密結合の方が都合の良いケース(リバースプロキシのNginxコンテナとその背後にあるAPIコンテナんど)も存在します。その場合、コンテナは個別にしPodという単位でコンテナをまとめてデプロイします。同じPod内のコンテナは全て同一のNodeに配置されます。同じPodを複数のNodeに配置したり、1つのNodeに複数配置することも可能です。

Podをデプロイしてみる
# マニフェストファイル
apiVersion: v1
kind: Pod
metadata:
  name: simple-echo
spec:
  containers:
  - name: nginx
    image: gihyodocker/nginx-proxy:latest
    env:
    - name: BACKEND_HOST
      value: localhost:8080
    ports:
    - containerPort: 80
  - name: echo
    image: gihyodocker/echo:latest
    ports:
    - containerPort: 8080
  • kind:Kubernetesのリソースの種類を指定する属性
  • spec:リソースを定義するための属性。Podの場合はPodを構成するコンテナ群を定義
  • containers:Pod内で扱うコンテナを定義

各Podには固有のIPアドレスが割り振られます。割り振られたIPアドレスはPodに所属する全てのコンテナと共有されます。この場合だとnginxコンテナを通して、echoコンテナにアクセスしたいため、環境変数「BACKEND_HOST」には「PodのIPアドレス:echoコンテナのポート」であるlocalhsot:8080を指定します。マニフェストファイルを指定し、デプロイします。

$ kubectl apply -f simple-pod.yaml
pod/simple-echo created

Podを操作するコマンド

Podの状態確認
$  kubectl get pod
NAME          READY   STATUS    RESTARTS   AGE
simple-echo   2/2     Running   0          3m25s
Pod内のコンテナで実行
# Pod内のコンテナに入る
$ kubectl exec -it simple-echo sh -c nginx
Pod内のコンテナ標準出力
$ kubectl logs -f simple-echo -c echo
2020/02/22 10:10:43 start server
Podを削除
$ kubectl delete pod simple-echo
pod "simple-echo" deleted
Podを削除(マニフェスト)

マニフェストファイルに記述されているリソースを全て削除します。

$ kubectl delete -f simple-pod.yaml

ReplicaSet

ReplicaSetは同じ仕様のPodを複数生成、管理するためのリソースです。マニフェストで指定した適切なPod数になるよう自動調整してくれるため、Podに障害が発生した場合や多すぎる場合は自動で適切なPod数になるように調整してくれます。メタデータのlabelを使ってPodを検索します。

# マニフェストファイル
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: echo
  labels:
    app: echo
spec:
  replicas: 3
  selector:
    matchLabels:
      app: echo
  template: # template以下はPodリソースにおける定義と同じ
    metadata:
      labels:
        app: echo
    spec:
      containers:
      - name: nginx
        image: gihyodocker/nginx:latest
        env:
        - name: BACKEND_HOST
          value: localhost:8080
        ports:
        - containerPort: 80
      - name: echo
        image: gihyodocker/echo:latest
        ports:
        - containerPort: 8080
  • replicas:作成するPod数

マニフェストをデプロイ

$ kubectl apply -f simple-replicaset.yaml
replicaset.apps/echo created

Podの状態を確認

$ kubectl get pod
NAME         READY   STATUS    RESTARTS   AGE
echo-nd45h   2/2     Running   0          6m17s
echo-tkl62   2/2     Running   0          6m17s
echo-wv4bp   2/2     Running   0          6m17s

マニフェストに該当するリソースを削除する

$ kubectl delete -f simple-replicaset.yaml
replicaset.apps "echo" deleted

Deployment

DeploymentはReplicaSetを管理・操作するためのリソースであり、新しいバージョンのリソースを管理するための仕組み(世代管理やロールバックなど)があります。マニフェストの定義はReplicaSetとあまり変わりません。

# マニフェストファイル
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo
  labels:
    app: echo
spec:
  replicas: 3
  selector:
    matchLabels:
      app: echo
  template: # template以下はPodリソースにおけるspec定義と同じ
    metadata: 
      labels:
        app: echo
    spec:
      containers:
      - name: nginx
        image: gihyodocker/nginx:latest
        env:
        - name: BACKEND_HOST
          value: localhost:8080
        ports:
        - containerPort: 80
      - name: echo
        image: gihyodocker/echo:latest
        ports:
        - containerPort: 8080

作成したマニフェストファイルをデプロイします。

$ kubectl apply -f simple-deployment.yaml --record
deployment.apps/echo created
  • record:実行したkubectlコマンドを記録

デプロイしたリソースを確認します。

$ kubectl get pod,replicaset,deployment --selector app=echo
NAME                        READY   STATUS    RESTARTS   AGE
pod/echo-68896f77d7-4c79t   2/2     Running   0          3m41s
pod/echo-68896f77d7-q2k5n   2/2     Running   0          3m41s
pod/echo-68896f77d7-qwkfx   2/2     Running   0          3m41s

NAME                                    DESIRED   CURRENT   READY   AGE
replicaset.extensions/echo-68896f77d7   3         3         3       3m41s

NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
deployment.extensions/echo   3/3     3            3           3m41s

Deploymentのリビジョンを確認します。初回は「REVISION=1」となっています。

$ kubectl rollout history deployment echo
deployment.extensions/echo
REVISION  CHANGE-CAUSE
1         kubectl apply --filename=simple-deployment.yaml --record=true
ReplicaSetのライフサイクル

KubernetesではDeploymentを1つの単位として、アプリケーションをデプロイして行きます。実運用だとReplicaSetを直接用いることはなく、Deploymentのマニフェストファイルを扱う運用にすることがほとんどです。内部的にはDeploymentが管理しているReplicaSetは指定されたPod数の確保や、新しいバージョンのPodへの入れ替え、以前のバージョンへのPodのロールバックといった重要な役割を担っています。

Podの数を更新した場合の挙動
spec:
  replicas: 4 # 3 -> 4
$ kubectl apply -f simple-deployment.yaml --record
deployment.apps/echo configured
$ kubectl get pod,replicaset,deployment --selector app=echo
NAME                        READY   STATUS              RESTARTS   AGE
pod/echo-68896f77d7-4c79t   2/2     Running             0          28m
pod/echo-68896f77d7-4hx4g   0/2     ContainerCreating   0          5s
pod/echo-68896f77d7-q2k5n   2/2     Running             0          28m
pod/echo-68896f77d7-qwkfx   2/2     Running             0          28m

Replica数を更新し、デプロイしてもリビジョンは変わらない。

$ kubectl rollout history deployment echo
deployment.extensions/echo
REVISION  CHANGE-CAUSE
1         kubectl apply --filename=simple-deployment.yaml --record=true
コンテナ定義を更新

コンテナの定義を更新し、デプロイしリビジョンが変更されるか確認します。

$ kubectl get pod,replicaset,deployment --selector app=echo
NAME                        READY   STATUS        RESTARTS   AGE
pod/echo-597949b5c5-4s7l8   2/2     Running       0          36s
pod/echo-597949b5c5-sgldm   2/2     Running       0          50s
pod/echo-597949b5c5-trpxs   2/2     Running       0          50s
pod/echo-597949b5c5-xqdnl   2/2     Running       0          38s
pod/echo-68896f77d7-q2k5n   2/2     Terminating   0          32m

リビジョンを確認すると更新されていることを確認できます。

$  kubectl rollout history deployment echo
deployment.extensions/echo
REVISION  CHANGE-CAUSE
1         kubectl apply --filename=simple-deployment.yaml --record=true
2         kubectl apply --filename=simple-deployment.yaml --record=true
ロールバック

運用で必要になってくるロールバックを試します。まず、リビジョンの内容を確認してみます。

$ kubectl rollout history deployment echo --revision=1
deployment.extensions/echo with revision #1
Pod Template:
  Labels:	app=echo
	pod-template-hash=68896f77d7
  Annotations:	kubernetes.io/change-cause: kubectl apply --filename=simple-deployment.yaml --record=true
  Containers:
   nginx:
    Image:	gihyodocker/nginx:latest
    Port:	80/TCP
    Host Port:	0/TCP
    Environment:
      BACKEND_HOST:	localhost:8080
    Mounts:	<none>
   echo:
    Image:	gihyodocker/echo:latest
    Port:	8080/TCP
    Host Port:	0/TCP
    Environment:	<none>
    Mounts:	<none>
  Volumes:	<none>

直前のリビジョンにロールバックさせます。

$ kubectl rollout undo deployment echo
deployment.extensions/echo rolled back

リビジョンも更新

$ kubectl rollout history deployment echo
deployment.extensions/echo
REVISION  CHANGE-CAUSE
2         kubectl apply --filename=simple-deployment.yaml --record=true
3         kubectl apply --filename=simple-deployment.yaml --record=true

最新のDeploymentに問題があった場合は以前のバージョンに戻すことが可能。

Sercvice

ServiceはPodの集合に対する経路やサービスディスカバリを提供するリソースです。Serviceを介してPodからPod群へのアクセスなどが可能になります。挙動を確認するために、ReplicaSetを2種類(spring/summer)用意して、その後Serviceを介してsummerにのみアクセスできるようにします。

# マニフェストファイル
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: echo-spring
  labels:
    app: echo
    release: spring
spec:
  replicas: 1
  selector:
    matchLabels:
      app: echo
      release: spring
  template:
    metadata:
      labels:
        app: echo
        release: spring
    spec:
      containers:
      - name: nginx
        image: gihyodocker/nginx:latest
        env:
        - name: BACKEND_HOST
          value: localhost:8080
        ports:
        - containerPort: 80
      - name: echo
        image: gihyodocker/echo:latest
        ports:
        - containerPort: 8080
--- 
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: echo-summer
  labels:
    app: echo
    release: summer
spec:
  replicas: 2
  selector:
    matchLabels:
      app: echo
      release: summer
  template:
    metadata:
      labels:
        app: echo
        release: summer
    spec:
      containers:
      - name: nginx
        image: gihyodocker/nginx:latest
        env:
        - name: BACKEND_HOST
          value: localhost:8080
        ports:
        - containerPort: 80
      - name: echo
        image: gihyodocker/echo:latest
        ports:
        - containerPort: 8080
  • release:対象となるラベル

デプロイ

$ kubectl apply -f simple-replicaset-with-label.yaml
replicaset.apps/echo-spring unchanged
replicaset.apps/echo-summer created

確認

$ kubectl get pod  -l app=echo -l release=spring
NAME                READY   STATUS    RESTARTS   AGE
echo-spring-br8zb   2/2     Running   0          2m27s

$ kubectl get pod  -l app=echo -l release=summer
NAME                READY   STATUS    RESTARTS   AGE
echo-summer-md8pd   2/2     Running   0          92s
echo-summer-ttsdc   2/2     Running   0          92s

Serviceのマニフェストを作り、Pod Summerに対してServiceを通してアクセスできるようにする。

# マニフェストファイル
apiVersion: v1
kind: Service
metadata:
  name: echo
spec:
  selector:
    app: echo
    release: summer
  ports:
    - name: http
      port: 80
  • spec.selector:ServiceのターゲットとしたいPodがもつラベルを指定
  • release:対象となるラベル

デプロイ

$ kubectl apply -f simple-service.yaml
service/echo created

サービスを確認

$ kubectl get svc echo
NAME   TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
echo   ClusterIP   10.97.61.186   <none>        80/TCP    11s

実際にrelease=summerにのみトラフィックが流れるか確認してみます。ServiceはKubernetesクラスタの中からしかアクセスできないため、Kubernetesクラスタにデバックコンテナをでプリし、curlコマンドで確認します。

$ kubectl run -i --rm --tty debug --image=gihyodocker/fundamental:0.1.0 --restart=Never -- bash -il

If you don't see a command prompt, try pressing enter.

debug:/#  curl http://echo/
Hello Docker!!debug:/#
debug:/#

summerコンテナでリクエスト受けていることを確認できます。

$ kubectl logs -f echo-summer-md8pd -c echo
2020/02/23 04:40:56 start server
2020/02/23 04:57:45 received request

Ingress

IngressはServiceをKubernetesクラスタ外からアクセスできるようにするためのリソースです。

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.16.2/deploy/mandatory.yaml
$ kubectl -n ingress-nginx get service,pod
NAME                           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/default-http-backend   ClusterIP   10.103.119.37   <none>        80/TCP    91s

NAME                                            READY   STATUS    RESTARTS   AGE
pod/default-http-backend-6cdd6c64f8-x2mfp       1/1     Running   0          91s
pod/nginx-ingress-controller-675df7b6fd-tlnfc   1/1     Running   0          90s
#  マニフェストファイル
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: echo
spec:
  rules:
  - host: ch05.gihyo.local
    http:
      paths:
      - path: /
        backend:
          serviceName: echo
          servicePort: 80
  • servicePort:Serviceのポートを指定する
$ kubectl apply -f simple-ingress.yaml
ingress.extensions/echo created
$ kubectl get ingress
NAME   HOSTS              ADDRESS   PORTS   AGE
echo   ch05.gihyo.local             80      41s

以上となります。次はGoole Kubernetes Engineについて調べてみたいと思います。