Istio 導入への道 – sidecar の調整編

Istio シリーズ 第12回です。

Istio は各 Pod に sidecar として Envoy コンテナを差し込み、通信の受信も送信も Envoy を経由します。アプリの更新時などに旧バージョンの Pod の停止する時、先に Envoy コンテナが停止してしまうとアプリのコンテナが通信できなくなり処理が完了できなくなったりします。開始時にもコンテナの起動順序は不定であるため起動スクリプトの調整や LivnessProbe, ReadinessProbe は重要です。

preStop フック

そこで、sidecar である Envoy が先に終了してしまわないようにするために Pod の preStop hook を活用することができます。

Envoy sidecar は istio によって deploy 時に inject されますが、どんな設定を inject するかは istio-system ネームスペースにある istio-sidecar-injector という ConfigMap に定義されています。

$ kubectl get configmap -n istio-system
NAME                     DATA   AGE
istio                    3      21d
istio-ca-root-cert       1      21d
istio-leader             0      21d
istio-security           1      21d
istio-sidecar-injector   2      21d
pilot-envoy-config       1      21d
prometheus               1      21d

config にテンプレートなどと、そのテンプレートに渡す values という変数が入っています。

istio-sidecar-injector ConfigMap の config

kubectl get configmap -n istio-system istio-sidecar-injector -o jsonpath='{.data.config}'
policy: enabled
alwaysInjectSelector:
        []
neverInjectSelector:
        []
injectedAnnotations:

# Configmap optimized for Istiod. Please DO NOT MERGE all changes from istio - in particular those dependent on
# Values.yaml, which should not be used by istiod.

# Istiod only uses SDS based config ( files will mapped/handled by SDS).

