Service Mesh 트러블슈팅

Service Mesh 트러블슈팅 (Service Mesh Troubleshooting)

이 문서는 Cilium Service Mesh 기능(특히 Ingress 컨트롤러와 CiliumEnvoyConfig) 문제를 진단하는 방법을 안내해요. Cilium CLI 설치부터 연결성 문제 격리까지 단계별로 살펴봅니다.

출처: Service Mesh Troubleshooting

본문

Cilium CLI 설치

최신 버전의 Cilium CLI를 설치하세요. Cilium CLI는 Cilium 설치, Cilium 설치 상태 점검, 다양한 기능(예: clustermesh, Hubble) 활성화/비활성화에 사용할 수 있어요.

Linux:

CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
CLI_ARCH=amd64
if [ "$(uname -m)" = "aarch64" ]; then CLI_ARCH=arm64; fi
curl -L --fail --remote-name-all https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}
sha256sum --check cilium-linux-${CLI_ARCH}.tar.gz.sha256sum
sudo tar xzvfC cilium-linux-${CLI_ARCH}.tar.gz /usr/local/bin
rm cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}

macOS:

CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
CLI_ARCH=amd64
if [ "$(uname -m)" = "arm64" ]; then CLI_ARCH=arm64; fi
curl -L --fail --remote-name-all https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-darwin-${CLI_ARCH}.tar.gz{,.sha256sum}
shasum -a 256 -c cilium-darwin-${CLI_ARCH}.tar.gz.sha256sum
sudo tar xzvfC cilium-darwin-${CLI_ARCH}.tar.gz /usr/local/bin
rm cilium-darwin-${CLI_ARCH}.tar.gz{,.sha256sum}

전체 릴리스 페이지도 참고하세요.

일반 (Generic)

  1. ds/cilium 및 deployment/cilium-operator 파드가 모두 정상(healthy)이고 준비(ready) 상태인지 확인하세요.
$ cilium status

설치 수동 검증

  1. kubeProxyReplacement가 true인지 확인하세요.
$ kubectl exec -n kube-system ds/cilium -- cilium-dbg status
...
KubeProxyReplacement:    True
...
  1. 런타임에 enable-envoy-config와 enable-ingress-controller 값이 true인지 확인하세요. 고객이 CiliumEnvoyConfig 또는 CiliumClusterwideEnvoyConfig CRD만 사용하는 경우 ingress controller 플래그는 선택사항이에요.
$ kubectl -n kube-system get cm cilium-config -o json | egrep "enable-ingress-controller|enable-envoy-config"
        "enable-envoy-config": "true",
        "enable-ingress-controller": "true",

Ingress 트러블슈팅

내부적으로 Cilium Ingress 컨트롤러는 각 Ingress 리소스마다 하나의 Load Balancer 서비스, 하나의 CiliumEnvoyConfig, 하나의 더미(dummy) Endpoint 리소스를 생성해요.

$ kubectl get ingress
NAME            CLASS    HOSTS   ADDRESS        PORTS   AGE
basic-ingress   cilium   *       10.97.60.117   80      16m

# For dedicated Load Balancer mode
$ kubectl get service cilium-ingress-basic-ingress
NAME                           TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)        AGE
cilium-ingress-basic-ingress   LoadBalancer   10.97.60.117   10.97.60.117   80:31911/TCP   17m

# For dedicated Load Balancer mode
$ kubectl get cec cilium-ingress-default-basic-ingress
NAME                                   AGE
cilium-ingress-default-basic-ingress   18m

# For shared Load Balancer mode
$ kubectl get services -n kube-system cilium-ingress
NAME             TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)                      AGE
cilium-ingress   LoadBalancer   10.111.109.99   10.111.109.99   80:32690/TCP,443:31566/TCP   38m

# For shared Load Balancer mode
$ kubectl get cec -n kube-system cilium-ingress
NAME             AGE
cilium-ingress   15m
  1. Load Balancer 서비스에 외부 IP 또는 FQDN이 할당됐는지 확인하세요. 오래 지나도 사용할 수 없으면 각 클라우드 제공업체의 Load Balancer 관련 문서를 확인하세요.
  2. Cilium이 CiliumEnvoyConfig 리소스를 프로비저닝하는 동안 경고나 오류 메시지가 있는지 확인하세요. Cilium Ingress 컨트롤러에서 기원한 CEC 리소스에서는 이런 일이 발생할 가능성이 낮아요.

참고

