1. 서론
지금까지 pilot-discovery에 대해서 학습하면서, k8s의 리소스 변화를 감지하고 이를 pilot-agent에게 전달하는 과정에 대해서 살펴봤습니다. 이번에는 애플리케이션 서비스에 pilot-agent를 배포하는 과정을 살펴보며, 이때 pilot-discovery는 어떤 역할을 수행하는지 살펴보겠습니다.
2. pilot-agent 배포

istio를 사용하지 않는 상황에서 단일 서비스에 envoy proxy를 사용한다면, envoy proxy와 service 그리고 proxy와 service 연결을 위한 설정 파일을 한데 묶어 배포하면 되었습니다. 하지만 이러한 방식은 사용자 입장에서 CI/CD 단계에서 service 관리 뿐만 아니라 envoy proxy 설정을 관리해야하는 불편함이 존재했습니다.

더욱이 istio와 같은 control plane이 추가되면, 이제는 control plane과 연결을 위한 속성까지 추가해야하므로 service 관리자의 불편은 더욱 가중됩니다. 만약 배포되는 istio의 설정 정보가 바뀌기라도 한다면, service 관리자는 전체 service에 속한 config 정보를 바꿔야할 지도 모릅니다.
istio에서는 이러한 문제를 해결하고자 사용자는 CI/CD를 위해서 개별 Service 설정에만 집중하도록 하고 pilot-agent 추가와 설정 정보 변경은 istio가 대신 해주기 위한 2가지 방법을 제공합니다.
첫 번째 방법은 수동 방식으로 사용자가 작성한 yaml 파일을 읽어들여 istio 설정이 적용된 형태로 yaml 형태를 변경해주는 스크립트를 제공해주는 방식입니다. 두 번째 방식은 자동으로 Pod 생성 시점에 istio 설정이 적용된 형태로 배포하는 것입니다. 지금부터 이 두 가지 방법에 대해서 살펴보겠습니다.
2-1 수동 배포(istioctl CLI)
첫번째 방법은 istioctl을 통해서 기존에 생성된 pod.yaml을 조작하는 방법입니다.
nginx.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: nginx
name: nginx
spec:
containers:
- image: nginx
name: nginx
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
가령 위와 같은 Pod를 만들기 위한 yaml 파일을 만들었다고 가정해봅시다. 해당 Pod를 배포할 때 envoy proxy를 사이드카로 배포하기 위해서는 istioctl을 통해 yaml 파일을 조작할 수 있습니다.
istioctl kube-inject -f nginx.yaml
istioctl에서 제공하는 kube-inject 명령어를 활용하면, 기존에 생성한 pod의 yaml 파일을 읽어서 envoy proxy를 주입한 yaml 파일을 제공합니다. 제공된 결과를 살펴보면 다음과 같습니다.
apiVersion: v1
kind: Pod
metadata:
annotations:
kubectl.kubernetes.io/default-container: nginx
kubectl.kubernetes.io/default-logs-container: nginx
prometheus.io/path: /stats/prometheus
prometheus.io/port: "15020"
prometheus.io/scrape: "true"
sidecar.istio.io/status: '{"initContainers":["istio-init"],"containers":["istio-proxy"],"volumes":["workload-socket","workload-certs","istio-envoy","istio-data","istio-podinfo","istio-token","istiod-ca-cert"],"imagePullSecrets":null,"revision":"default"}'
creationTimestamp: null
labels:
run: nginx
security.istio.io/tlsMode: istio
service.istio.io/canonical-name: nginx
service.istio.io/canonical-revision: latest
name: nginx
spec:
containers:
- image: nginx
name: nginx
resources: {}
- args:
- proxy
- sidecar
- --domain
- $(POD_NAMESPACE).svc.cluster.local
- --proxyLogLevel=warning
- --proxyComponentLogLevel=misc:error
- --log_output_level=default:info
- --concurrency
- "2"
env:
- name: JWT_POLICY
value: third-party-jwt
- name: PILOT_CERT_PROVIDER
value: istiod
- name: CA_ADDR
value: istiod.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: PROXY_CONFIG
value: |
{"envoyAccessLogService":{"address":"skywalking-oap.skywalking:11800"}}
- name: ISTIO_META_POD_PORTS
value: |-
[
]
- name: ISTIO_META_APP_CONTAINERS
value: nginx
- name: ISTIO_META_CLUSTER_ID
value: Kubernetes
- name: ISTIO_META_INTERCEPTION_MODE
value: REDIRECT
- name: ISTIO_META_WORKLOAD_NAME
value: nginx
- name: ISTIO_META_OWNER
value: kubernetes://apis/v1/namespaces/default/pods/nginx
- name: ISTIO_META_MESH_ID
value: cluster.local
- name: TRUST_DOMAIN
value: cluster.local
image: docker.io/istio/proxyv2:1.14.2
name: istio-proxy
ports:
- containerPort: 15090
name: http-envoy-prom
protocol: TCP
readinessProbe:
failureThreshold: 30
httpGet:
path: /healthz/ready
port: 15021
initialDelaySeconds: 1
periodSeconds: 2
timeoutSeconds: 3
resources:
limits:
cpu: "2"
memory: 1Gi
requests:
cpu: 10m
memory: 40Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
privileged: false
readOnlyRootFilesystem: true
runAsGroup: 1337
runAsNonRoot: true
runAsUser: 1337
volumeMounts:
- mountPath: /var/run/secrets/workload-spiffe-uds
name: workload-socket
- mountPath: /var/run/secrets/workload-spiffe-credentials
name: workload-certs
- mountPath: /var/run/secrets/istio
name: istiod-ca-cert
- mountPath: /var/lib/istio/data
name: istio-data
- mountPath: /etc/istio/proxy
name: istio-envoy
- mountPath: /var/run/secrets/tokens
name: istio-token
- mountPath: /etc/istio/pod
name: istio-podinfo
dnsPolicy: ClusterFirst
initContainers:
- args:
- istio-iptables
- -p
- "15001"
- -z
- "15006"
- -u
- "1337"
- -m
- REDIRECT
- -i
- '*'
- -x
- ""
- -b
- '*'
- -d
- 15090,15021,15020
image: docker.io/istio/proxyv2:1.14.2
name: istio-init
resources:
limits:
cpu: "2"
memory: 1Gi
requests:
cpu: 10m
memory: 40Mi
securityContext:
allowPrivilegeEscalation: false
capabilities:
add:
- NET_ADMIN
- NET_RAW
drop:
- ALL
privileged: false
readOnlyRootFilesystem: false
runAsGroup: 0
runAsNonRoot: false
runAsUser: 0
restartPolicy: Always
volumes:
- name: workload-socket
- name: workload-certs
- emptyDir:
medium: Memory
name: istio-envoy
- emptyDir: {}
name: istio-data
- downwardAPI:
items:
- fieldRef:
fieldPath: metadata.labels
path: labels
- fieldRef:
fieldPath: metadata.annotations
path: annotations
name: istio-podinfo
- name: istio-token
projected:
sources:
- serviceAccountToken:
audience: istio-ca
expirationSeconds: 43200
path: istio-token
- configMap:
name: istio-ca-root-cert
name: istiod-ca-cert
status: {}
---
위 출력 결과물을 보면, 이전에 작성한 Pod.yaml 과 비교해서 꽤나 복잡해진 것을 확인할 수 있습니다. 이는 istiod와 통신을 위한 iptables 변경과 pilot-agent 주입을 위한 기본 설정이 포함되었기 때문입니다. 만약 사용자가 직접 이를 작성해야한다면 꽤나 불편했을텐데, istioctl을 통해서 위와같이 변경된 yaml 파일을 얻을 수 있습니다.
변경된 yaml 파일을 토대로 kubernetes에 배포하면 istio control plane과 통신할 수 있는 envoy proxy가 내장된 Pod를 사용할 수 있습니다.
2-2 자동 배포
이번에는 자동 배포에 대해서 알아보겠습니다. 자동 배포에는 두 가지 방법이 있습니다. 첫번째 방법은 Pod.yaml에 istio에서 요구하는 label을 추가하는 것입니다.
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: nginx
sidecar.istio.io/inject: "true"
name: nginx
spec:
containers:
- image: nginx
name: nginx
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
이전에 살펴본 nginx pod yaml 파일에 sidecar.istio.io/inject: "true" label을 추가하면, Pod 생성 시점에 envoy proxy가 내장된 형태로 Pod yaml이 변경되고 배포가 이루어집니다. 참고로 해당 annotation의 값은 true 이외에도 y, yes, on 중 하나가 입력되면 사이드카 배포가 이루어집니다.
두번째 방법은 배포하려는 namespace에 label을 추가하는 것입니다.
[cla9@DESKTOP-FBK64T0]$ kubectl label ns default istio-injection=enabled
[cla9@DESKTOP-FBK64T0]$ kubectl get ns default -L istio-injection
NAME STATUS AGE ISTIO-INJECTION
default Active 334d enabled
위와 같이 Pod를 배포하려는 namespace에 istio-injection=enabled label을 추가하면, 이후 해당 namespace에 배포되는 Pod에는 모두 envoy proxy가 내장된 형태로 배포가 이루어집니다.
지금까지 envoy proxy를 배포하기 위한 수동 배포와 자동 배포에 대해서 살펴봤습니다. 여기서 자동 배포의 경우 Pod 혹은 namespace에 label을 추가하는 것만으로 Pod 생성시에 자동으로 pilot-agent가 배포되는 것을 확인했는데요. 해당 작업은 어떻게 이루어지는 것일까요?
3. Mutation Webhook
사이드카 컨테이너의 자동 주입과정에 대해서 이해하려면, 먼저 kubernetes의 API Server를 활용한 배포 과정에서 어떠한 일이 일어나는지에 대해서 알아야합니다. 따라서 istio의 자동 배포 프로세스 과정을 살펴보기 전에 kubernetes의 Resource 생성 과정에 대해서 살펴보겠습니다.