template: |
  rewriteAppHTTPProbe: {{ valueOrDefault .Values.sidecarInjectorWebhook.rewriteAppHTTPProbe false }}
  initContainers:
  {{ if ne (annotation .ObjectMeta `sidecar.istio.io/interceptionMode` .ProxyConfig.InterceptionMode) `NONE` }}
  {{ if .Values.istio_cni.enabled -}}
  - name: istio-validation
  {{ else -}}
  - name: istio-init
  {{ end -}}
  {{- if contains "/" .Values.global.proxy_init.image }}
    image: "{{ .Values.global.proxy_init.image }}"
  {{- else }}
    image: "{{ .Values.global.hub }}/{{ .Values.global.proxy_init.image }}:{{ .Values.global.tag }}"
  {{- end }}
    command:
    - istio-iptables
    - "-p"
    - 15001
    - "-z"
    - "15006"
    - "-u"
    - 1337
    - "-m"
    - "{{ annotation .ObjectMeta `sidecar.istio.io/interceptionMode` .ProxyConfig.InterceptionMode }}"
    - "-i"
    - "{{ annotation .ObjectMeta `traffic.sidecar.istio.io/includeOutboundIPRanges` .Values.global.proxy.includeIPRanges }}"
    - "-x"
    - "{{ annotation .ObjectMeta `traffic.sidecar.istio.io/excludeOutboundIPRanges` .Values.global.proxy.excludeIPRanges }}"
    - "-b"
    - "{{ annotation .ObjectMeta `traffic.sidecar.istio.io/includeInboundPorts` `*` }}"
    - "-d"
    - "15090,{{ excludeInboundPort (annotation .ObjectMeta `status.sidecar.istio.io/port` .Values.global.proxy.statusPort) (annotation .ObjectMeta `traffic.sidecar.istio.io/excludeInboundPorts` .Values.global.proxy.excludeInboundPorts) }}"
    {{ if or (isset .ObjectMeta.Annotations `traffic.sidecar.istio.io/excludeOutboundPorts`) (ne (valueOrDefault .Values.global.proxy.excludeOutboundPorts "") "") -}}
    - "-o"
    - "{{ annotation .ObjectMeta `traffic.sidecar.istio.io/excludeOutboundPorts` .Values.global.proxy.excludeOutboundPorts }}"
    {{ end -}}
    {{ if (isset .ObjectMeta.Annotations `traffic.sidecar.istio.io/kubevirtInterfaces`) -}}
    - "-k"
    - "{{ index .ObjectMeta.Annotations `traffic.sidecar.istio.io/kubevirtInterfaces` }}"
    {{ end -}}
    {{ if .Values.istio_cni.enabled -}}
    - "--run-validation"
    - "--skip-rule-apply"
    {{ end -}}
    imagePullPolicy: "{{ valueOrDefault .Values.global.imagePullPolicy `Always` }}"
  {{- if .Values.global.proxy_init.resources }}
    resources:
      {{ toYaml .Values.global.proxy_init.resources | indent 4 }}
  {{- else }}
    resources: {}
  {{- end }}
    securityContext:
      allowPrivilegeEscalation: {{ .Values.global.proxy.privileged }}
      privileged: {{ .Values.global.proxy.privileged }}
      capabilities:
    {{- if not .Values.istio_cni.enabled }}
        add:
        - NET_ADMIN
        - NET_RAW
    {{- end }}
        drop:
        - ALL
      readOnlyRootFilesystem: false
    {{- if not .Values.istio_cni.enabled }}
      runAsGroup: 0
      runAsNonRoot: false
      runAsUser: 0
    {{- else }}
      runAsGroup: 1337
      runAsUser: 1337
      runAsNonRoot: true
    {{- end }}
    restartPolicy: Always
  {{ end -}}
  {{- if eq .Values.global.proxy.enableCoreDump true }}
  - name: enable-core-dump
    args:
    - -c
    - sysctl -w kernel.core_pattern=/var/lib/istio/core.proxy && ulimit -c unlimited
    command:
      - /bin/sh
  {{- if contains "/" .Values.global.proxy_init.image }}
    image: "{{ .Values.global.proxy_init.image }}"
  {{- else }}
    image: "{{ .Values.global.hub }}/{{ .Values.global.proxy_init.image }}:{{ .Values.global.tag }}"
  {{- end }}
    imagePullPolicy: "{{ valueOrDefault .Values.global.imagePullPolicy `Always` }}"
    resources: {}
    securityContext:
      allowPrivilegeEscalation: true
      capabilities:
        add:
        - SYS_ADMIN
        drop:
        - ALL
      privileged: true
      readOnlyRootFilesystem: false
      runAsGroup: 0
      runAsNonRoot: false
      runAsUser: 0
  {{ end }}
  containers:
  - name: istio-proxy
  {{- if contains "/" (annotation .ObjectMeta `sidecar.istio.io/proxyImage` .Values.global.proxy.image) }}
    image: "{{ annotation .ObjectMeta `sidecar.istio.io/proxyImage` .Values.global.proxy.image }}"
  {{- else }}
    image: "{{ .Values.global.hub }}/{{ .Values.global.proxy.image }}:{{ .Values.global.tag }}"
  {{- end }}
    ports:
    - containerPort: 15090
      protocol: TCP
      name: http-envoy-prom
    args:
    - proxy
    - sidecar
    - --domain
    - $(POD_NAMESPACE).svc.{{ .Values.global.proxy.clusterDomain }}
    - --configPath
    - "/etc/istio/proxy"
    - --binaryPath
    - "/usr/local/bin/envoy"
    - --serviceCluster
    {{ if ne "" (index .ObjectMeta.Labels "app") -}}
    - "{{ index .ObjectMeta.Labels `app` }}.$(POD_NAMESPACE)"
    {{ else -}}
    - "{{ valueOrDefault .DeploymentMeta.Name `istio-proxy` }}.{{ valueOrDefault .DeploymentMeta.Namespace `default` }}"
    {{ end -}}
    - --drainDuration
    - "{{ formatDuration .ProxyConfig.DrainDuration }}"
    - --parentShutdownDuration
    - "{{ formatDuration .ProxyConfig.ParentShutdownDuration }}"
    - --discoveryAddress
    - "{{ annotation .ObjectMeta `sidecar.istio.io/discoveryAddress` .ProxyConfig.DiscoveryAddress }}"
  {{- if eq .Values.global.proxy.tracer "lightstep" }}
    - --lightstepAddress
    - "{{ .ProxyConfig.GetTracing.GetLightstep.GetAddress }}"
    - --lightstepAccessToken
    - "{{ .ProxyConfig.GetTracing.GetLightstep.GetAccessToken }}"
    - --lightstepSecure={{ .ProxyConfig.GetTracing.GetLightstep.GetSecure }}
    - --lightstepCacertPath
    - "{{ .ProxyConfig.GetTracing.GetLightstep.GetCacertPath }}"
  {{- else if eq .Values.global.proxy.tracer "zipkin" }}
    - --zipkinAddress
    - "{{ .ProxyConfig.GetTracing.GetZipkin.GetAddress }}"
  {{- else if eq .Values.global.proxy.tracer "datadog" }}
    - --datadogAgentAddress
    - "{{ .ProxyConfig.GetTracing.GetDatadog.GetAddress }}"
  {{- end }}
    - --proxyLogLevel={{ annotation .ObjectMeta `sidecar.istio.io/logLevel` .Values.global.proxy.logLevel}}
    - --proxyComponentLogLevel={{ annotation .ObjectMeta `sidecar.istio.io/componentLogLevel` .Values.global.proxy.componentLogLevel}}
    - --connectTimeout
    - "{{ formatDuration .ProxyConfig.ConnectTimeout }}"
  {{- if .Values.global.proxy.envoyStatsd.enabled }}
    - --statsdUdpAddress
    - "{{ .ProxyConfig.StatsdUdpAddress }}"
  {{- end }}
  {{- if .Values.global.proxy.envoyMetricsService.enabled }}
    - --envoyMetricsService
    - '{{ protoToJSON .ProxyConfig.EnvoyMetricsService }}'
  {{- end }}
  {{- if .Values.global.proxy.envoyAccessLogService.enabled }}
    - --envoyAccessLogService
    - '{{ protoToJSON .ProxyConfig.EnvoyAccessLogService }}'
  {{- end }}
    - --proxyAdminPort
    - "{{ .ProxyConfig.ProxyAdminPort }}"
    {{ if gt .ProxyConfig.Concurrency 0 -}}
    - --concurrency
    - "{{ .ProxyConfig.Concurrency }}"
    {{ end -}}
    {{- if .Values.global.istiod.enabled }}
    - --controlPlaneAuthPolicy
    - NONE
    {{- else if .Values.global.controlPlaneSecurityEnabled }}
    - --controlPlaneAuthPolicy
    - MUTUAL_TLS
    {{- else }}
    - --controlPlaneAuthPolicy
    - NONE
    {{- end }}
    - --dnsRefreshRate
    - {{ valueOrDefault .Values.global.proxy.dnsRefreshRate "300s" }}
  {{- if (ne (annotation .ObjectMeta "status.sidecar.istio.io/port" .Values.global.proxy.statusPort) "0") }}
    - --statusPort
    - "{{ annotation .ObjectMeta `status.sidecar.istio.io/port` .Values.global.proxy.statusPort }}"
  {{- end }}
  {{- if .Values.global.sts.servicePort }}
    - --stsPort={{ .Values.global.sts.servicePort }}
  {{- end }}
  {{- if .Values.global.trustDomain }}
    - --trust-domain={{ .Values.global.trustDomain }}
  {{- end }}
  {{- if .Values.global.logAsJson }}
    - --log_as_json
  {{- end }}
    - --controlPlaneBootstrap=false
  {{- if .Values.global.proxy.lifecycle }}
    lifecycle:
      {{ toYaml .Values.global.proxy.lifecycle | indent 4 }}
    {{- end }}
    env:
    - name: JWT_POLICY
      value: {{ .Values.global.jwtPolicy }}
    - name: PILOT_CERT_PROVIDER
      value: {{ .Values.global.pilotCertProvider }}
    # Temp, pending PR to make it default or based on the istiodAddr env
    - name: CA_ADDR
    {{- if .Values.global.configNamespace }}
      value: istio-pilot.{{ .Values.global.configNamespace }}.svc:15012
    {{- else }}
      value: istio-pilot.istio-system.svc:15012
    {{- end }}
    - 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
  {{- if eq .Values.global.proxy.tracer "datadog" }}
  {{- if isset .ObjectMeta.Annotations `apm.datadoghq.com/env` }}
  {{- range $key, $value := fromJSON (index .ObjectMeta.Annotations `apm.datadoghq.com/env`) }}
    - name: {{ $key }}
      value: "{{ $value }}"
  {{- end }}
  {{- end }}
  {{- end }}
    - name: ISTIO_META_POD_PORTS
      value: |-
        [
        {{- $first := true }}
        {{- range $index1, $c := .Spec.Containers }}
          {{- range $index2, $p := $c.Ports }}
            {{- if (structToJSON $p) }}
            {{if not $first}},{{end}}{{ structToJSON $p }}
            {{- $first = false }}
            {{- end }}
          {{- end}}
        {{- end}}
        ]
    - name: ISTIO_META_CLUSTER_ID
      value: "{{ valueOrDefault .Values.global.multiCluster.clusterName `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: "{{ or (index .ObjectMeta.Annotations `sidecar.istio.io/interceptionMode`) .ProxyConfig.InterceptionMode.String }}"
    {{- if .Values.global.network }}
    - name: ISTIO_META_NETWORK
      value: "{{ .Values.global.network }}"
    {{- end }}
    {{ if .ObjectMeta.Annotations }}
    - name: ISTIO_METAJSON_ANNOTATIONS
      value: |
             {{ toJSON .ObjectMeta.Annotations }}
    {{ end }}
    {{- if .DeploymentMeta.Name }}
    - name: ISTIO_META_WORKLOAD_NAME
      value: {{ .DeploymentMeta.Name }}
    {{ end }}
    {{- if and .TypeMeta.APIVersion .DeploymentMeta.Name }}
    - name: ISTIO_META_OWNER
      value: kubernetes://apis/{{ .TypeMeta.APIVersion }}/namespaces/{{ valueOrDefault .DeploymentMeta.Namespace `default` }}/{{ toLower .TypeMeta.Kind}}s/{{ .DeploymentMeta.Name }}
    {{- end}}
    {{- if (isset .ObjectMeta.Annotations `sidecar.istio.io/bootstrapOverride`) }}
    - name: ISTIO_BOOTSTRAP_OVERRIDE
      value: "/etc/istio/custom-bootstrap/custom_bootstrap.json"
    {{- end }}
    {{- if .Values.global.meshID }}
    - name: ISTIO_META_MESH_ID
      value: "{{ .Values.global.meshID }}"
    {{- else if .Values.global.trustDomain }}
    - name: ISTIO_META_MESH_ID
      value: "{{ .Values.global.trustDomain }}"
    {{- end }}
    {{- if eq .Values.global.proxy.tracer "stackdriver" }}
    - name: STACKDRIVER_TRACING_ENABLED
      value: "true"
    - name: STACKDRIVER_TRACING_DEBUG
      value: "{{ .ProxyConfig.GetTracing.GetStackdriver.GetDebug }}"
    - name: STACKDRIVER_TRACING_MAX_NUMBER_OF_ANNOTATIONS
      value: "{{ .ProxyConfig.GetTracing.GetStackdriver.GetMaxNumberOfAnnotations.Value }}"
    - name: STACKDRIVER_TRACING_MAX_NUMBER_OF_ATTRIBUTES
      value: "{{ .ProxyConfig.GetTracing.GetStackdriver.GetMaxNumberOfAttributes.Value }}"
    - name: STACKDRIVER_TRACING_MAX_NUMBER_OF_MESSAGE_EVENTS
      value: "{{ .ProxyConfig.GetTracing.GetStackdriver.GetMaxNumberOfMessageEvents.Value }}"
    {{- end }}
    {{- if and (eq .Values.global.proxy.tracer "datadog") (isset .ObjectMeta.Annotations `apm.datadoghq.com/env`) }}
    {{- range $key, $value := fromJSON (index .ObjectMeta.Annotations `apm.datadoghq.com/env`) }}
      - name: {{ $key }}
        value: "{{ $value }}"
    {{- end }}
    {{- end }}
    {{- range $key, $value := .ProxyConfig.ProxyMetadata }}
    - name: {{ $key }}
      value: "{{ $value }}"
    {{- end }}
    imagePullPolicy: "{{ valueOrDefault .Values.global.imagePullPolicy `Always` }}"
    {{ if ne (annotation .ObjectMeta `status.sidecar.istio.io/port` .Values.global.proxy.statusPort) `0` }}
    readinessProbe:
      httpGet:
        path: /healthz/ready
        port: {{ annotation .ObjectMeta `status.sidecar.istio.io/port` .Values.global.proxy.statusPort }}
      initialDelaySeconds: {{ annotation .ObjectMeta `readiness.status.sidecar.istio.io/initialDelaySeconds` .Values.global.proxy.readinessInitialDelaySeconds }}
      periodSeconds: {{ annotation .ObjectMeta `readiness.status.sidecar.istio.io/periodSeconds` .Values.global.proxy.readinessPeriodSeconds }}
      failureThreshold: {{ annotation .ObjectMeta `readiness.status.sidecar.istio.io/failureThreshold` .Values.global.proxy.readinessFailureThreshold }}
    {{ end -}}
    securityContext:
      allowPrivilegeEscalation: {{ .Values.global.proxy.privileged }}
      capabilities:
        {{ if or (eq (annotation .ObjectMeta `sidecar.istio.io/interceptionMode` .ProxyConfig.InterceptionMode) `TPROXY`) (eq (annotation .ObjectMeta `sidecar.istio.io/capNetBindService` .Values.global.proxy.capNetBindService) `true`) -}}
        add:
        {{ if eq (annotation .ObjectMeta `sidecar.istio.io/interceptionMode` .ProxyConfig.InterceptionMode) `TPROXY` -}}
        - NET_ADMIN
        {{- end }}
        {{ if eq (annotation .ObjectMeta `sidecar.istio.io/capNetBindService` .Values.global.proxy.capNetBindService) `true` -}}
        - NET_BIND_SERVICE
        {{- end }}
        {{- end }}
        drop:
        - ALL
      privileged: {{ .Values.global.proxy.privileged }}
      readOnlyRootFilesystem: {{ not .Values.global.proxy.enableCoreDump }}
      runAsGroup: 1337
      fsGroup: 1337
      {{ if or (eq (annotation .ObjectMeta `sidecar.istio.io/interceptionMode` .ProxyConfig.InterceptionMode) `TPROXY`) (eq (annotation .ObjectMeta `sidecar.istio.io/capNetBindService` .Values.global.proxy.capNetBindService) `true`) -}}
      runAsNonRoot: false
      runAsUser: 0
      {{- else -}}
      runAsNonRoot: true
      runAsUser: 1337
      {{- end }}
    resources:
      {{ if or (isset .ObjectMeta.Annotations `sidecar.istio.io/proxyCPU`) (isset .ObjectMeta.Annotations `sidecar.istio.io/proxyMemory`) -}}
      requests:
        {{ if (isset .ObjectMeta.Annotations `sidecar.istio.io/proxyCPU`) -}}
        cpu: "{{ index .ObjectMeta.Annotations `sidecar.istio.io/proxyCPU` }}"
        {{ end}}
        {{ if (isset .ObjectMeta.Annotations `sidecar.istio.io/proxyMemory`) -}}
        memory: "{{ index .ObjectMeta.Annotations `sidecar.istio.io/proxyMemory` }}"
        {{ end }}
    {{ else -}}
  {{- if .Values.global.proxy.resources }}
      {{ toYaml .Values.global.proxy.resources | indent 4 }}
  {{- end }}
    {{  end -}}
    volumeMounts:
    {{- if eq .Values.global.pilotCertProvider "istiod" }}
    - mountPath: /var/run/secrets/istio
      name: istiod-ca-cert
    {{- end }}
    {{ if (isset .ObjectMeta.Annotations `sidecar.istio.io/bootstrapOverride`) }}
    - mountPath: /etc/istio/custom-bootstrap
      name: custom-bootstrap-volume
    {{- end }}
    # SDS channel between istioagent and Envoy
    - mountPath: /etc/istio/proxy
      name: istio-envoy
    {{- if eq .Values.global.jwtPolicy "third-party-jwt" }}
    - mountPath: /var/run/secrets/tokens
      name: istio-token
    {{- end }}
    {{- if .Values.global.mountMtlsCerts }}
    # Use the key and cert mounted to /etc/certs/ for the in-cluster mTLS communications.
    - mountPath: /etc/certs/
      name: istio-certs
      readOnly: true
    {{- end }}
    - name: podinfo
      mountPath: /etc/istio/pod
    {{- if and (eq .Values.global.proxy.tracer "lightstep") .Values.global.tracer.lightstep.cacertPath }}
    - mountPath: {{ directory .ProxyConfig.GetTracing.GetLightstep.GetCacertPath }}
      name: lightstep-certs
      readOnly: true
    {{- end }}
      {{- if isset .ObjectMeta.Annotations `sidecar.istio.io/userVolumeMount` }}
      {{ range $index, $value := fromJSON (index .ObjectMeta.Annotations `sidecar.istio.io/userVolumeMount`) }}
    - name: "{{  $index }}"
      {{ toYaml $value | indent 4 }}
      {{ end }}
      {{- end }}
  volumes:
  {{- if (isset .ObjectMeta.Annotations `sidecar.istio.io/bootstrapOverride`) }}
  - name: custom-bootstrap-volume
    configMap:
      name: {{ annotation .ObjectMeta `sidecar.istio.io/bootstrapOverride` "" }}
  {{- end }}
  # SDS channel between istioagent and Envoy
  - emptyDir:
      medium: Memory
    name: istio-envoy
  - name: podinfo
    downwardAPI:
      items:
        - path: "labels"
          fieldRef:
            fieldPath: metadata.labels
        - path: "annotations"
          fieldRef:
            fieldPath: metadata.annotations
  {{- if eq .Values.global.jwtPolicy "third-party-jwt" }}
  - name: istio-token
    projected:
      sources:
      - serviceAccountToken:
          path: istio-token
          expirationSeconds: 43200
          audience: {{ .Values.global.sds.token.aud }}
  {{- end }}
  {{- if eq .Values.global.pilotCertProvider "istiod" }}
  - name: istiod-ca-cert
    configMap:
      name: istio-ca-root-cert
  {{- end }}
  {{- if .Values.global.mountMtlsCerts }}
  # Use the key and cert mounted to /etc/certs/ for the in-cluster mTLS communications.
  - name: istio-certs
    secret:
      optional: true
      {{ if eq .Spec.ServiceAccountName "" }}
      secretName: istio.default
      {{ else -}}
      secretName: {{  printf "istio.%s" .Spec.ServiceAccountName }}
      {{  end -}}
  {{- end }}
    {{- if isset .ObjectMeta.Annotations `sidecar.istio.io/userVolume` }}
    {{range $index, $value := fromJSON (index .ObjectMeta.Annotations `sidecar.istio.io/userVolume`) }}
  - name: "{{ $index }}"
    {{ toYaml $value | indent 2 }}
    {{ end }}
    {{ end }}
  {{- if and (eq .Values.global.proxy.tracer "lightstep") .Values.global.tracer.lightstep.cacertPath }}
  - name: lightstep-certs
    secret:
      optional: true
      secretName: lightstep.cacert
  {{- end }}
  {{- if .Values.global.podDNSSearchNamespaces }}
  dnsConfig:
    searches:
      {{- range .Values.global.podDNSSearchNamespaces }}
      - {{ render . }}
      {{- end }}
  {{- end }}
  podRedirectAnnot:
    sidecar.istio.io/interceptionMode: "{{ annotation .ObjectMeta `sidecar.istio.io/interceptionMode` .ProxyConfig.InterceptionMode }}"
    traffic.sidecar.istio.io/includeOutboundIPRanges: "{{ annotation .ObjectMeta `traffic.sidecar.istio.io/includeOutboundIPRanges` .Values.global.proxy.includeIPRanges }}"
    traffic.sidecar.istio.io/excludeOutboundIPRanges: "{{ annotation .ObjectMeta `traffic.sidecar.istio.io/excludeOutboundIPRanges` .Values.global.proxy.excludeIPRanges }}"
    traffic.sidecar.istio.io/includeInboundPorts: "{{ annotation .ObjectMeta `traffic.sidecar.istio.io/includeInboundPorts` (includeInboundPorts .Spec.Containers) }}"
    traffic.sidecar.istio.io/excludeInboundPorts: "{{ excludeInboundPort (annotation .ObjectMeta `status.sidecar.istio.io/port` .Values.global.proxy.statusPort) (annotation .ObjectMeta `traffic.sidecar.istio.io/excludeInboundPorts` .Values.global.proxy.excludeInboundPorts) }}"
  {{ if or (isset .ObjectMeta.Annotations `traffic.sidecar.istio.io/excludeOutboundPorts`) (ne .Values.global.proxy.excludeOutboundPorts "") }}
    traffic.sidecar.istio.io/excludeOutboundPorts: "{{ annotation .ObjectMeta `traffic.sidecar.istio.io/excludeOutboundPorts` .Values.global.proxy.excludeOutboundPorts }}"
  {{- end }}
    traffic.sidecar.istio.io/kubevirtInterfaces: "{{ index .ObjectMeta.Annotations `traffic.sidecar.istio.io/kubevirtInterfaces` }}"  

