Istio 導入への道 - サービス間通信編

前回の続きです。

Istio でのサービス間通信

まあ、ただサービス間で通信するだけなら Istio は不要なわけだけれども、まずはここから。

httpbin をサービスとして deploy

httpbin.org のコンテナは Request Header をそのまま返してくれたりして便利なのでこれをサービスとして deploy します。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin-deployment
  labels:
    app: httpbin
spec:
  replicas: 2
  selector:
    matchLabels:
      app: httpbin
  template:
    metadata:
      labels:
        app: httpbin
    spec:
      containers:
      - name: httpbin
        image: kennethreitz/httpbin:latest
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
        readinessProbe:
          httpGet:
            path: /status/200
            port: 80
          initialDelaySeconds: 10
          periodSeconds: 5
        livenessProbe:
          httpGet:
            path: /status/200
            port: 80
          initialDelaySeconds: 15
          periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: httpbin-service
spec:
  selector:
    app: httpbin
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 80

不要なんだけど、全部同じ名前にしちゃうとどれとどれが連動しているのかわかりにくくなるので -service とか -deployment とかを名前に入れています。これを httpbin.yaml として保存します。

これに Istio の sidecar を inject するのが istioctl kube-inject です。次のように実行すれば inject 済みの manifest が出力されます。

istioctl kube-inject -f httpbin.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: httpbin
  name: httpbin-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: httpbin
  strategy: {}
  template:
    metadata:
      annotations:
        sidecar.istio.io/interceptionMode: REDIRECT
        sidecar.istio.io/status: '{"version":"1cdb312e0b39910b7401fa37c42113f6436e281598036cb126f9692adebf1545","initContainers":["istio-init"],"containers":["istio-proxy"],"volumes":["istio-envoy","podinfo","istiod-ca-cert"],"imagePullSecrets":null}'
        traffic.sidecar.istio.io/excludeInboundPorts: "15020"
        traffic.sidecar.istio.io/includeInboundPorts: "80"
        traffic.sidecar.istio.io/includeOutboundIPRanges: '*'
      creationTimestamp: null
      labels:
        app: httpbin
        security.istio.io/tlsMode: istio
    spec:
      containers:
      - image: kennethreitz/httpbin:latest
        imagePullPolicy: IfNotPresent
        livenessProbe:
          httpGet:
            path: /status/200
            port: 80
          initialDelaySeconds: 15
          periodSeconds: 5
        name: httpbin
        ports:
        - containerPort: 80
        readinessProbe:
          httpGet:
            path: /status/200
            port: 80
          initialDelaySeconds: 10
          periodSeconds: 5
        resources: {}
      - args:
        - proxy
        - sidecar
        - --domain
        - $(POD_NAMESPACE).svc.cluster.local
        - --configPath
        - /etc/istio/proxy
        - --binaryPath
        - /usr/local/bin/envoy
        - --serviceCluster
        - httpbin.$(POD_NAMESPACE)
        - --drainDuration
        - 45s
        - --parentShutdownDuration
        - 1m0s
        - --discoveryAddress
        - istiod.istio-system.svc:15012
        - --zipkinAddress
        - zipkin.istio-system:9411
        - --proxyLogLevel=warning
        - --proxyComponentLogLevel=misc:error
        - --connectTimeout
        - 10s
        - --proxyAdminPort
        - "15000"
        - --concurrency
        - "2"
        - --controlPlaneAuthPolicy
        - NONE
        - --dnsRefreshRate
        - 300s
        - --statusPort
        - "15020"
        - --trust-domain=cluster.local
        - --controlPlaneBootstrap=false
        env:
        - name: JWT_POLICY
          value: first-party-jwt
        - name: PILOT_CERT_PROVIDER
          value: istiod
        - name: CA_ADDR
          value: istio-pilot.istio-system.svc:15012
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: INSTANCE_IP
          valueFrom:
            fieldRef:
              fieldPath: status.podIP
        - name: SERVICE_ACCOUNT
          valueFrom:
            fieldRef:
              fieldPath: spec.serviceAccountName
        - name: HOST_IP
          valueFrom:
            fieldRef:
              fieldPath: status.hostIP
        - name: ISTIO_META_POD_PORTS
          value: |-
            [
                {"containerPort":80}
            ]            
        - name: ISTIO_META_CLUSTER_ID
          value: Kubernetes
        - name: ISTIO_META_POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: ISTIO_META_CONFIG_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: ISTIO_META_INTERCEPTION_MODE
          value: REDIRECT
        - name: ISTIO_META_WORKLOAD_NAME
          value: httpbin-deployment
        - name: ISTIO_META_OWNER
          value: kubernetes://apis/apps/v1/namespaces/default/deployments/httpbin-deployment
        - name: ISTIO_META_MESH_ID
          value: cluster.local
        image: docker.io/istio/proxyv2:1.5.0
        imagePullPolicy: IfNotPresent
        name: istio-proxy
        ports:
        - containerPort: 15090
          name: http-envoy-prom
          protocol: TCP
        readinessProbe:
          failureThreshold: 30
          httpGet:
            path: /healthz/ready
            port: 15020
          initialDelaySeconds: 1
          periodSeconds: 2
        resources:
          limits:
            cpu: "2"
            memory: 1Gi
          requests:
            cpu: 100m
            memory: 128Mi
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop:
            - ALL
          privileged: false
          readOnlyRootFilesystem: true
          runAsGroup: 1337
          runAsNonRoot: true
          runAsUser: 1337
        volumeMounts:
        - mountPath: /var/run/secrets/istio
          name: istiod-ca-cert
        - mountPath: /etc/istio/proxy
          name: istio-envoy
        - mountPath: /etc/istio/pod
          name: podinfo
      initContainers:
      - command:
        - istio-iptables
        - -p
        - "15001"
        - -z
        - "15006"
        - -u
        - "1337"
        - -m
        - REDIRECT
        - -i
        - '*'
        - -x
        - ""
        - -b
        - '*'
        - -d
        - 15090,15020
        image: docker.io/istio/proxyv2:1.5.0
        imagePullPolicy: IfNotPresent
        name: istio-init
        resources:
          limits:
            cpu: 100m
            memory: 50Mi
          requests:
            cpu: 10m
            memory: 10Mi
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            add:
            - NET_ADMIN
            - NET_RAW
            drop:
            - ALL
          privileged: false
          readOnlyRootFilesystem: false
          runAsGroup: 0
          runAsNonRoot: false
          runAsUser: 0
      securityContext:
        fsGroup: 1337
      volumes:
      - emptyDir:
          medium: Memory
        name: istio-envoy
      - downwardAPI:
          items:
          - fieldRef:
              fieldPath: metadata.labels
            path: labels
          - fieldRef:
              fieldPath: metadata.annotations
            path: annotations
        name: podinfo
      - configMap:
          name: istio-ca-root-cert
        name: istiod-ca-cert
