eksworkshop.comのApp Meshハンズオン

eksworkshop.comのApp Meshハンズオンをやってみたメモ。

環境

コンポーネント バージョン 備考
eksctl 0.14.0
Kubernetes バージョン 1.14
プラットフォームのバージョン eks.9
Helm v3.1.1
appmesh-controllerチャート 0.4.0
appmesh-controller 0.3.0
appmesh-injectチャート 0.11.0
appmesh-inject 0.4.0

クラスターを作成する。

eksctl create cluster --name=appmesh --version 1.14 --nodes=3 --managed --ssh-access --ssh-public-key=sotosugi

DJ Appのデプロイ

(メモ) Istioのサンプルアプリでは、reviewsのようなDeploymentにreviews-v1のようなバージョンつきの名前を付けていた。Serviceにはバージョン番号なし。

$ k get deploy -n bookinfo
NAME             READY   UP-TO-DATE   AVAILABLE   AGE
details-v1       1/1     1            1           8d
productpage-v1   1/1     1            1           8d
ratings-v1       1/1     1            1           8d
reviews-v1       1/1     1            1           8d
reviews-v2       1/1     1            1           8d
reviews-v3       1/1     1            1           8d
$ k get svc -n bookinfo
NAME          TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
details       ClusterIP   10.100.75.161    <none>        9080/TCP   8d
productpage   ClusterIP   10.100.192.148   <none>        9080/TCP   8d
ratings       ClusterIP   10.100.148.131   <none>        9080/TCP   8d
reviews       ClusterIP   10.100.144.233   <none>        9080/TCP   8d

リポジトリをクローンしてサンプルアプリのディレクトリに移動する。

git clone https://github.com/aws/aws-app-mesh-examples
cd aws-app-mesh-examples/examples/apps/djapp/

Namespaceを作成する。

kubectl apply -n prod \
  -f 1_create_the_initial_architecture/1_prod_ns.yaml

(上記YAMLの内容)

apiVersion: v1
kind: Namespace
metadata:
  name: prod

Deploymentを作成する。

kubectl apply -n prod \
  -f 1_create_the_initial_architecture/1_initial_architecture_deployment.yaml

(上記YAMLの内容)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dj
spec:
  replicas: 1
  selector:
    matchLabels:
      app: dj
      version: v1
  template:
    metadata:
      labels:
        app: dj
        version: v1
    spec:
      containers:
        - name: dj
          image: "672518094988.dkr.ecr.us-west-2.amazonaws.com/hello-world:v1.0"
          imagePullPolicy: Always
          ports:
            - containerPort: 9080
          env:
            - name: "HW_RESPONSE"
              value: "DJ Reporting for duty!"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: metal-v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: metal
      version: v1
  template:
    metadata:
      labels:
        app: metal
        version: v1
    spec:
      containers:
        - name: metal
          image: "672518094988.dkr.ecr.us-west-2.amazonaws.com/hello-world:v1.0"
          imagePullPolicy: Always
          ports:
            - containerPort: 9080
          env:
            - name: "HW_RESPONSE"
              value: "[\"Megadeth\",\"Judas Priest\"]"

---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: jazz-v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jazz
      version: v1
  template:
    metadata:
      labels:
        app: jazz
        version: v1
    spec:
      containers:
        - name: jazz
          image: "672518094988.dkr.ecr.us-west-2.amazonaws.com/hello-world:v1.0"
          imagePullPolicy: Always
          ports:
            - containerPort: 9080
          env:
            - name: "HW_RESPONSE"
              value: "[\"Astrud Gilberto\",\"Miles Davis\"]"

Serviceを作成する。

kubectl apply -n prod \
  -f 1_create_the_initial_architecture/1_initial_architecture_services.yaml

(上記YAMLの内容)

apiVersion: v1
kind: Service
metadata:
  name: dj
  labels:
    app: dj
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: dj
---
apiVersion: v1
kind: Service
metadata:
  name: metal-v1
  labels:
    app: metal
    version: v1
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: metal
    version: v1

---
apiVersion: v1
kind: Service
metadata:
  name: jazz-v1
  labels:
    app: jazz
    version: v1
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: jazz
    version: v1

確認する。