istio-sidecar-injector ConfigMap の values

$ kubectl get configmap -n istio-system istio-sidecar-injector -o jsonpath='{.data.values}'
{
  "global": {
    "arch": {
      "amd64": 2,
      "ppc64le": 2,
      "s390x": 2
    },
    "certificates": [],
    "configNamespace": "istio-system",
    "configValidation": true,
    "controlPlaneSecurityEnabled": true,
    "defaultNodeSelector": {},
    "defaultPodDisruptionBudget": {
      "enabled": true
    },
    "defaultResources": {
      "requests": {
        "cpu": "10m"
      }
    },
    "disablePolicyChecks": true,
    "enableHelmTest": false,
    "enableTracing": true,
    "enabled": true,
    "hub": "docker.io/istio",
    "imagePullPolicy": "IfNotPresent",
    "imagePullSecrets": [],
    "istioNamespace": "istio-system",
    "istiod": {
      "enabled": true
    },
    "jwtPolicy": "first-party-jwt",
    "k8sIngress": {
      "enableHttps": false,
      "enabled": false,
      "gatewayName": "ingressgateway"
    },
    "localityLbSetting": {
      "enabled": true
    },
    "logAsJson": false,
    "logging": {
      "level": "default:info"
    },
    "meshExpansion": {
      "enabled": false,
      "useILB": false
    },
    "meshNetworks": {},
    "mountMtlsCerts": false,
    "mtls": {
      "auto": true,
      "enabled": false
    },
    "multiCluster": {
      "clusterName": "",
      "enabled": false
    },
    "namespace": "istio-system",
    "network": "",
    "omitSidecarInjectorConfigMap": false,
    "oneNamespace": false,
    "operatorManageWebhooks": false,
    "outboundTrafficPolicy": {
      "mode": "ALLOW_ANY"
    },
    "pilotCertProvider": "istiod",
    "policyCheckFailOpen": false,
    "policyNamespace": "istio-system",
    "priorityClassName": "",
    "prometheusNamespace": "istio-system",
    "proxy": {
      "accessLogEncoding": "JSON",
      "accessLogFile": "/dev/stdout",
      "accessLogFormat": "",
      "autoInject": "enabled",
      "clusterDomain": "cluster.local",
      "componentLogLevel": "misc:error",
      "concurrency": 2,
      "dnsRefreshRate": "300s",
      "enableCoreDump": false,
      "envoyAccessLogService": {
        "enabled": false
      },
      "envoyMetricsService": {
        "enabled": false,
        "tcpKeepalive": {
          "interval": "10s",
          "probes": 3,
          "time": "10s"
        },
        "tlsSettings": {
          "mode": "DISABLE",
          "subjectAltNames": []
        }
      },
      "envoyStatsd": {
        "enabled": false
      },
      "excludeIPRanges": "",
      "excludeInboundPorts": "",
      "excludeOutboundPorts": "",
      "image": "proxyv2",
      "includeIPRanges": "*",
      "includeInboundPorts": "*",
      "kubevirtInterfaces": "",
      "logLevel": "warning",
      "privileged": false,
      "protocolDetectionTimeout": "100ms",
      "readinessFailureThreshold": 30,
      "readinessInitialDelaySeconds": 1,
      "readinessPeriodSeconds": 2,
      "resources": {
        "limits": {
          "cpu": "2000m",
          "memory": "1024Mi"
        },
        "requests": {
          "cpu": "100m",
          "memory": "128Mi"
        }
      },
      "statusPort": 15020,
      "tracer": "zipkin"
    },
    "proxy_init": {
      "image": "proxyv2",
      "resources": {
        "limits": {
          "cpu": "100m",
          "memory": "50Mi"
        },
        "requests": {
          "cpu": "10m",
          "memory": "10Mi"
        }
      }
    },
    "sds": {
      "enabled": false,
      "token": {
        "aud": "istio-ca"
      },
      "udsPath": ""
    },
    "securityNamespace": "istio-system",
    "sts": {
      "servicePort": 0
    },
    "tag": "1.5.0",
    "telemetryNamespace": "istio-system",
    "tracer": {
      "datadog": {
        "address": "$(HOST_IP):8126"
      },
      "lightstep": {
        "accessToken": "",
        "address": "",
        "cacertPath": "",
        "secure": true
      },
      "stackdriver": {
        "debug": false,
        "maxNumberOfAnnotations": 200,
        "maxNumberOfAttributes": 200,
        "maxNumberOfMessageEvents": 200
      },
      "zipkin": {
        "address": ""
      }
    },
    "trustDomain": "cluster.local",
    "useMCP": false
  },
  "istio_cni": {
    "enabled": false
  },
  "sidecarInjectorWebhook": {
    "alwaysInjectSelector": [],
    "enableNamespacesByDefault": false,
    "enabled": false,
    "image": "sidecar_injector",
    "injectLabel": "istio-injection",
    "injectedAnnotations": {},
    "namespace": "istio-system",
    "neverInjectSelector": [],
    "objectSelector": {
      "autoInject": true,
      "enabled": false
    },
    "rewriteAppHTTPProbe": false,
    "selfSigned": false
  }
}