사용자가 Kubectl을 통해서 Pod를 생성하였을 때, 우리는 일반적으로 위와같이 Kubectl 명령어를 통해서 Kube ApiServer에 전달하면, Pod가 생성된다고 생각합니다.

이때 내부 과정을 조금 더 자세히 살펴보면, 실제로는 위 그림과 같은 단계를 거쳐 Pod가 생성됩니다.
1. kubectl로 명령어를 Kube ApiServer로 전달할 때 명령어를 실행하는 컴퓨터에 존재하는 config파일 정보를 같이 전달합니다. 해당 config 파일에는 인증서 정보와 사용자 정보가 같이 포함되어있습니다.
2. Kube ApiServer는 제일 첫번째로 인증(Authentication) 과정을 수행합니다. 해당 과정은 사용자가 전달한 인증서 정보를 토대로 해당 서버에 접속 가능한 요청인지를 분석하고 승인하는 과정입니다. 마치 ID/Password를 입력했을 때, 유효한 계정인지를 검증하는 것과 같습니다.
3. 두번째로는 인가(Authorization) 과정을 수행합니다. 해당 과정은 접속이 완료된 이후에 해당 사용자가 요청한 작업에 대해서 수행 권한이 있는지를 분석하고 승인하는 과정입니다. 만약 Pod 생성을 요청했는데, 해당 권한이 존재하지 않는다면 이 단계에서 실패합니다.
4. 마지막 단계에서는 Admission Controllers 체인을 거치면서 사용자가 전달한 yaml 파일내에서 validation을 진행하거나 부가적인 내용을 추가하거나 변경하는 등의 기능을 제공합니다.
5. 모든 단계가 완료되면 Pod가 생성됩니다.
여기서 주목할 부분은 Admission Controllers입니다. Admission Controller는 이전에 설명했듯이 인증과 인가외에 검증(Validation)이나 내용을 변경(Mutation)하기 위해 사용됩니다. 또한 Admission Controller는 하나만 존재하는 것이 아니라
여러 Admission Controller가 Chain 형태로 엮여있고 해당 Chain을 순회하면서 validation 혹은 mutation을 진행합니다.

