--- title: Kubernetesハンズオン - 2. アプリケーションのデプロイ tags: ["Kubernetes Handson", "Kubernetes", "PKS"] categories: ["Dev", "CaaS", "Kubernetes"] date: 2019-09-01T17:34:39Z updated: 1970-01-01T00:00:00Z --- Kubernetesにアプリケーションをデプロイする上で最も基本的なPod, ReplicaSet, Deploymentについて説明します。 Podはアプリケーションを動かすための最小の単位で、1つ以上のコンテナから構成されます。 ReplicaSetはPodを複製し、指定されたレプリカ数を維持するようにPodを管理します。 DeploymentはReplicaSetを生成、管理しローリングアップデートやロールバックを実現します。 ![image](https://user-images.githubusercontent.com/106908/40192320-f24e52b2-5a3e-11e8-8641-6eb7e49f7733.png) 通常、アプリケーションをデプロイする際はDeploymentを作成し、ReplicaSetやPodはそれぞれDeployment、ReplicaSetに管理させます。 **目次** ### Podのデプロイ まずはPod単体でデプロイすることから試しましょう。 `hello-pks-pod.yml`を作成して、次の内容を記述してください。 ```yaml apiVersion: v1 kind: Pod metadata: name: hello-pks spec: containers: - image: making/hello-pks:0.0.1 name: hello-pks ports: - containerPort: 8080 ``` このmanifestファイルを使い、次のコマンドでPodをデプロイします。 ``` kubectl apply -f hello-pks-pod.yml ``` 出力結果 ``` pod/hello-pks created ``` `kubectl get pod`でPodを一覧表示します。 ``` kubectl get pod ``` 出力結果 ``` NAME READY STATUS RESTARTS AGE hello-pks 1/1 Running 0 25s ``` `kubectl describe pod `でPodの詳細情報を確認できます。 ``` kubectl describe pod hello-pks ``` 出力結果 ``` Name: hello-pks Namespace: default Priority: 0 PriorityClassName: Node: ip-10-0-8-6.ap-northeast-1.compute.internal/10.0.8.6 Start Time: Mon, 02 Sep 2019 01:56:51 +0900 Labels: Annotations: kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"v1","kind":"Pod","metadata":{"annotations":{},"name":"hello-pks","namespace":"default"},"spec":{"containers":[{"image":"mak... Status: Running IP: 10.200.74.52 Containers: hello-pks: Container ID: docker://3e6c04ce22a7ec591a5f485bded17a83ecbd89eb8e0e100a7836765acf3498a9 Image: making/hello-pks:0.0.1 Image ID: docker-pullable://making/hello-pks@sha256:cdceb399a5b4a11216792d662dbc23ecbdef04d6e49b9af48fd730a0173384cc Port: 8080/TCP Host Port: 0/TCP State: Running Started: Mon, 02 Sep 2019 01:57:12 +0900 Ready: True Restart Count: 0 Environment: Mounts: /var/run/secrets/kubernetes.io/serviceaccount from default-token-hdrng (ro) Conditions: Type Status Initialized True Ready True ContainersReady True PodScheduled True Volumes: default-token-hdrng: Type: Secret (a volume populated by a Secret) SecretName: default-token-hdrng Optional: false QoS Class: BestEffort Node-Selectors: Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s node.kubernetes.io/unreachable:NoExecute for 300s Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 38s default-scheduler Successfully assigned default/hello-pks to ip-10-0-8-6.ap-northeast-1.compute.internal Normal Pulling 37s kubelet, ip-10-0-8-6.ap-northeast-1.compute.internal Pulling image "making/hello-pks:0.0.1" Normal Pulled 18s kubelet, ip-10-0-8-6.ap-northeast-1.compute.internal Successfully pulled image "making/hello-pks:0.0.1" Normal Created 17s kubelet, ip-10-0-8-6.ap-northeast-1.compute.internal Created container hello-pks Normal Started 17s kubelet, ip-10-0-8-6.ap-northeast-1.compute.internal Started container hello-pks ``` PodにアクセスするためPodの8080番ポートをlocalhostの8080番ポートにフォワードします。 ``` kubectl port-forward hello-pks 8080:8080 ``` 出力結果 ``` Forwarding from 127.0.0.1:8080 -> 8080 Forwarding from [::1]:8080 -> 8080 ``` > 既存のプロセスが8080番ポートを使用している場合はエラーになります。`kill $(lsof -i:8080 | awk 'NR>1 { print $2}')`等でプロセスを終了してください。 ターミナルから[http://localhost:8080/actuator/health](http://localhost:8080/actuator/health)にアクセスして下さい。 ``` curl http://localhost:8080/actuator/health ``` 出力結果 ```json { "status" : "UP" } ``` `kubectl delete`で作成したリソースを削除してください。 ``` kubectl delete -f hello-pks-pod.yml ``` 出力結果 ``` pod "hello-pks" deleted ``` Ctrl+Cで`kubectl port-forward`を止めてください。 ### ReplicaSetのデプロイ ReplicaSetはPodを複製し、複数インスタンスで起動したり、指定したレプリカ数を維持するように管理します。 `hello-pks-rs.yml`を作成して、次の内容を記述してください。 ```yaml apiVersion: apps/v1 kind: ReplicaSet metadata: name: hello-pks spec: replicas: 1 selector: matchLabels: app: hello-pks template: metadata: labels: app: hello-pks spec: containers: - image: making/hello-pks:0.0.1 name: hello-pks ports: - containerPort: 8080 ``` `spec.template`以下が複製するPodの情報です。レプリカ数(`spec.replicas`)が`1`なので、この場合は1Podのみ作成されます。 `spec.template.metadata.labels`に設定したラベルを`spec.selector.matchLabels`に設定して結びつける必要があります。 `kubectl apply`でデプロイします。 ``` kubectl apply -f hello-pks-rs.yml ``` 出力結果 ``` replicaset.apps/hello-pks created ``` `kubectl get pod,rs`でPodとReplicaSetを一覧表示します。 ``` kubectl get pod,rs ``` 出力結果 ``` NAME READY STATUS RESTARTS AGE pod/hello-pks-gvgh6 1/1 Running 0 9s NAME DESIRED CURRENT READY AGE replicaset.extensions/hello-pks 1 1 1 9s ``` 1Pod作成されていることがわかります。Pod名は"`ReplicaSet名`-`一意な文字列`"になっています。 次にレプリカ数を2に変更します。 ```yaml # ... spec: replicas: 2 # ... ``` 再度、`kubectl apply`を実行してください。 ``` kubectl apply -f hello-pks-rs.yml ``` 出力結果 ``` replicaset.apps/hello-pks configured ``` `kubectl get pod,rs`でPodとReplicaSet一覧を確認できます。 ``` kubectl get pod,rs ``` 出力結果 ``` NAME READY STATUS RESTARTS AGE po/hello-pks-dp9tb 1/1 Running 0 15s po/hello-pks-x9gdm 1/1 Running 0 3s NAME DESIRED CURRENT READY AGE rs/hello-pks 2 2 2 23m ``` `hello-pks`から始まる名前を持つPodが2つ作成されていることがわかります。 次は`-o wide`をつけて実行してください。 ``` $ kubectl get pod -o wide ``` 出力結果 ``` NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES hello-pks-gvgh6 1/1 Running 0 93s 10.200.74.53 ip-10-0-8-6.ap-northeast-1.compute.internal hello-pks-r7qx6 1/1 Running 0 52s 10.200.6.17 ip-10-0-9-4.ap-northeast-1.compute.internal ``` `NODE`列にPodが起動しているWorker Nodeが表示されています。Nodeが複数台ある場合は、`NODE`列に別々の値が表示されるかもしれません。 ここでPodを一つ削除して見ます。 ``` kubectl delete pod hello-pks-gvgh6 ``` 出力結果 ``` pod "hello-pks-gvgh6" deleted ``` この状態で、Pod一覧を表示してください。 ``` kubectl get pod,rs ``` 出力結果 ``` NAME READY STATUS RESTARTS AGE pod/hello-pks-gvgh6 0/1 Terminating 0 29s pod/hello-pks-r7qx6 1/1 Running 0 17s pod/hello-pks-tml22 1/1 Running 0 1s NAME DESIRED CURRENT READY AGE replicaset.extensions/hello-pks 2 2 2 29s ``` 1つPodが削除されるとほぼ同時に新しいPodが起動しています。しばらくすると再びPodが2つ起動している状態になります。 ``` kubectl get pod,rs ``` 出力結果 ``` NAME READY STATUS RESTARTS AGE pod/hello-pks-r7qx6 1/1 Running 0 4m19s pod/hello-pks-tml22 1/1 Running 0 21s NAME DESIRED CURRENT READY AGE replicaset.extensions/hello-pks 2 2 2 5m ``` Podのインスタンス数を管理しているのはReplicaSetであるため、`kubectl`コマンドでPodを意図的に削除してもReplicaSetにより、元々指定していたインスタンス数になるように復元されます。 `kubectl describe rs `でReplicaSetの詳細情報を確認できます。 ``` kubectl describe rs hello-pks ``` 出力結果 ``` Name: hello-pks Namespace: default Selector: app=hello-pks Labels: Annotations: kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"apps/v1","kind":"ReplicaSet","metadata":{"annotations":{},"name":"hello-pks","namespace":"default"},"spec":{"replicas":2,"s... Replicas: 2 current / 2 desired Pods Status: 2 Running / 0 Waiting / 0 Succeeded / 0 Failed Pod Template: Labels: app=hello-pks Containers: hello-pks: Image: making/hello-pks:0.0.1 Port: 8080/TCP Host Port: 0/TCP Environment: Mounts: Volumes: Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal SuccessfulCreate 8m20s replicaset-controller Created pod: hello-pks-gvgh6 Normal SuccessfulCreate 7m39s replicaset-controller Created pod: hello-pks-r7qx6 Normal SuccessfulCreate 3m41s replicaset-controller Created pod: hello-pks-tml22 ``` `Events:`にReplicaSetが作成したPodの情報が記載されています。 作成したReplicaSetは削除してください。 ``` kubectl delete -f hello-pks-rs.yml ``` 出力結果 ``` replicaset.apps "hello-pks" deleted ``` ### Deploymentのデプロイ DeploymentはReplicaSetを管理し、ローリングアップデートできるようにします。 `hello-pks.yml`を作成して、次の内容を記述してください。(`hello-pks-rs.yml`との違いは`kind`が`ReplicaSet`から`Deployment`になっただけです。) ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: hello-pks spec: replicas: 2 selector: matchLabels: app: hello-pks template: metadata: labels: app: hello-pks spec: containers: - image: making/hello-pks:0.0.1 name: hello-pks ports: - containerPort: 8080 ``` `kubectl apply`でデプロイします。 ``` kubectl apply -f hello-pks.yml ``` 出力結果 ``` deployment.apps/hello-pks created ``` `kubectl get pod,rs,deploy`でPodとReplicaSetおよびDeployment一覧を確認できます。 ``` kubectl get pod,rs,deploy ``` 出力結果 ``` NAME READY STATUS RESTARTS AGE pod/hello-pks-8658768855-526jn 1/1 Running 0 10s pod/hello-pks-8658768855-6h7sp 1/1 Running 0 10s NAME DESIRED CURRENT READY AGE replicaset.extensions/hello-pks-8658768855 2 2 2 11s NAME READY UP-TO-DATE AVAILABLE AGE deployment.extensions/hello-pks 2/2 2 2 11s ``` 名前が`Deployment名`-`一意な文字列`であるReplicaSetができていることがわかります。そしてこのReplicaSetによって2つのPodができていることがわかります。 `kubectl describe `でDeploymentの詳細情報を確認できます。 ``` kubectl describe deploy hello-pks ``` 出力結果 ``` Name: hello-pks Namespace: default CreationTimestamp: Mon, 02 Sep 2019 02:09:59 +0900 Labels: Annotations: deployment.kubernetes.io/revision: 1 kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"name":"hello-pks","namespace":"default"},"spec":{"replicas":2,"s... Selector: app=hello-pks Replicas: 2 desired | 2 updated | 2 total | 2 available | 0 unavailable StrategyType: RollingUpdate MinReadySeconds: 0 RollingUpdateStrategy: 25% max unavailable, 25% max surge Pod Template: Labels: app=hello-pks Containers: hello-pks: Image: making/hello-pks:0.0.1 Port: 8080/TCP Host Port: 0/TCP Environment: Mounts: Volumes: Conditions: Type Status Reason ---- ------ ------ Available True MinimumReplicasAvailable Progressing True NewReplicaSetAvailable OldReplicaSets: NewReplicaSet: hello-pks-8658768855 (2/2 replicas created) Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal ScalingReplicaSet 35s deployment-controller Scaled up replica set hello-pks-8658768855 to 2 ``` ### Serviceのデプロイ ここまでPodへのアクセスはPort Forwardingを利用して行いましたが、ReplicaSetによって複製されたPodへロードバランスすることはできていません。 PodをKubernetesクラスタ内外に公開し、ロードバランシングの機能も提供するのがServiceです。 ServiceのTypeはいくつか用意されており、次の3つが代表的です。 * `ClusterIP`: クラスタ内にのみ公開される。デフォルトのService Type。 * `NodePort`: Nodeのポートを経由して公開される。クラスタ外からアクセス可能。 * `LoadBalancer`: クラウドプロバイダ等が提供する外部ロードバランサを経由して公開される。ロードバランサは動的に作成される。 `ClusterIP`や`NodePort`は全てのクラスタで利用可能ですが、全てのk8s環境で`LoadBalancer`が対応されているわけではありません。 本資料が想定としている環境の`LoadBalancer`対応状況は次の表の通りです。 | 環境 | NSX-Tなし | NSX-Tあり | | - | - | - | | PKS on vSphere | ❌ | ✅ | | PKS on GCP | ✅ | - | | PKS on AWS | ✅ | - | | PKS on Azure | ✅ | - | ここでは`NodePort`のServiceを作成する例を説明します。 #### Type=NodePort `LoadBalancer`に対応していない環境では`NodePort`のServiceを作成します。 `hello-pks.yml`を次の内容を変更してください。 ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: hello-pks spec: replicas: 2 selector: matchLabels: app: hello-pks template: metadata: labels: app: hello-pks spec: containers: - image: making/hello-pks:0.0.1 name: hello-pks ports: - containerPort: 8080 # ここから追記 --- kind: Service apiVersion: v1 metadata: name: hello-pks spec: type: NodePort selector: app: hello-pks ports: - protocol: TCP port: 8080 ``` `spec.selector`にはPodに設定したラベルを、`spec.ports[].port`には公開対象のPodのポートを指定します。 `kubectl apply`でデプロイしてください。 ``` kubectl apply -f hello-pks.yml ``` 出力結果 ``` deployment.apps/hello-pks unchanged service/hello-pks created ``` `kubectl get service`でService一覧を確認できます。 ``` kubectl get pod,rs,deploy,service ``` 出力結果 ``` NAME READY STATUS RESTARTS AGE pod/hello-pks-8658768855-526jn 1/1 Running 0 4m27s pod/hello-pks-8658768855-6h7sp 1/1 Running 0 4m27s NAME DESIRED CURRENT READY AGE replicaset.extensions/hello-pks-8658768855 2 2 2 4m28s NAME READY UP-TO-DATE AVAILABLE AGE deployment.extensions/hello-pks 2/2 2 2 4m28s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/hello-pks NodePort 10.100.200.190 8080:32569/TCP 15s service/kubernetes ClusterIP 10.100.200.1 443/TCP 230d ``` `svc/hello-pks-service`の`PORT(S)`列の`8080:32569`に注目してください。左がPodのIPで右がNode VMでlistenしているポートです。 この場合は、Node VMのIPの`32569`番ポートにアクセスすると`hello-pks`のPodにルーティングされます。ポートはデフォルトでは`30000`から`32767`で空いているポートがランダムで選択されます。 Node上のポートを固定したい場合は、次のように指定可能です。 ```yaml spec: type: NodePort selector: app: hello-pks ports: - protocol: TCP port: 8080 nodePort: 32569 ``` Node VMのIPは`kubectl get node -o wide`の結果の`EXTERNAL-IP`列から取得できます。 Node VMが複数ある場合は、どのNode VMにアクセスしても指定のPodへルーティングされます。 > PKS on GCPやGKEでNode VMにインターネットからアクセスする場合は、Firewallの設定を変更してポートをポートを開放する必要があります。 > `gcloud compute instances list --format=json`でNode VMに設定されているNetwork Tagを取得し、 > `gcloud compute firewall-rules create k8s-allow-node-port --allow tcp:30000-32767 --target-tags --network ` Node VMの`32569`ポートにアクセスします。Nodeが複数ある場合はどれか一つのIPアドレスを選んでください。 ``` curl http://:32569/actuator/health ``` 出力結果 ``` { "status" : "UP" } ``` #### Type=LoadBalancer `LoadBalancer`に対応している環境では`LoadBalancer`のServiceを作成します。 `hello-pks.yml`を次の内容を変更してください。 ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: hello-pks spec: replicas: 2 selector: matchLabels: app: hello-pks template: metadata: labels: app: hello-pks spec: containers: - image: making/hello-pks:0.0.1 name: hello-pks ports: - containerPort: 8080 # ここから追記 --- kind: Service apiVersion: v1 metadata: name: hello-pks spec: type: LoadBalancer selector: app: hello-pks ports: - protocol: TCP port: 8080 ``` `spec.selector`にはPodに設定したラベルを、`spec.ports[].port`には公開対象のPodのポートを指定します。 `kubectl apply`でデプロイしてください。 ``` kubectl apply -f hello-pks.yml ``` 出力結果 ``` deployment.apps/hello-pks unchanged service/hello-pks configured ``` `kubectl get service`でService一覧を確認できます。 ``` kubectl get pod,rs,deploy,service ``` 出力結果 ``` NAME READY STATUS RESTARTS AGE pod/hello-pks-8658768855-526jn 1/1 Running 0 8m43s pod/hello-pks-8658768855-6h7sp 1/1 Running 0 8m43s NAME DESIRED CURRENT READY AGE replicaset.extensions/hello-pks-8658768855 2 2 2 8m44s NAME READY UP-TO-DATE AVAILABLE AGE deployment.extensions/hello-pks 2/2 2 2 8m44s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/hello-pks LoadBalancer 10.100.200.190 aeb43003accdb11e99cb7066f53a5a1a-2093361350.ap-northeast-1.elb.amazonaws.com 8080:32569/TCP 4m31s service/kubernetes ClusterIP 10.100.200.1 ``` `service/hello-pks`の`EXTERNAL-IP`列にアクセス可能なLoad BalancerのIPアドレスまたはホスト名が表示されます。 このIPアドレスがアタッチされた外部のLoadBalancerを経由してServiceにアクセス可能になりました。 ``になっている場合はLoad Balancer作成中またはエラーが起きてLoad Balancerの作成に失敗しています。`kubectl describe service hello-pkd`で状態を確認してください。 `curl`で`http://:8080/actuator/health`にアクセスしてください。 ``` curl http://aeb43003accdb11e99cb7066f53a5a1a-2093361350.ap-northeast-1.elb.amazonaws.com:8080/actuator/health ``` 出力結果 ```json { "status" : "UP" } ``` #### Node/Pod情報の表示 このままではロードバランスがされていることがわからないため、アクセスしているNodeとPodの情報をレスポンスボディに含めるようにします。 `making/hello-pks`アプリは環境変数が`NODE`から始まるものと`POD`から始まるものがレスポンスボディに含まれるように作られています。 Podの環境変数にNodeとPodの情報を埋め込みます。NodeやPodの情報は[Download API](https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/)を使って取得できます。 `hello-pks.yml`を次のように修正してください。 ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: hello-pks spec: replicas: 2 selector: matchLabels: app: hello-pks template: metadata: labels: app: hello-pks spec: containers: - image: making/hello-pks:0.0.1 name: hello-pks ports: - containerPort: 8080 # ここから追加 env: - name: NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName - name: NODE_IP valueFrom: fieldRef: fieldPath: status.hostIP - name: POD_NAME valueFrom: fieldRef: fieldPath: metadata.name - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace - name: POD_IP valueFrom: fieldRef: fieldPath: status.podIP # ここまで --- kind: Service apiVersion: v1 metadata: name: hello-pks spec: type: LoadBalancer selector: app: hello-pks ports: - protocol: TCP port: 8080 ``` `kubectl apply`で変更内容を適用します。 ``` kubectl apply -f hello-pks.yml ``` 出力結果 ``` deployment.apps/hello-pks configured service/hello-pks unchanged ``` `kubectl get pod`でPodの`STATUS`が`Running`になっていることを確認したら、`curl`で、 * NodePortの場合`http://:` * LoadBalancerの場合`http://:8080` にアクセスしてください。 ``` curl http://aeb43003accdb11e99cb7066f53a5a1a-2093361350.ap-northeast-1.elb.amazonaws.com:8080 ``` 出力結果 ```json { "node" : { "NODE_IP" : "10.0.8.6", "NODE_NAME" : "ip-10-0-8-6.ap-northeast-1.compute.internal" }, "pod" : { "POD_IP" : "10.200.74.57", "POD_NAME" : "hello-pks-795466b574-lfr2z", "POD_NAMESPACE" : "default" }, "container" : { } } ``` NodeとPodの情報が返ります。 何回かアクセスして、異なるPodの情報が表示されることを確認してください。 ```json { "node" : { "NODE_IP" : "10.0.8.6", "NODE_NAME" : "ip-10-0-8-6.ap-northeast-1.compute.internal" }, "pod" : { "POD_IP" : "10.200.74.56", "POD_NAME" : "hello-pks-795466b574-75dcz", "POD_NAMESPACE" : "default" }, "container" : { } } ``` WebブラウザでアクセスするとHTMLで表示されます。 ![image](https://user-images.githubusercontent.com/106908/64079955-6e561800-cd29-11e9-98eb-ac801af1388a.png) "SHUTDOWN"ボタンを押すとアプリケーションが終了します。ボタンを押したあとはReplicaSetによってPodが再作成されますが、 それまでは1 Podだけでリクエストを捌きます。"SHUTDOWN"ボタンを押した後、別のPodの情報が表示されることを確認してください。 `kubectl delete`でdeplymentとserviceを削除してください。 ``` kubectl delete -f hello-pks.yml ``` 出力結果 ``` deployment.apps "hello-pks" deleted service "hello-pks" deleted ```