$ kubectl -n prod get pods,deploy,service
NAME                           READY   STATUS    RESTARTS   AGE
pod/dj-8d4fc6ccd-nkhxn         1/1     Running   0          6m1s
pod/jazz-v1-f94cdc64d-45kbv    1/1     Running   0          6m1s
pod/metal-v1-654d4858f-cgcpl   1/1     Running   0          6m1s

NAME                             READY   UP-TO-DATE   AVAILABLE   AGE
deployment.extensions/dj         1/1     1            1           6m1s
deployment.extensions/jazz-v1    1/1     1            1           6m1s
deployment.extensions/metal-v1   1/1     1            1           6m1s

NAME               TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
service/dj         ClusterIP   10.100.99.124   <none>        9080/TCP   6m
service/jazz-v1    ClusterIP   10.100.74.238   <none>        9080/TCP   6m
service/metal-v1   ClusterIP   10.100.202.57   <none>        9080/TCP   6m

Istioのサンプルと異なり、Serviceの名前にバージョンがついている。AppMeshの場合はVirtualNodeという抽象化が増えるから?

DJのPodに入って動きを確認する。

export DJ_POD_NAME=$(kubectl get pods -n prod -l app=dj -o jsonpath='{.items[].metadata.name}')
kubectl exec -n prod -it ${DJ_POD_NAME} bash

バックエンドにリクエストを投げる。

root@dj-8d4fc6ccd-nkhxn:/usr/src/app# curl -s jazz-v1:9080 | json_pp
[
   "Astrud Gilberto",
   "Miles Davis"
]
root@dj-8d4fc6ccd-nkhxn:/usr/src/app# curl -s metal-v1:9080 | json_pp
[
   "Megadeth",
   "Judas Priest"
]

コンテナをexitする。

ポートフォワードして確認する。

k port-forward svc/dj -n prod 8888:9080

別ターミナルでcurlする。djは外からのリクエストを受けてバックエンドにリクエストを投げる機能は持っていないように見える。

$ curl -w '\n' localhost:8888
DJ Reporting for duty!

ポートフォワードは終了する。

コンテナの中でソースを確認するとこれだけで、実際のマイクロサービスをイメージしたようなものではなく、ただのクライアントとなるPodである。

root@dj-8d4fc6ccd-nkhxn:/usr/src/app# cat server.js
const express = require('express')
const app = express()
const port = 9080

const response = process.env.HW_RESPONSE ? process.env.HW_RESPONSE : "hell no world"

app.get('/', (req, res) => res.send(response))

app.listen(port, () => console.log(`Example app listening on port ${port}!`))

App Meshのデプロイ

helmのバージョンを確認する。

$ helm version --short
v3.1.1+gafe7058

リポジトリを追加する。

helm repo add eks https://aws.github.io/eks-charts

追加したリポジトリを確認する。

$ helm repo list | grep eks-charts
eks     https://aws.github.io/eks-charts

チャートを確認する。

$ helm search repo appmesh
NAME                    CHART VERSION   APP VERSION DESCRIPTION
eks/appmesh-controller  0.4.0            0.3.0        App Mesh controller Helm chart for Kubernetes
eks/appmesh-grafana     0.1.0            6.4.3        App Mesh Grafana Helm chart for Kubernetes
eks/appmesh-inject      0.11.0           0.4.0        App Mesh Inject Helm chart for Kubernetes
eks/appmesh-jaeger      0.2.0            1.14.0       App Mesh Jaeger Helm chart for Kubernetes
eks/appmesh-prometheus  0.3.0            2.13.1       App Mesh Prometheus Helm chart for Kubernetes

Namespaceを作成する。

kubectl create ns appmesh-system

App MeshのCRDをデプロイする。

kubectl apply -f https://raw.githubusercontent.com/aws/eks-charts/master/stable/appmesh-controller/crds/crds.yaml

CRDを確認する。

$ kubectl get crd | grep appmesh
meshes.appmesh.k8s.aws             2020-03-12T02:39:40Z
virtualnodes.appmesh.k8s.aws       2020-03-12T02:39:40Z
virtualservices.appmesh.k8s.aws    2020-03-12T02:39:40Z

IRSAを使ってappmesh-controllerAWSAppMeshFullAccessAWSCloudMapFullAccessの権限を付与する。