status: {}
---
apiVersion: v1
kind: Service
metadata:
  name: httpbin-service
spec:
  selector:
    app: httpbin
  ports:
  - name: http
    protocol: TCP
    port: 80
    targetPort: 80

よって、これを pipe で kubectl apply に食わせることで deploy できます。kubectl apply -f <(istioctl kube-inject -f ...) でも良いし、もちろんファイルに一旦書き出しても大丈夫。

istioctl kube-inject -f httpbin.yaml | kubectl apply -f -

default namespace に deploy しました。

$ istioctl kube-inject -f httpbin.yaml | kubectl apply -f -
deployment.apps/httpbin-deployment created
service/httpbin-service created

$ kubectl get pods,deployments,services
NAME                                     READY   STATUS    RESTARTS   AGE
pod/httpbin-deployment-9bfd96975-px5lv   2/2     Running   0          88s
pod/httpbin-deployment-9bfd96975-v6mgg   2/2     Running   0          88s

NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.extensions/httpbin-deployment   2/2     2            2           88s

NAME                      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/httpbin-service   ClusterIP   10.109.118.31   80/TCP    88s
service/kubernetes        ClusterIP   10.96.0.1       443/TCP   4h24m 

自動で inject されるようにする

毎回 Deployment を作る度に kube-inject をするのは面倒なので自動化することができる。自動 inject 対象としたい namespace に対して istio-injection=enabled という label をつけるだけで良い。