이쯤되면 istio가 어떻게 자동으로 Pod에 사이드카 컨테이너를 주입시킬 수 있는지 눈치를 챌 수 있는데요. Admission controller 중 하나인 MutatingAdmission Webhook을 활용하여 사용자의 요청을 변경시킵니다.
[cla9@DESKTOP-FBK64T0]$ kubectl get mutatingwebhookconfigurations
NAME WEBHOOKS AGE
istio-revision-tag-default 4 2d
istio-sidecar-injector 4 26d
이를 위해서 kubernetes 상에 위와 같이 찾아보면, istio에서 사이드카를 주입하기 위한 MutatingWebhookConfiguration이 존재하는 것을 확인할 수 있습니다.
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
labels:
app: sidecar-injector
install.operator.istio.io/owning-resource: unknown
install.operator.istio.io/owning-resource-namespace: istio-system
istio.io/rev: default
operator.istio.io/component: Pilot
operator.istio.io/managed: Reconcile
operator.istio.io/version: 1.12.2
release: istio
name: istio-sidecar-injector
webhooks:
- admissionReviewVersions:
- v1beta1
- v1
clientConfig:
caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMvRENDQWVTZ0F3SUJBZ0lRZWhpd2hmTTFLaDNTUGUvVC9qZ29tVEFOQmdrcWhraUc5dzBCQVFzRkFEQVkKTVJZd0ZBWURWUVFLRXcxamJIVnpkR1Z5TG14dlkyRnNNQjRYRFRJeU1Ea3hPREF6TURreE5sb1hEVE15TURreApOVEF6TURreE5sb3dHREVXTUJRR0ExVUVDaE1OWTJ4MWMzUmxjaTVzYjJOaGJEQ0NBU0l3RFFZSktvWklodmNOCkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFNaXliT3A5SVhpZEpGbU5yUGlVbFVMeVN0RVoyU1BZV3ZVdWlNUWwKRFo2NG44cEI2TmVIL2ZGMFB0eXJneTlxemhtNkZSOCtBTkpFTXBWSDlmTWlHcHpwMVNQeVQ1RXVyaHYwdGVpWQp3ZzRKa2RINEQ2bm03ZUJCTkxkdkszMzFqR0o1RU5WZjlOTVIxR05wdUZZMXgyT045NExkUGxjWHhMOHlFd2NHClVOY2k3aFlyd1JuSHl0VTlFSjVNSGZCKzIydW5KR3ZuVzI1L2RNTllBN0ZMVkd2VHdST0d1RkZzR29UejRQNkMKYUI5bGd1MFZwbkVDb3B1bmJZLys2QW5NMHN5UHJTdjZQVmkvcUVjdzV1cmlYM1BiNFhxSzRkTDI0Vk1aVGNTSgpUQkUwTUpLazEwQjFMNlNqa0JZOGRNeDBCblR5VEJMUnJ6RFNuQlY4cjRiRldsa0NBd0VBQWFOQ01FQXdEZ1lEClZSMFBBUUgvQkFRREFnSUVNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdIUVlEVlIwT0JCWUVGTDk2eG1pUmRTbGgKVlNFZUtEbXhvdVhYajJnNk1BMEdDU3FHU0liM0RRRUJDd1VBQTRJQkFRQ2pwYTJ5N2U3SkVNQlplVTRxRHFVUwp6aG40cFVpSE1XelJSLzRTU0NwRnZQVzVwRkRLd21ZY2NwUFp2MllCLzg3L2JKU2ZFakk5N0Ewa1NoN2J3aHRPCmNTUm5RdHBmdkp2dlQ2U1BQRGJzWEg1NnVwWXd6Z1dPVElPOEY5b3p1RmpoMHFXQ0VuTFcvcHN5ZHNTbWlyNVMKNHppclVXazVGZjhYTVdMWllYS29wYnpqK0VvNUd2TVVSRDg5cHY3Yng3dE1sOGxGU0MwZkkwVGVLc3d6OUJsbwpPVXExZkp0bStJaDFPdU9aQTFOZGZmYmNzOTZCOGdFSVM2bVFEUjBUVmVEayt4NWk0Zk8xSG56SnZMbEJhVmtpClJneGxiUExFei81VlY1RHBoMzRpTTBPcVZrampiT05RNUlBd211YUZ5RC9jNmJvN2tqcEMyWFpUNndybDI5SXkKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
service:
name: istiod
namespace: istio-system
path: /inject
port: 443
failurePolicy: Fail
matchPolicy: Equivalent
name: namespace.sidecar-injector.istio.io
namespaceSelector:
matchExpressions:
- key: istio-injection
operator: In
values:
- enabled
objectSelector:
matchExpressions:
- key: sidecar.istio.io/inject
operator: NotIn
values:
- "false"
reinvocationPolicy: Never
rules:
- apiGroups:
- ""
apiVersions:
- v1
operations:
- CREATE
resources:
- pods
scope: '*'
sideEffects: None
timeoutSeconds: 10
해당 설정을 조금 더 자세히 살펴보겠습니다. 위 코드는 istio-sidecar-injector ConfigMap 일부를 발췌한 부분으로 먼저 살펴볼 지점은 rules 부분에 Pod를 생성할 때 해당 Webhook이 동작하는 것을 확인할 수 있습니다.
또한 namespaceSelector와 objectSelector를 통해서 특정 label의 값이 존재할 경우에만 동작하도록 되어있는 것을 확인할 수 있습니다.
마지막으로 살펴볼 지점은 조건들이 부합되었을 때, istiod의 /inject URL로 요청을 전달하여 후속 작업 처리를 요청한다는 점입니다.