まずOIDC identity providerを作成する。

eksctl utils associate-iam-oidc-provider \
  --cluster appmesh \
  --approve

確認する。

$ ARN=$(aws iam list-open-id-connect-providers | jq -r '.OpenIDConnectProviderList[].Arn')
$ aws iam get-open-id-connect-provider --open-id-connect-provider-arn ${ARN}
{
    "Url": "oidc.eks.ap-northeast-1.amazonaws.com/id/6D19363CA97FEB75CEEC9713BCB86EFB",
    "ClientIDList": [
        "sts.amazonaws.com"
    ],
    "ThumbprintList": [
        "9e99a48a9960b14926bb7f3b02e22da2b0ab7280"
    ],
    "CreateDate": "2020-03-12T02:43:44.256000+00:00"
}

IAMロールとServiceAccountを作成する。

eksctl create iamserviceaccount \
  --cluster appmesh \
  --namespace appmesh-system \
  --name appmesh-controller \
  --attach-policy-arn arn:aws:iam::aws:policy/AWSCloudMapFullAccess,arn:aws:iam::aws:policy/AWSAppMeshFullAccess \
  --override-existing-serviceaccounts \
  --approve

確認する。

$ aws iam list-roles | jq '.Roles[] | select( .RoleName | test("iamserviceaccount") )'
{
  "Path": "/",
  "RoleName": "eksctl-appmesh-addon-iamserviceaccount-appme-Role1-1BSXGNXTRW623",
  "RoleId": "AROAYXO4ULJGQYPSQJGFH",
  "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/eksctl-appmesh-addon-iamserviceaccount-appme-Role1-1BSXGNXTRW623",
  "CreateDate": "2020-03-12T02:44:07+00:00",
  "AssumeRolePolicyDocument": {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Principal": {
          "Federated": "arn:aws:iam::XXXXXXXXXXXX:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/6D19363CA97FEB75CEEC9713BCB86EFB"
        },
        "Action": "sts:AssumeRoleWithWebIdentity",
        "Condition": {
          "StringEquals": {
            "oidc.eks.ap-northeast-1.amazonaws.com/id/6D19363CA97FEB75CEEC9713BCB86EFB:aud": "sts.amazonaws.com",
            "oidc.eks.ap-northeast-1.amazonaws.com/id/6D19363CA97FEB75CEEC9713BCB86EFB:sub": "system:serviceaccount:appmesh-system:appmesh-controller"
          }
        }
      }
    ]
  },
  "Description": "",
  "MaxSessionDuration": 3600
}
$ k get sa -n appmesh-system
NAME                 SECRETS   AGE
appmesh-controller   1         12m
default              1         18m

App Mesh Controllerをデプロイする。なおリージョンの設定を省略したらコントローラーによるAWSリソースの作成がエラーになったので指定は必須。

helm upgrade -i appmesh-controller eks/appmesh-controller \
  --namespace appmesh-system \
  --set region=ap-northeast-1 \
  --set serviceAccount.create=false \
  --set serviceAccount.name=appmesh-controller

App Mesh Sidecar Injectorをデプロイする。オプションでmeshを作っている。

export APPMESH_NAME="dj-app"
helm upgrade -i appmesh-inject eks/appmesh-inject \
  --namespace appmesh-system \
  --set mesh.name=${APPMESH_NAME} \
  --set mesh.create=true

デプロイされたコンポーネントを確認する。

$ kubectl -n appmesh-system get deploy,pods,service
NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.extensions/appmesh-controller   1/1     1            1           6m32s
deployment.extensions/appmesh-inject       1/1     1            1           41s

NAME                                      READY   STATUS    RESTARTS   AGE
pod/appmesh-controller-6b57d964c6-h9f9h   1/1     Running   0          6m32s
pod/appmesh-inject-78844456d7-9w48q       1/1     Running   0          41s

NAME                     TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
service/appmesh-inject   ClusterIP   10.100.144.199   <none>        443/TCP   41s

Sidecar InjectorにServiceがあるのはAdmission Webhookを使っているからと思われる。

Meshリソースもできている。このリソースはNamespacesスコープではない。