preStop hook のカスタマイズ

template の中に次のような箇所があります。よって、values でこの global.proxy.lifecycle を定義してやれば preStop hook を入れることが可能になります。

  {{- if .Values.global.proxy.lifecycle }}
    lifecycle:
      {{ toYaml .Values.global.proxy.lifecycle | indent 4 }}
    {{- end }}

preStop hook にどんなコマンドを入れるかですが「本番環境のマルチテナント Kubernetes クラスタへの Istio 導入」で Istio の issue #7136 に投稿されているものが紹介されています。

lifecycle:
  preStop:
    exec:
      command:
        - "/bin/sh"
        - "-c"
        - "while [ $(netstat -plunt | grep tcp | grep -v envoy | wc -l | xargs) -ne 0 ]; do sleep 1; done"

netstat で envoy 以外に port を listen しているプロセスが存在するかどうかを確認して、存在しなくなるまで sleep 1 して再チェックを繰り返し、存在しなくなったら終了します。その後コンテナのプロセスに対して SIGTERM が送られます。

試しに Istio 1.5 環境で netstat -plunt を実行してみると次のようになりました。pilot-agent も istio-proxy コンテナ内で実行されているため、このコマンドのままでは terminationGracePeriodSeconds (デフォルト30秒) を待っても終了せず、その2秒後に SIGTERM が送られることになります。