이를 토대로 살펴보면, Kubernetes는 사용자가 요청한 내용을 분석하여 istio를 통한 Mutation 변경이 필요하다고 판단될 때 istiod에게 요청하여 mutation 작업을 수행하도록 요청합니다. 그 이후에는 istiod 내부에서 istio 설정에 맞추어 yaml을 변경한 다음 이를 다시 Kubernetes로 전달하여 후속 작업 진행 후 배포하는 과정을 거쳐 Pod가 생성됩니다.
4. Sidecar Injector
이전 내용을 통해 Pod가 생성되는 시점에 istio pilot-discovery에 요청하여 istio 연결에 필요한 설정이 적용되는 것을 확인할 수 있었습니다. 그렇다면 istio 내부에 어떤 컴포넌트가 어떤 과정을 거쳐 이러한 과정이 수행될까요? pilot-discovery에 위치한 Sidecar Injector 컴포넌트를 살펴보면서, 사이드카 컨테이너를 어떻게 주입하는지 과정을 살펴보겠습니다. 먼저 살펴볼 것은 Sidecar Injector의 기동 과정입니다.
처음에 pilot-discovery가 기동될 당시에 INJECT_ENABLED 환경 변수 값을 확인합니다. 해당 값이 true이면, Sidecar Injector의 초기화 과정을 수행합니다. 참고로 해당 환경 변수의 기본 값은 true입니다.
초기화 과정을 살펴보면 다음과 같습니다.