$ k get mesh
NAME     AGE
dj-app   2m52s
$ aws appmesh list-meshes
{
    "meshes": [
        {
            "arn": "arn:aws:appmesh:ap-northeast-1:XXXXXXXXXXXX:mesh/dj-app",
            "meshName": "dj-app"
        }
    ]
}

prod Namespaceにラベルをつけてサイドカーのオートインジェクションを有効にする。

kubectl label \
  namespace prod appmesh.k8s.aws/sidecarInjectorWebhook=enabled

DJ AppのApp Mesh対応

VirtualNodeの作成

VirtualNodeを作成する。 もともとmetaljazzというKubernetes Serviveは存在しておらず、新たに仮想的なノードを作成しているイメージ。 このVirtualNodeを使うことはなく、この定義は不要に思える。

kubectl create -f 4_create_initial_mesh_components/nodes_representing_virtual_services.yaml

(上記YAMLの内容)

apiVersion: appmesh.k8s.aws/v1beta1
kind: VirtualNode
metadata:
  name: metal
  namespace: prod
spec:
  meshName: dj-app
  listeners:
    - portMapping:
        port: 9080
        protocol: http
  serviceDiscovery:
    dns:
      hostName: metal.prod.svc.cluster.local
---
apiVersion: appmesh.k8s.aws/v1beta1
kind: VirtualNode
metadata:
  name: jazz
  namespace: prod
spec:
  meshName: dj-app
  listeners:
    - portMapping:
        port: 9080
        protocol: http
  serviceDiscovery:
    dns:
      hostName: jazz.prod.svc.cluster.local

さらにVirtualNodeを作成する。今度はもともと存在しているdjmetal-v1jazz-v1というKubernetes Serviveを示す仮想的なノード。 djに関してはバックエンドを定義している。これによってdjはこのバックエンドにしかリクエストできない。

kubectl create -f 4_create_initial_mesh_components/nodes_representing_physical_services.yaml

(上記YAMLの内容)

apiVersion: appmesh.k8s.aws/v1beta1
kind: VirtualNode
metadata:
  name: dj
  namespace: prod
spec:
  meshName: dj-app
  listeners:
    - portMapping:
        port: 9080
        protocol: http
  serviceDiscovery:
    dns:
      hostName: dj.prod.svc.cluster.local
  backends:
    - virtualService:
        virtualServiceName: jazz.prod.svc.cluster.local
    - virtualService:
        virtualServiceName: metal.prod.svc.cluster.local
---
apiVersion: appmesh.k8s.aws/v1beta1
kind: VirtualNode
metadata:
  name: jazz-v1
  namespace: prod
spec:
  meshName: dj-app
  listeners:
    - portMapping:
        port: 9080
        protocol: http
  serviceDiscovery:
    dns:
      hostName: jazz-v1.prod.svc.cluster.local
---
apiVersion: appmesh.k8s.aws/v1beta1
kind: VirtualNode
metadata:
  name: metal-v1
  namespace: prod
spec:
  meshName: dj-app
  listeners:
    - portMapping:
        port: 9080
        protocol: http
  serviceDiscovery:
    dns:
      hostName: metal-v1.prod.svc.cluster.local

VirtualNodeを確認する。

$ k get virtualnode -n prod
NAME       AGE
dj         27m
jazz       30m
jazz-v1    27m
metal      30m
metal-v1   27m

VirtualServiceの作成

VirtualServiceを作成する。

kubectl apply -n prod -f 4_create_initial_mesh_components/virtual-services.yaml

(上記YAMLの内容)

apiVersion: appmesh.k8s.aws/v1beta1
kind: VirtualService
metadata:
  name: jazz.prod.svc.cluster.local
  namespace: prod
spec:
  meshName: dj-app
  virtualRouter:
    name: jazz-router
  routes:
    - name: jazz-route
      http:
        match:
          prefix: /
        action:
          weightedTargets:
            - virtualNodeName: jazz-v1
              weight: 100

---
apiVersion: appmesh.k8s.aws/v1beta1
kind: VirtualService
metadata:
  name: metal.prod.svc.cluster.local
  namespace: prod
spec:
  meshName: dj-app
  virtualRouter:
    name: metal-router
  routes:
    - name: metal-route
      http:
        match:
          prefix: /
        action:
          weightedTargets:
            - virtualNodeName: metal-v1
              weight: 100