kubectl label namespace default istio-injection=enabled
$ kubectl get ns default -o yaml
apiVersion: v1
kind: Namespace
metadata:
  creationTimestamp: "2020-03-07T06:44:22Z"
  labels:
    istio-injection: enabled
  name: default
  resourceVersion: "12273"
  selfLink: /api/v1/namespaces/default
  uid: 1c02a69b-5ab5-433a-90be-6c6e3b785867
spec:
  finalizers:
  - kubernetes
status:
  phase: Active

先ほど deploy した httpbin に対してクラスタ内からアクセスするための ubuntu コンテナを Deployment として deploy します。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ubuntu-deployment
  labels:
    app: ubuntu
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ubuntu
  template:
    metadata:
      labels:
        app: ubuntu
    spec:
      containers:
      - name: ubuntu
        image: ubuntu:latest
        imagePullPolicy: IfNotPresent
        command:
        - sleep
        - infinity

この manifest を ubuntu.yaml として保存し、今度は istioctl kube-inject を使わずにそのまま kubectl apply します。それでも自動で inject されるはずです。

$ kubectl apply -f ubuntu.yaml
deployment.apps/ubuntu-deployment created

Deployment の spec には ubuntu コンテナしか書いてなかったのにコンテナの数が2になっています。

$ kubectl get pods -l app=ubuntu
NAME                                READY   STATUS    RESTARTS   AGE
ubuntu-deployment-cc86cc647-vsvbh   2/2     Running   0          21s

istio-proxy というコンテナが追加されています。長いので名前だけ表示。

$ kubectl get pods -l app=ubuntu -o json | jq '.items[].spec.containers[].name'
"ubuntu"
"istio-proxy"

curl でアクセスしてみる

ubuntu Pod から httpbin Service にアクセスしてみます。

$ kubectl exec -it ubuntu-deployment-cc86cc647-vsvbh -c ubuntu -- bash
root@ubuntu-deployment-cc86cc647-vsvbh:/#

curl が入っていないのでインストールする。

root@ubuntu-deployment-cc86cc647-vsvbh# apt-get update && apt-get install -y curl

httpbin-service にアクセスしてみる。