먼저 pilot-discovery가 실행되는 Pod 내의 /var/lib/istio/inject 하위 디렉토리에 config 파일이 존재하는지를 살펴봅니다. 만약 해당 디렉토리에 config 파일이 위치한다면, config와 values를 FileWatcher에 매핑시킵니다.
만약 해당 디렉토리에 파일이 위치하지 않으면, 그 다음으로 확인하는 것은 k8s의 Configmap을 확인하고 관련 ConfigMapWatcher 컴포넌트를 생성하여 바인딩하는 역할을 수행합니다.

먼저 살펴볼 것은 ConfigMap Watcher 내부입니다. 내부에는 Controller가 존재하고 ConfigMap 이벤트를 전달받기 위해서 informer를 등록하고 변경 사항을 전달 받습니다. 이때 ConfigMap Watcher가 감시하는 대상은 istio-system 네임스페이스에 존재하는 istio-sidecar-injector입니다. 참고로 해당 ConfigMap은 istio 설치 시에 자동 등록되는 리소스로 내부에는 config와 values 두 Key에 해당하는 데이터가 존재합니다.
informer에 등록된 이후 해당 Configmap에 변경이 발생하면, 다음과 같은 과정을 거칩니다.
1. informer에서 queue에 이벤트 정보를 전달합니다.
2. queue에는 등록된 callback을 호출합니다.
3. Controller에 등록된 callback의 역할은 Configmap 정보를 읽고 거기에서 등록된 config 데이터와 values 두 개의 데이터를 읽어들이는 작업을 수행하고, ConfigMap Watcher에 등록된 handler에 전달하여 후속 작업을 위임합니다.
4. handler의 역할은 Webhook 구조체에 위치한 updateConfig 메소드를 호출하는 것입니다.
5. updateConfig 메소드의 역할은 ConfigMap을 읽어들여 Webhook 구조체 멤버에 저장합니다. 이때 config의 경우는 configMap의 config key의 값을 그대로 저장하지만, values의 경우는 해당 데이터를 map으로 변환 후 ValuesConfig 구조체로 감싸서 Webhook의 valuesconfig에 저장하는 차이점이 있습니다.