이러한 Envoy 리소스는 K8s에서 전혀 검증되지 않으므로, Envoy 리소스의 오류는 이 CRD를 관찰하는 Cilium 에이전트를 통해서만 보여요. 즉 kubectl apply는 성공을 보고하지만, 노드 로컬 Envoy 인스턴스에 리소스를 파싱·설치하는 과정은 실패했을 수 있어요. 현재 이를 검증하는 유일한 방법은 Cilium 에이전트 로그에서 오류와 경고를 관찰하는 거예요. 또한 Cilium 에이전트는 클러스터의 충돌하는 Envoy 리소스에 대해 경고 로그를 출력해요.

참고

Cilium Ingress 컨트롤러는 필요한 Envoy 리소스를 내부적으로(under the hood) 구성해요. Envoy 리소스를 명시적으로 생성한다면 충돌이 없는지 Cilium 에이전트 로그를 확인하세요.

연결성 트러블슈팅

이 섹션은 주로 Ingress 리소스의 연결성 문제를 격리하기 위한 것이지만, 수동으로 구성한 CiliumEnvoyConfig 리소스에도 같은 단계를 적용할 수 있어요.

아래 값으로 debug와 debug-verbose를 활성화하는 것이 좋아요. Cilium 플래그를 변경하면 Cilium 에이전트와 operator의 재시작이 필요하다는 점에 유의하세요.

$ kubectl get -n kube-system cm cilium-config -o json | grep "debug"
        "debug": "true",
        "debug-verbose": "flow",

참고

발신 소스 IP(originating source IP)가 ingress 트래픽 적용에 사용돼요.

요청은 일반적으로 LoadBalancer 서비스에서 노드의 미리 할당된 포트로 이동한 뒤 Cilium Envoy 프록시로 전달되고, 마지막으로 실제 백엔드 서비스로 프록시돼요.

  1. 클라우드 Load Balancer에서 노드 포트까지의 첫 단계는 Cilium 범위를 벗어나요. 클러스터가 제대로 구성됐는지 각 클라우드 제공업체 관련 문서를 확인하세요.
  2. 두 번째 단계는 기본 호스트에 SSH로 접속해 관련 포트의 localhost로 비슷한 요청을 보내 확인할 수 있어요:
$ kubectl get service cilium-ingress-basic-ingress
NAME                           TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)        AGE
cilium-ingress-basic-ingress   LoadBalancer   10.97.60.117   10.97.60.117   80:31911/TCP   17m