root@ubuntu-deployment-cc86cc647-vsvbh:/# curl -sv http://httpbin-service/headers
*   Trying 10.109.118.31...
* TCP_NODELAY set
* Connected to httpbin-service (10.109.118.31) port 80 (#0)
> GET /headers HTTP/1.1
> Host: httpbin-service
> User-Agent: curl/7.58.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< server: envoy
< date: Sat, 07 Mar 2020 11:33:28 GMT
< content-type: application/json
< content-length: 521
< access-control-allow-origin: *
< access-control-allow-credentials: true
< x-envoy-upstream-service-time: 26
< 
{
  "headers": {
    "Accept": "*/*", 
    "Content-Length": "0", 
    "Host": "httpbin-service", 
    "User-Agent": "curl/7.58.0", 
    "X-B3-Parentspanid": "361390f32cd55bdc", 
    "X-B3-Sampled": "0", 
    "X-B3-Spanid": "f95fbc3875ab93e5", 
    "X-B3-Traceid": "3a3e0cd03f1ded4e361390f32cd55bdc", 
    "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/default/sa/default;Hash=b87885c2542d1279071454ae1ce34cea21b7a265095bb23297ff44542009a304;Subject=\"\";URI=spiffe://cluster.local/ns/default/sa/default"
  }
}
* Connection #0 to host httpbin-service left intact

できました。Response Header に server: envoyx-envoy-upstream-service-time: 26 が入っています。また、X- プレフィックスのついた curl では送っていないヘッダーが httpbin 側に届いているようです。Envoy が間に入っているようです。X-Forwarded-Client-Cent も送られているのでクライアント証明書も送られたっぽいですね。これは Istio 1.5 からデフォルトになったのだろうか。1.4.6 の時は追加の設定が必要だったのだが。

$ kubectl get -n istio-system cm istio -o yaml | grep -v '{' | grep enableAutoMtls:              
    enableAutoMtls: true

true になってる。前回の記事に戻って default プロファイルの値を確認してみよう。

$ istioctl manifest generate --set profile=default | grep enableAutoMtls
    enableAutoMtls: false

あれ?どこで変わったんだ? 🤔 後で確認してみよう。

    # If true, automatically configure client side mTLS settings to match the corresponding service's
    # server side mTLS authentication policy, when destination rule for that service does not specify
    # TLS settings.
    enableAutoMtls: true

っていう値。

Envoy のログを確認してみる

前回の istio のインストール時に Envoy のログを有効にしておいたので istio-proxy の出力を確認してみます。

まずは送信元の ubuntu 側。 grep を pipe に繋げると buffering されて全然 jq まで渡ってこないので --line-buffered をつけています。

kubectl logs -l app=ubuntu -c istio-proxy -f --tail 0 | grep --line-buffered '^{' | jq . 
{
  "authority": "httpbin-service",
  "bytes_received": "0",
  "bytes_sent": "521",
  "downstream_local_address": "10.109.118.31:80",
  "downstream_remote_address": "172.17.0.9:52178",
  "duration": "2",
  "istio_policy_status": "-",
  "method": "GET",
  "path": "/headers",
  "protocol": "HTTP/1.1",
  "request_id": "a93e2afa-cc27-4bb7-897c-5282c754d382",
  "requested_server_name": "-",
  "response_code": "200",
  "response_flags": "-",
  "route_name": "default",
  "start_time": "2020-03-07T12:00:58.425Z",
  "upstream_cluster": "outbound|80||httpbin-service.default.svc.cluster.local",
  "upstream_host": "172.17.0.8:80",
  "upstream_local_address": "172.17.0.9:43422",
  "upstream_service_time": "2",
  "upstream_transport_failure_reason": "-",
  "user_agent": "curl/7.58.0",
  "x_forwarded_for": "-"
}

172.17.0.9 は ubuntu Pod の IP アドレス。10.109.118.31 は httpbin-service Serivce の IP アドレス。172.17.0.8 は httpbin Pod の IP アドレス。

istio-proxy が 172.17.0.9:43422 –> 172.17.0.8:80 でリクエストを投げて、10.109.118.31:80 から 172.17.0.9:52178 に返したことにしてるってことなのか??

次に httpbin 側のログ

kubectl logs -l app=httpbin -c istio-proxy -f --tail 0 \
  | grep --line-buffered '^{' \
  | grep --line-buffered -v kube-probe \
  | jq .
{
  "authority": "httpbin-service",
  "bytes_received": "0",
  "bytes_sent": "521",
  "downstream_local_address": "172.17.0.8:80",
  "downstream_remote_address": "172.17.0.9:43422",
  "duration": "1",
  "istio_policy_status": "-",
  "method": "GET",
  "path": "/headers",
  "protocol": "HTTP/1.1",
  "request_id": "a93e2afa-cc27-4bb7-897c-5282c754d382",
  "requested_server_name": "outbound_.80_._.httpbin-service.default.svc.cluster.local",
  "response_code": "200",
  "response_flags": "-",
  "route_name": "default",
  "start_time": "2020-03-07T12:00:58.425Z",
  "upstream_cluster": "inbound|80|http|httpbin-service.default.svc.cluster.local",
  "upstream_host": "127.0.0.1:80",
  "upstream_local_address": "127.0.0.1:55232",
  "upstream_service_time": "1",
  "upstream_transport_failure_reason": "-",
  "user_agent": "curl/7.58.0",
  "x_forwarded_for": "-"
}

172.17.0.9:43422 (ubuntu:istio-proxy) –> 172.17.0.8:80 (httpbin:istio-proxy) 127.0.0.1:55232 –> 127.0.0.1:80 (httpbin:httpbin)

各 Pod と Serivce の IP アドレスは次の通り。

$ kubectl get pods -o wide
NAME                                 READY   STATUS    RESTARTS   AGE   IP           NODE   NOMINATED NODE   READINESS GATES
httpbin-deployment-9bfd96975-px5lv   2/2     Running   0          60m   172.17.0.8   m01    httpbin-deployment-9bfd96975-v6mgg   2/2     Running   0          60m   172.17.0.7   m01    ubuntu-deployment-cc86cc647-vsvbh    2/2     Running   0          45m   172.17.0.9   m01    $ kubectl get svc 
NAME              TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
httpbin-service   ClusterIP   10.109.118.31   80/TCP    60m
kubernetes        ClusterIP   10.96.0.1       443/TCP   5h22m 

続く


Istio 導入への道シリーズ

Built with Hugo
テーマ StackJimmy によって設計されています。