위 내용을 보면, istio-sidecar-injector ConfigMap의 values 데이터를 Map 형태로 Parsing하여 저장하는 것을 확인할 수 있습니다.
5번 단계까지 진행되면, pilot-discovery 내부에 사이드카 주입을 위한 템플릿 정보를 취득할 수 있으며, ConfigMap 변경에 따라서 이를 감지하고 반영할 수 있게 되었습니다. 이후 진행되는 6번 단계에서는 외부에서 /inject 혹은 /inject/ URI를 통해서 접근할 경우 요청을 처리하기 위한 handler 메소드를 등록하는데, 이때 담당을 webhook에 위치한 serverInject 메소드가 담당합니다. 즉 사이드카 컨테이너 주입 역할은 serverInject 메소드에 있습니다. 지금부터 해당 메소드에서 수행되는 주요 과정에 대해서 살펴보겠습니다.
4-1 사이드카 주입 가능 여부 확인

serveInject 메소드내에서 가장 먼저 수행하는 작업은 위 그림과 같습니다. 먼저 Client의 요청 타입은 JSON으로 전달되기 때문에 해당 타입이 JSON인지 확인하고 Body에서 데이터를 추출합니다. 그 후 수행하는 작업은 해당 Pod의 사이드카 주입 요청이 적절한지를 확인하는 것입니다. 해당 과정은 위 그림과 같은 과정을 거칩니다.
1. Pod의 spec.hostNetwork 값이 true인가?
2. 사이드카 생성 요청 대상 Namespace가 Ignored Namespace에 해당하는가?
3. sidecar.istio.io/inject 어노테이션이 존재하거나 대상 Namespace에 자동 주입 활성화 Label이 존재하는가?
먼저 첫번째 조건에 대해서 살펴보면, hostNetwork 값이 true라면, 이는 Pod의 네트워크 설정이 해당 Pod가 동작하는 host 노드의 네트워크 설정을 따라가는 것을 의미합니다. istio에서는 Pod 컨테이너의 iptables를 조작하여 네트워크 트래픽을 변경하는데, 해당 설정이 true일 경우 노드 전체에 장애가 발생할 여지가 있습니다. 따라서 Pod의 hostNetwork가 true인 경우에는 사이드카 주입을 수행하지 않습니다.
두 번째 조건은 사이드카 요청 대상 Namespace가 시스템에서 내부적으로 사용하는 Namespace인 경우에는 요청을 거절합니다. 대상 Namespace는 위 그림과 같이 총 4개입니다.
세 번째 조건은 Pod 요청에 사이드카 주입 어노테이션이 존재하는지 확인하는 것입니다. 해당 어노테이션이 존재하면서 값 주입을 요청하거나 생성 대상 Namespace에 사이드카 자동 주입 Label이 활성화되어있는 경우에는 사이드카 주입을 허가하지만 그렇지 않을 경우에는 주입을 수행하지 않습니다.
4-2 Pod Annotation 검증
이번에는 Pod 내에 존재하는 Annotation 중 istio 동작에 관여하는 Annotation이 지정되어있을 경우 해당 Annotation이 istio에서 요구하는 형태로 작성되어있는지 검증하는 단계입니다. 이전 단계에서는 특정 조건에 부합되지 않은 경우에는 사이드카 주입은 수행하지 않더라도 Pod 생성은 진행되었지만, 해당 단계에서는 Validation을 통과하지 않은 경우에 Pod 생성이 되지 않는 것이 차이점입니다.

위와같이 pilot-discovery 내에는 사용자가 Pod에 입력한 Annotation의 이름이 위 Map에서 제공하는 Annotation과 일치할 경우 입력값이 올바른지 검증하는 함수가 매핑되어있는 것을 확인할 수 있습니다.
따라서 Pod 내부 Annotation을 순회하면서 위 조건에 부합하는 Key Annotation이 존재할 경우 해당 값에 매핑된 검증 함수를 수행하여 올바른 값이 입력되지 않았을 경우 Pod 생성 요청을 거절합니다.
4-3 Concurrency 계산
envoy 구조 포스팅에서 다루어봤듯이 envoy 내부에는 Master 쓰레드와 Worker 쓰레드가 분리되어 있고 해당 쓰레드 개수의 설정은 --concurency 인자에 의해 결정되는 것을 확인했습니다.
istio에서 사이드카를 주입할 때 내부적으로 envoy proxy를 기동시켜야되기 때문에 해당 과정에서는 몇 개의 Worker 쓰레드를 기동시키는 지 계산하여 사이드카 컨테이너 템플릿을 생성할 때 이 과정에서 계산된 값이 주입됩니다.