確認する。

$ k get virtualservice -n prod
NAME                           AGE
jazz.prod.svc.cluster.local    17s
metal.prod.svc.cluster.local   17s

これによって、以下が定義される。

  • jazz.prod.svc.cluster.localへのリクエストをjazz-v1のVirtualNodeへルーティング
  • metal.prod.svc.cluster.localへのリクエストをmetal-v1のVirtualNodeへルーティング

クライアントはこのDNS名の名前解決を最初に行うが、jazzmetalというKubernetes Serviceはないので名前解決できない。

名前解決をできるようにするため、セレクタなしのServiceを定義する。

kubectl -n prod create -f 4_create_initial_mesh_components/metal_and_jazz_placeholder_services.yaml

(上記YAMLの内容)

apiVersion: v1
kind: Service
metadata:
  name: jazz
  labels:
    app: jazz
spec:
  ports:
  - port: 9080
    name: http
---
apiVersion: v1
kind: Service
metadata:
  name: metal
  labels:
    app: metal
spec:
  ports:
  - port: 9080
    name: http

確認する。

$ k get svc -n prod
NAME       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
dj         ClusterIP   10.100.99.124    <none>        9080/TCP   159m
jazz       ClusterIP   10.100.199.235   <none>        9080/TCP   3s
jazz-v1    ClusterIP   10.100.74.238    <none>        9080/TCP   159m
metal      ClusterIP   10.100.164.219   <none>        9080/TCP   3s
metal-v1   ClusterIP   10.100.202.57    <none>        9080/TCP   159m

サイドカーのインジェクション

Sidecar InjectorはPodの作成時点でインジェクションを行うため、Sidecar Injectorを作成する前にデプロイした現在のアプリにはサイドカーが入っていない。

$ k get po -n prod
NAME                       READY   STATUS    RESTARTS   AGE
dj-8d4fc6ccd-nkhxn         1/1     Running   0          170m
jazz-v1-f94cdc64d-45kbv    1/1     Running   0          170m
metal-v1-654d4858f-cgcpl   1/1     Running   0          170m

Deploymentにアノテーションを追加することでPodを再作成させる。なお、単純にPodを削除してもよい。

export TIMESTAMP=$(date +%s)
kubectl -n prod patch deployment dj \
  -p "{\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"date\":\"${TIMESTAMP}\"}}}}}"
kubectl -n prod patch deployment metal-v1 \
 -p "{\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"date\":\"${TIMESTAMP}\"}}}}}"
kubectl -n prod patch deployment jazz-v1 -nprod \
  -p "{\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"date\":\"${TIMESTAMP}\"}}}}}"

サイドカーが入ったことを確認する。

$ k get po -n prod
NAME                       READY   STATUS    RESTARTS   AGE
dj-869b4b8db-mj6jz         2/2     Running   0          43s
jazz-v1-784f9d4fd8-ctv6d   2/2     Running   0          37s
metal-v1-877db99d5-twcws   2/2     Running   0          42s

動作確認

DJのPodに入って動きを確認する。

export DJ_POD_NAME=$(kubectl get pods -n prod -l app=dj -o jsonpath='{.items[].metadata.name}')
kubectl -n prod exec -it ${DJ_POD_NAME} -c dj bash

バックエンドにリクエストを投げる。

root@dj-869b4b8db-mj6jz:/usr/src/app# curl -s jazz.prod.svc.cluster.local:9080 | json_pp
[
   "Astrud Gilberto",
   "Miles Davis"
]
root@dj-869b4b8db-mj6jz:/usr/src/app# curl -s metal.prod.svc.cluster.local:9080 | json_pp
[
   "Megadeth",
   "Judas Priest"
]

App Mesh導入前と同じ動作。

exitする。

カナリアリリース

v2へのカナリアリリースをテストする。

jazz-v2

jazz-v2のDeploymentとServiceとVirtualNodeをデプロイする。

kubectl -n prod apply -f 5_canary/jazz_v2.yaml

