Kubernetesについて調べたことを備忘録としてまとめてみました。
Code
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/
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
- f:マニフェストファイルのパス
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
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について調べてみたいと思います。