이때 Concurrency가 계산되는 과정을 살펴보면, 위 그림과 같이 Proxy Config 설정 여부에 따라 달라집니다. Proxy Config는 istio 전체에 전역적으로 설정하거나 특정 Workload에만 적용하거나 아니면 특정 Pod에 대해서 Annotation에서 설정 변경이 가능합니다. 이때 우선순위는 Pod > Workload > Global 순이기 때문에 우선순위에 따라 값이 Override 됩니다.
만약 Concurrency 값이 양수 값이 입력되어있다면, 계산 과정에서는 해당 값이 적용됩니다. 하지만 Concurrency 값이 0일 경우에는 계산 과정이 달라집니다.

가장 먼저 확인하는 것은 sidecar.istio.io/proxyCPULimit Annotation 값이 존재하는지 확인하는 것입니다. 해당 Annotation은 Envoy를 위해 사용되는 CPU의 Limit을 지정하는 값으로 해당 Annotation이 존재한다면, 그 값을 기준으로 Concurrency 값을 계산합니다.
만약 해당 Annotation이 존재하지 않는다면, sidecar.istio.io/proxyCPU 값이 존재하는지 확인합니다. 해당 Annotation은 Envoy를 위해 사용되는 CPU를 의미합니다.
만약 위 두 Annotation이 존재하지 않는다면, 해당 Pod에 지정되어있는 CPU Resource의 Request와 Limit을 확인합니다. 만약 지정된 값이 있으면, 그 값을 기준으로 Concurrency를 계산합니다.
마지막으로 어떠한 조건에도 부합하지 않는다면, 기본 값인 2를 지정합니다.
참고로 Concurrency 계산에 필요한 값이 존재할 경우에는 입력된 값을 1000으로 나눈 값을 올림하여 요구 Concurrency를 계산합니다. 가령 500m이 입력되었다면, CEIL(500/1000) = 1이므로 1개가 지정됩니다.
4-4 Template Yaml 생성
istio-system namespace에 존재하는 istio-sidecar-injector Configmap을 살펴보면, 사이드카 주입을 위한 Template이 Yaml 형식으로 정의된 것을 확인할 수 있습니다.
args:
- istio-iptables
- "-p"
- {{ .MeshConfig.ProxyListenPort | default "15001" | quote }}
- "-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` .Values.global.proxy.includeInboundPorts }}"
- "-d"
{{- if excludeInboundPort (annotation .ObjectMeta `status.sidecar.istio.io/port` .Values.global.proxy.statusPort) (annotation .ObjectMeta `traffic.sidecar.istio.io/excludeInboundPorts` .Values.global.proxy.excludeInboundPorts) }}
- "15090,15021,{{ excludeInboundPort (annotation .ObjectMeta `status.sidecar.istio.io/port` .Values.global.proxy.statusPort) (annotation .ObjectMeta `traffic.sidecar.istio.io/excludeInboundPorts` .Values.global.proxy.excludeInboundPorts) }}"
{{- else }}
- "15090,15021"
{{- end }}
{{ if or (isset .ObjectMeta.Annotations `traffic.sidecar.istio.io/includeOutboundPorts`) (ne (valueOrDefault .Values.global.proxy.includeOutboundPorts "") "") -}}
- "-q"
- "{{ annotation .ObjectMeta `traffic.sidecar.istio.io/includeOutboundPorts` .Values.global.proxy.includeOutboundPorts }}"
{{ end -}}
{{ 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 (isset .ObjectMeta.Annotations `traffic.sidecar.istio.io/excludeInterfaces`) -}}
- "-c"
- "{{ index .ObjectMeta.Annotations `traffic.sidecar.istio.io/excludeInterfaces` }}"
{{ end -}}
- "--log_output_level={{ annotation .ObjectMeta `sidecar.istio.io/agentLogLevel` .Values.global.logging.level }}"
{{ if .Values.global.logAsJson -}}
- "--log_as_json"
{{ end -}}
{{ if .Values.istio_cni.enabled -}}
- "--run-validation"
- "--skip-rule-apply"
{{ end -}}
위 예시는 Template 내부에 있는 Yaml 중 init-container의 Argument를 설정하는 부분을 발췌했습니다. 자세히 살펴보면 Template에는 사용자가 지정한 값을 기반으로 사이드카 주입을 위한 Yaml의 최종 내용이 결정되도록 디자인 되었음을 확인할 수 있습니다.
해당 Template은 이미 서버 기동시점에 주입이 되었으므로 사용자 요청을 분석하여 Template을 만드는 작업을 수행합니다.
4-5 Template 적용
사이드카 컨테이너 주입을 위한 Template을 만들었으면, 이제 사용자가 요청한 Pod에 Template을 병합하는 과정을 수행합니다.