$ netstat -plunt
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:15090           0.0.0.0:*               LISTEN      18/envoy            
tcp        0      0 127.0.0.1:15000         0.0.0.0:*               LISTEN      18/envoy            
tcp        0      0 0.0.0.0:15001           0.0.0.0:*               LISTEN      18/envoy            
tcp        0      0 0.0.0.0:15006           0.0.0.0:*               LISTEN      18/envoy            
tcp6       0      0 :::15020                :::*                    LISTEN      1/pilot-agent

ところで、netstat に -u がついてるけど grep tcp してるから意味ないな。あと、xargs って何の意味があるんだろうか??

ということで global.proxy.lifecycle に設定するのは次のようになる。kubectl edit cm istio-sidecar-injector -n istio-system で編集します。

$ kubectl -n istio-system get configmap istio-sidecar-injector -o=jsonpath='{.data.values}' | jq .global.proxy.lifecycle 
{
  "preStop": {
    "exec": {
      "command": [
        "/bin/sh",
        "-c",
        "while [ $(netstat -plnt | grep tcp | egrep -v 'envoy|pilot-agent' | wc -l) -ne 0 ]; do sleep 1; done"
      ]
    }
  }
}

Deployment の Pod を削除すれば新しい Pod が作成される時に新しい設定で istio-proxy が Inject されるため、kubectl get pod で確認できる。

$ kubectl get pod httpbin-deployment-v2-ccd49cc9c-5z9mt -o jsonpath='{.spec.containers[*].lifecycle.preStop}'
map[exec:map[command:[/bin/sh -c while [ $(netstat -plnt | grep tcp | egrep -v 'envoy|pilot-agent' | wc -l) -ne 0 ]; do sleep 1; done]]]

Istio 導入への道シリーズ

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