(上記YAMLの内容)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: jazz-v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: jazz
      version: v2
  template:
    metadata:
      labels:
        app: jazz
        version: v2
    spec:
      containers:
        - name: jazz
          image: "672518094988.dkr.ecr.us-west-2.amazonaws.com/hello-world:v1.0"
          ports:
            - containerPort: 9080
          env:
            - name: "HW_RESPONSE"
              value: "[\"Astrud Gilberto (Bahia, Brazil)\",\"Miles Davis (Alton, Illinois)\"]"

---
apiVersion: v1
kind: Service
metadata:
  name: jazz-v2
  labels:
    app: jazz
    version: v2
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: jazz
    version: v2
---
apiVersion: appmesh.k8s.aws/v1beta1
kind: VirtualNode
metadata:
  name: jazz-v2
  namespace: prod
spec:
  meshName: dj-app
  listeners:
    - portMapping:
        port: 9080
        protocol: http
  serviceDiscovery:
    dns:
      hostName: jazz-v2.prod.svc.cluster.local

jazzのVirtualServiceを更新し、振り分けの重みを変更する。

kubectl -n prod apply -f 5_canary/jazz_service_update.yaml

変更後の定義を確認する。

$ k get virtualservice -n prod jazz.prod.svc.cluster.local -o json | jq '.spec.routes[].http.action'
{
  "weightedTargets": [
    {
      "virtualNodeName": "jazz-v1",
      "weight": 90
    },
    {
      "virtualNodeName": "jazz-v2",
      "weight": 10
    }
  ]
}

metal-v2

metal-v2のDeploymentとServiceとVirtualNodeをデプロイする。

kubectl -n prod apply  -f 5_canary/metal_v2.yaml

(上記YAMLの内容)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: metal-v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: metal
      version: v2
  template:
    metadata:
      labels:
        app: metal
        version: v2
    spec:
      containers:
        - name: metal
          image: "672518094988.dkr.ecr.us-west-2.amazonaws.com/hello-world:v1.0"
          ports:
            - containerPort: 9080
          env:
            - name: "HW_RESPONSE"
              value: "[\"Megadeth (Los Angeles, California)\",\"Judas Priest (West Bromwich, England)\"]"

---
apiVersion: v1
kind: Service
metadata:
  name: metal-v2
  labels:
    app: metal
    version: v2
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: metal
    version: v2


---
apiVersion: appmesh.k8s.aws/v1beta1
kind: VirtualNode
metadata:
  name: metal-v2
  namespace: prod
spec:
  meshName: dj-app
  listeners:
    - portMapping:
        port: 9080
        protocol: http
  serviceDiscovery:
    dns:
      hostName: metal-v2.prod.svc.cluster.local

metalのVirtualServiceを更新し、振り分けの重みを変更する。

kubectl -n prod apply -f 5_canary/metal_service_update.yaml

変更後の定義を確認する。

$ k get virtualservice -n prod metal.prod.svc.cluster.local -o json | jq '.spec.routes[].http.action'
{
  "weightedTargets": [
    {
      "virtualNodeName": "metal-v1",
      "weight": 50
    },
    {
      "virtualNodeName": "metal-v2",
      "weight": 50
    }
  ]
}

動作確認

DJのPodに入って動きを確認する。

export DJ_POD_NAME=$(kubectl get pods -n prod -l app=dj -o jsonpath='{.items[].metadata.name}')
kubectl -n prod exec -it ${DJ_POD_NAME} -c dj bash

metalをテストする。

while true; do
  curl http://metal.prod.svc.cluster.local:9080/
  echo
  sleep .5
done

50:50でルーティングされていることを確認する。

$ kubectl -n prod exec -it ${DJ_POD_NAME} -c dj bash
root@dj-869b4b8db-mj6jz:/usr/src/app# while true; do
>   curl http://metal.prod.svc.cluster.local:9080/
>   echo
>   sleep .5
> done
["Megadeth (Los Angeles, California)","Judas Priest (West Bromwich, England)"]
["Megadeth (Los Angeles, California)","Judas Priest (West Bromwich, England)"]
["Megadeth","Judas Priest"]
["Megadeth","Judas Priest"]
["Megadeth","Judas Priest"]
["Megadeth","Judas Priest"]
["Megadeth","Judas Priest"]
["Megadeth (Los Angeles, California)","Judas Priest (West Bromwich, England)"]
["Megadeth (Los Angeles, California)","Judas Priest (West Bromwich, England)"]
["Megadeth (Los Angeles, California)","Judas Priest (West Bromwich, England)"]
["Megadeth","Judas Priest"]
["Megadeth","Judas Priest"]
["Megadeth (Los Angeles, California)","Judas Priest (West Bromwich, England)"]
["Megadeth","Judas Priest"]
["Megadeth","Judas Priest"]