이를 위해서 첫번째로 하는 작업은 병합을 원활하게 수행하기 위해 Original Pod의 요청 Spec과 Template Yaml을 Map으로 변환합니다.

위 그림은 Pod의 요청을 Map으로 변환한 originalMap과 Template Yaml을 Map으로 변환한 patchMap의 결과입니다. 내용을 살펴보면, patchMap에는 init-container 한개와 Container한개가 존재하는 것을 확인할 수 있습니다. 위 두 컨테이너의 이미지는 pilot-agent이며, 사이드카 컨테이너 주입을 통해 기본적으로 1개의 init-container와 Container가 추가로 삽입되는 것을 확인할 수 있습니다.
개별 Container가 어떤 역할을 수행하는지는 다음 포스팅에서 살펴보도록하며, 지금은 위 그림을 통해 서로 다른 요청 Spec을 병합하기 위해 Map으로 만들었음을 이해하면 좋을 것 같습니다.
두 Map을 만들고나면, 그 다음은 두 Map의 Key, Value를 비교하면서 병합하는 작업을 거칩니다.

위와같이 병합을 완료하고나면 최종적으로 하나의 Map이 완성됩니다. 내용을 살펴보면 Template에 존재하던 init-container 추가와 Template Container 더해져서 컨테이너 수가 2가 되었음을 확인할 수 있습니다.
병합이 완료된 이후에는 부가적인 작업을 수행하기 위해 postProcess 과정을 거칩니다. 이때 만약 Prometheus 설정이 존재한다면, 병합된 결과에 추가적으로 Prometheus 통합을 위한 어노테이션 등이 추가되는 작업을 거칩니다.
그리고 k8s에 반영을 요청하기 위해 JSON으로 데이터를 다시 변환 후 AdmissionResponse를 통해 응답을 반환하여 사이드카가 주입된 Pod 생성을 요청하는 것으로 마무리됩니다.
5. 마치며
이번 포스팅까지 해서 pilot-discovery의 가장 핵심인 Service Discovery와 사이드카 컨테이너 주입에 대해서 살펴봤습니다. Pilot-discovery는 위 두가지 기능 외에도 Multi Cluster 관리, 인증서 관리 등 중요한 기능과 핵심 컴포넌트가 여럿 존재합니다.
하지만 istio-internals 시리즈의 목표는 pilot-discovery와 pilot-agent 그리고 envoy가 어떻게 상호 작용하는지를 살펴보는 것이기 때문에 흐름을 유지하기 위해 우선 pilot-discovery에 대한 탐구는 여기서 마치고 나머지는 추후 포스팅을 통해 다루어 보겠습니다.
다음 포스팅에서는 Sidecar Injector에 의해서 주입된 사이드카 컨테이너의 init-container에 대해서 살펴보겠습니다.
'MSA > Istio' 카테고리의 다른 글
| 13. [istio-internals] Pilot-agent - 사이드카 컨테이너 (1) | 2026.03.03 |
|---|---|
| 12. [istio-internals] Pilot-agent - init-container (0) | 2026.03.03 |
| 10. [istio-internals] Pilot-discovery - XDS Server (0) | 2026.03.03 |
| 9. [istio-internals] istio - Service Discovery (0) | 2026.03.03 |
| 8. [envoy-internals] Client 요청 전달 과정 이해하기 - 2 (0) | 2026.03.03 |