# After ssh to any of k8s node
$ curl -v http://localhost:31911/
*   Trying 127.0.0.1:31911...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 31911 (#0)
> GET / HTTP/1.1
> Host: localhost:31911
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 503 Service Unavailable
< content-length: 19
< content-type: text/plain
< date: Thu, 07 Jul 2022 12:25:56 GMT
< server: envoy
<
* Connection #0 to host localhost left intact

# Flows for world identity
$ kubectl -n kube-system exec ds/cilium -- hubble observe -f --identity 2
Jul  7 12:28:27.970: 127.0.0.1:54704 <- 127.0.0.1:13681 http-response FORWARDED (HTTP/1.1 503 0ms (GET http://localhost:31911/))

또는 Envoy 프록시 포트에 직접 요청을 보낼 수도 있어요. Ingress의 경우 프록시 포트는 Cilium Ingress 컨트롤러가 임의로 할당해요. 수동으로 구성한 CiliumEnvoyConfig 리소스의 경우 프록시 포트를 spec에서 직접 가져와요.

$  kubectl logs -f -n kube-system ds/cilium --timestamps | egrep "envoy|proxy"
...
2022-07-08T08:05:13.986649816Z level=info msg="Adding new proxy port rules for cilium-ingress-default-basic-ingress:19672" proxy port name=cilium-ingress-default-basic-ingress subsys=proxy

# After ssh to any of k8s node, send request to Envoy proxy port directly
$ curl -v  http://localhost:19672
*   Trying 127.0.0.1:19672...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 19672 (#0)
> GET / HTTP/1.1
> Host: localhost:19672
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 503 Service Unavailable
< content-length: 19
< content-type: text/plain
< date: Fri, 08 Jul 2022 08:12:35 GMT
< server: envoy

위와 같은 응답을 보면 요청이 프록시로 성공적으로 리다이렉트된 것이에요. HTTP 응답에는 그에 따라 특별한 헤더 server: envoy가 포함돼요. 같은 내용을 hubble observe 명령으로도 관찰할 수 있어요: Hubble로 흐름 관찰하기.

가장 흔한 근본 원인은 Cilium Envoy 프록시가 노드에서 실행 중이지 않거나, CEC 리소스 프로비저닝에 다른 문제가 있는 경우예요.

$ kubectl exec -n kube-system ds/cilium -- cilium-dbg status
...
Controller Status:       49/49 healthy
Proxy Status:            OK, ip 10.0.0.25, 6 redirects active on ports 10000-20000
Global Identity Range:   min 256, max 65535
  1. 위 단계가 성공적으로 완료됐다고 가정하면, 다음으로 외부 IP 또는 FQDN을 통해 요청을 보낼 수 있어요. 백엔드 서비스가 준비되고 정상인지 다시 확인하세요. Envoy Discovery Service(EDS)의 이름은 <namespace>/<service-name>:<port> 규칙을 따릅니다.
$ LB_IP=$(kubectl get ingress basic-ingress -o json | jq '.status.loadBalancer.ingress[0].ip' | jq -r .)
$ curl -s http://$LB_IP/details/1
no healthy upstream

$ kubectl get cec cilium-ingress-default-basic-ingress -o json | jq '.spec.resources[] | select(.type=="EDS")'
{
  "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
  "connectTimeout": "5s",
  "name": "default/details:9080",
  "outlierDetection": {
    "consecutiveLocalOriginFailure": 2,
    "splitExternalLocalOriginErrors": true
  },
  "type": "EDS",
  "typedExtensionProtocolOptions": {
    "envoy.extensions.upstreams.http.v3.HttpProtocolOptions": {
      "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions",
      "useDownstreamProtocolConfig": {
        "http2ProtocolOptions": {}
      }
    }
  }
}
{
  "@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
  "connectTimeout": "5s",
  "name": "default/productpage:9080",
  "outlierDetection": {
    "consecutiveLocalOriginFailure": 2,
    "splitExternalLocalOriginErrors": true
  },
  "type": "EDS",
  "typedExtensionProtocolOptions": {
    "envoy.extensions.upstreams.http.v3.HttpProtocolOptions": {
      "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions",
      "useDownstreamProtocolConfig": {
        "http2ProtocolOptions": {}
      }
    }
  }
}

모든 것이 올바르게 구성됐다면 아래처럼 world(identity 2), ingress(identity 8) 및 백엔드 파드에서 오는 흐름을 볼 수 있어요.

# Flows for world identity
$ kubectl exec -n kube-system ds/cilium -- hubble observe --identity 2 -f
Defaulted container "cilium-agent" out of: cilium-agent, mount-cgroup (init), apply-sysctl-overwrites (init), mount-bpf-fs (init), clean-cilium-state (init)
Jul  7 13:07:46.726: 192.168.49.1:59608 -> default/details-v1-5498c86cf5-cnt9q:9080 http-request FORWARDED (HTTP/1.1 GET http://10.97.60.117/details/1)
Jul  7 13:07:46.727: 192.168.49.1:59608 <- default/details-v1-5498c86cf5-cnt9q:9080 http-response FORWARDED (HTTP/1.1 200 1ms (GET http://10.97.60.117/details/1))

# Flows for Ingress identity (e.g. envoy proxy)
$ kubectl exec -n kube-system ds/cilium -- hubble observe --identity 8 -f
Defaulted container "cilium-agent" out of: cilium-agent, mount-cgroup (init), apply-sysctl-overwrites (init), mount-bpf-fs (init), clean-cilium-state (init)
Jul  7 13:07:46.726: 10.0.0.95:42509 -> default/details-v1-5498c86cf5-cnt9q:9080 to-endpoint FORWARDED (TCP Flags: SYN)
Jul  7 13:07:46.726: 10.0.0.95:42509 <- default/details-v1-5498c86cf5-cnt9q:9080 to-stack FORWARDED (TCP Flags: SYN, ACK)
Jul  7 13:07:46.726: 10.0.0.95:42509 -> default/details-v1-5498c86cf5-cnt9q:9080 to-endpoint FORWARDED (TCP Flags: ACK)
Jul  7 13:07:46.726: 10.0.0.95:42509 -> default/details-v1-5498c86cf5-cnt9q:9080 to-endpoint FORWARDED (TCP Flags: ACK, PSH)
Jul  7 13:07:46.727: 10.0.0.95:42509 <- default/details-v1-5498c86cf5-cnt9q:9080 to-stack FORWARDED (TCP Flags: ACK, PSH)

# Flows for backend pod, the identity can be retrieved via cilium identity list command
$ kubectl exec -n kube-system ds/cilium -- hubble observe --identity 48847 -f
Defaulted container "cilium-agent" out of: cilium-agent, mount-cgroup (init), apply-sysctl-overwrites (init), mount-bpf-fs (init), clean-cilium-state (init)
Jul  7 13:07:46.726: 10.0.0.95:42509 -> default/details-v1-5498c86cf5-cnt9q:9080 to-endpoint FORWARDED (TCP Flags: SYN)
Jul  7 13:07:46.726: 10.0.0.95:42509 <- default/details-v1-5498c86cf5-cnt9q:9080 to-stack FORWARDED (TCP Flags: SYN, ACK)
Jul  7 13:07:46.726: 10.0.0.95:42509 -> default/details-v1-5498c86cf5-cnt9q:9080 to-endpoint FORWARDED (TCP Flags: ACK)
Jul  7 13:07:46.726: 10.0.0.95:42509 -> default/details-v1-5498c86cf5-cnt9q:9080 to-endpoint FORWARDED (TCP Flags: ACK, PSH)
Jul  7 13:07:46.726: 192.168.49.1:59608 -> default/details-v1-5498c86cf5-cnt9q:9080 http-request FORWARDED (HTTP/1.1 GET http://10.97.60.117/details/1)
Jul  7 13:07:46.727: 10.0.0.95:42509 <- default/details-v1-5498c86cf5-cnt9q:9080 to-stack FORWARDED (TCP Flags: ACK, PSH)
Jul  7 13:07:46.727: 192.168.49.1:59608 <- default/details-v1-5498c86cf5-cnt9q:9080 http-response FORWARDED (HTTP/1.1 200 1ms (GET http://10.97.60.117/details/1))
Jul  7 13:08:16.757: 10.0.0.95:42509 <- default/details-v1-5498c86cf5-cnt9q:9080 to-stack FORWARDED (TCP Flags: ACK, FIN)
Jul  7 13:08:16.757: 10.0.0.95:42509 -> default/details-v1-5498c86cf5-cnt9q:9080 to-endpoint FORWARDED (TCP Flags: ACK, FIN)

# Sample output of cilium-dbg monitor
$ ksysex ds/cilium -- cilium-dbg monitor
level=info msg="Initializing dissection cache..." subsys=monitor
-> endpoint 212 flow 0x3000e251 , identity ingress->61131 state new ifindex lxcfc90a8580fd6 orig-ip 10.0.0.192: 10.0.0.192:34219 -> 10.0.0.164:9080 tcp SYN
-> stack flow 0x2481d648 , identity 61131->ingress state reply ifindex 0 orig-ip 0.0.0.0: 10.0.0.164:9080 -> 10.0.0.192:34219 tcp SYN, ACK
-> endpoint 212 flow 0x3000e251 , identity ingress->61131 state established ifindex lxcfc90a8580fd6 orig-ip 10.0.0.192: 10.0.0.192:34219 -> 10.0.0.164:9080 tcp ACK
-> endpoint 212 flow 0x3000e251 , identity ingress->61131 state established ifindex lxcfc90a8580fd6 orig-ip 10.0.0.192: 10.0.0.192:34219 -> 10.0.0.164:9080 tcp ACK
-> Request http from 0 ([reserved:world]) to 212 ([k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=default k8s:io.cilium.k8s.policy.cluster=minikube k8s:io.cilium.k8s.policy.serviceaccount=bookinfo-details k8s:io.kubernetes.pod.namespace=default k8s:version=v1 k8s:app=details]), identity 2->61131, verdict Forwarded GET http://10.99.74.157/details/1 => 0
-> stack flow 0x2481d648 , identity 61131->ingress state reply ifindex 0 orig-ip 0.0.0.0: 10.0.0.164:9080 -> 10.0.0.192:34219 tcp ACK
-> Response http to 0 ([reserved:world]) from 212 ([k8s:io.kubernetes.pod.namespace=default k8s:version=v1 k8s:app=details k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name=default k8s:io.cilium.k8s.policy.cluster=minikube k8s:io.cilium.k8s.policy.serviceaccount=bookinfo-details]), identity 61131->2, verdict Forwarded GET http://10.99.74.157/details/1 => 200

더 알아보기 (Learn more)