jazzをテストする。

while true; do
  curl http://jazz.prod.svc.cluster.local:9080/
  echo
  sleep .5
done

90:10でルーティングされていることを確認する。

root@dj-869b4b8db-mj6jz:/usr/src/app# while true; do
>   curl http://jazz.prod.svc.cluster.local:9080/
>   echo
>   sleep .5
> done
["Astrud Gilberto","Miles Davis"]
["Astrud Gilberto","Miles Davis"]
["Astrud Gilberto","Miles Davis"]
["Astrud Gilberto (Bahia, Brazil)","Miles Davis (Alton, Illinois)"]
["Astrud Gilberto","Miles Davis"]
["Astrud Gilberto","Miles Davis"]
["Astrud Gilberto","Miles Davis"]
["Astrud Gilberto","Miles Davis"]
["Astrud Gilberto","Miles Davis"]
["Astrud Gilberto","Miles Davis"]
["Astrud Gilberto","Miles Davis"]
["Astrud Gilberto","Miles Davis"]
["Astrud Gilberto","Miles Davis"]
["Astrud Gilberto (Bahia, Brazil)","Miles Davis (Alton, Illinois)"]
["Astrud Gilberto","Miles Davis"]

メモ

IstioとAppMeshの違いに関するメモ。

IstioのCRD 説明
Gateway Istioでは外部LoadBalancerからのリクエストはistio-ingressgatewayというServiceが受け取るが、このリソースでistio-ingressgatewayの設定を行い、どのようなリクエストを受け取るかを定義する。
VirtualService ルーティングルールを定義する。サブセットへの振り分けが可能。
DestinationRule サービスまでルーティングされた後のポリシーを定義する。ラベルを使ってサブセットを定義できる。
App MeshのCRD 説明
Mesh AWS側の「メッシュ」リソースに対応。
VirtualNode 実際のエンドポイントを指し示す仮想的なエンドポイント。エンドポイントにはKubernetes ServiceのDNS名を指定する。AWS側の「仮想ノード」リソースに対応。
VirtualService ルーティングルールを定義する。AWS側のリソースとしては、「仮想サービス」に加え、「仮想ルーター」と「ルート」の定義も含まれる。

メッシュへの入口

Istioにはメッシュへの入口となるIngress Gatewayがあるが、App Meshにはない。 メッシュの中へ入れる外部公開されたServiceは自分で定義する。Istioでも外部公開されたServiceを定義すれば同じようにメッシュの中に入れると思われる。

Serviceを作成する単位の違い

Istioのサンプルではのスタート地点では、 - バージョン毎のDeployment - バージョンによらない単一のService を用意している。 これに対して、Service内でのバージョンを定義するDestinationRuleを作成し、振り分けルールを定義するVirtualServiceを定義する。

一方AppMeshのサンプルのスタート地点では、 - バージョン毎のDeployment - バージョン毎のService を用意している。 これに対して各バージョンのServiceに対応するVirtualNodeを作成し、振り分けルールを定義するVirtualServiceを作成。名前解決のための単一のServiceをさらに作成する。

これは、VirtulNodeがポイントする先がServiceに対応するホスト名であるため、バージョン毎のService定義が必要なため。

ルーティング定義の違い

両者のVirtualServiceはよく似た概念でどちらもルーティングを定義するが、定義の仕方は違う。 これは前述のKubernetes Serviceを定義する単位に関係する。

(Istio)

VirtualNodeのような概念がなく、DestinationRuleでラベルと使って定義したKubernetes ServiceのサブセットへルーティングをVirtualServiceで指定する。 VirtualServiceが受け取るリクエストのホスト名はspec.hostsで定義する。

(App Mesh)

まずKubernetes ServiceをVirtualNodeとして定義する。 VirtualServiceではVirtualNodeへのルーティングルールを定義する。 VirtualServiceの名前のリクエストを受け取るので、名前自体をFQDNにする方がよい。