OpenTracing 사용하여 Kubernetes NGINX Ingress Controller 추적

OpenTracing 은 분산 추적을 위한 사양 및 API 규격이자 집합입니다. Open Tracing은 9개 프로그래밍 언어 라이브러리를 제공하며 이번 포스트에서 구성 방법을 알아보겠습니다.

지난 몇 년간 Kubernetes에 대한 관심은 꾸준히 증가했으며, 많은 기업들이 Production 환경에서 Microservices 아키텍처와 함께 Kubernetes를 채택하고 있습니다. 그러나 분산 서비스를 채택하는 것은 새로운 문제를 야기합니다. Microservices 기반 애플리케이션에서 발생하는 작업을 이해하고 디버깅하는 것은 특히 서비스가 많은 경우 어려울 수 있습니다. 장애 또는 성능 문제를 발견하려면 애플리케이션을 구성하는 Microservices 간에 데이터가 전달될 때 요청을 End-to-End 간으로 추적하는 분산 추적(Distributed Tracing) 도구가 필요합니다.

Kubernetes용 NGINX 및 NGINX Plus Ingress Controller를 사용하여 클러스터의 트래픽 Load Balancer를 수행할 때 Kubernetes 클러스터에서 HTTP 및 gRPC 요청의 OpenTracing에 대한 기본 지원을 추가하고 있습니다.

OpenTracing을 사용하는 다양한 사용 사례가 있으며, 여기서는 요청 컨텍스트(Context) 전파를 사용하여 서버 Endpoint를 추적하는 데 중점을 두고 있습니다. 분산 환경에서는 클러스터 내의 각 애플리케이션은 서로 다른 서버로 표시됩니다. 클라이언트의 요청을 처리하는 데 관련된 두 개의 애플리케이션 또는 서비스를 상상해 보십시오. 예를 들어 다음 다이어그램에 표시된 토폴로지(Topology)에서 App1은 HTTP 요청을 처리하고 App2로 리디렉션하는 웹 서버입니다. 두 애플리케이션 모두 NGINX Ingress Controller에 의해 Load Balancer된 Kubernetes 클러스터 내에서 실행되고 OpenTracing이 활성화되어 있으므로, App1을 통과하고 App2에 도달할 때 Ingress Controller의 요청을 추적할 수 있습니다.

시스템의 구성 요소(예: 서비스)가 수행하는 각 작업에 대한 정보는 OpenTracing이 Span이라고 부르는 범위에서 캡처되며 Span을 함께 연결하면 클러스터의 Microservices를 통과하는 요청을 식별하고 추적할 수 있습니다.

목차

1. NGINX Ingress Controller 이미지에 OpenTracing 구축
2. Jaeger Tracer 배포
3. OpenTracing 활성화
4. Sample 애플리케이션 배포
4-1. App1 배포
4-2. App2 배포
5. OpenTracing 요청 추적
6. 추적 검토
7. 결론

1. NGINX Ingress Controller 이미지에 OpenTracing 구축

Ingress Controller와 함께 OpenTracing을 사용하려면 OpenTracing 모듈을 NGINX 또는 NGINX Plus Ingress Controller Docker의 이미지에 통합하고 사용 중인 Tracer을 지정해야 합니다.

Ingress Controller GitHub Repository에서 NGINX 및 NGINX Plus에 대한 별도의 Dockerfile을 제공합니다. 둘 다 오픈 소스인 OpenTracing 모듈을 Docker 이미지에 통합하지만 방식은 다릅니다.

  • NGINX의 경우 DockerfileWithOpentracing은 GitHub에서 OpenTracing 모듈을 다운로드하고 Docker Build의 첫 번째 단계에서 수동으로 컴파일합니다.
  • NGINX Plus의 경우 DockerfileWithOpentracingForPlus는 패키지 관리자를 사용하여 NGINX가 오픈 소스 OpenTracing 모듈에서 Build하고 유지 관리하는 동적 모듈을 검색합니다.

다음 단계를 수행합니다.

1. (선택 사항) 기본값인 Jaeger 이외의 Tracer를 지정합니다. Datadog, LightStep 및 Zipkin 플러그인도 사용할 수 있습니다. 다른 Tracer를 사용하려면 OpenTracing 사용 지침의 필수 구성 요소 섹션에 지정된 대로 Dockerfile을 수정하십시오.

2. Ingress Controller Repository의 지침에 따라 Docker 이미지를 Build합니다. 3단계에서 적절한 Dockerfile을 지정하십시오.

NGINX의 경우:

$ make clean
$ make DOCKERFILE=DockerfileWithOpentracing PREFIX=YOUR-PRIVATE-REGISTRY/nginx-ingress

NGINX Plus의 경우:

$ make clean
$ make DOCKERFILE=DockerfileWithOpentracingForPlus PREFIX=YOUR-PRIVATE-REGISTRY/nginx-plus-ingress

3. Ingress Controller Repository의 지침에 따라 이미지를 설치합니다.

섹션 3에서 kubectl apply 명령을 실행하기 전에 YAML 파일을 업데이트하여 OpenTracing이 통합된 새로 Build된 이미지를 지정해야 합니다.

containers:
   - image: IMAGE_WITH_OPENTRACING

2. Jaeger Tracer 배포

이 포스트에서는 기본 Tracer인 Jaeger를 사용하고 단순함을 위해 클러스터 내부에 배포하지만 외부에도 배포할 수 있습니다. OpenTracing을 사용하는 Ingress Controller Pod와 애플리케이션 Pod는 Tracer에 액세스할 수 있어야 합니다.

또한 단순함을 위해 Jaeger에서 제공하는 All-In-One 템플릿을 사용하여 클러스터에서 Non-Production인 Jaeger 인스턴스를 설정합니다. 개발 환경에 적합한 결과 배포는 Jaeger Pod와 Pod에 액세스하는 데 필요한 서비스 세트를 생성합니다. 이는 메모리 내 스토리지 및 기능 제한을 사용하여 기본 Namespace에 최신 Jaeger 버전을 설정합니다.

$ kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-kubernetes/master/all-in-one/jaeger-all-in-one-template.yml

Production에서는 설치를 위해 Jaeger Operator를 사용하는 것이 좋습니다. Jaeger에 대한 자세한 내용은 Jaeger 웹 사이트를 참조하십시오.

3. OpenTracing 활성화

NGINX Ingress Controller에 대한 다음 ConfigMap(nginx-config.yaml)은 전역적으로 OpenTracing을 활성화합니다. 데이터 섹션에 세 개의 새 ConfigMap 키를 추가합니다.

  • opentracing key는 클러스터에서 생성된 모든 Ingress 리소스에 대해 OpenTracing을 활성화합니다.
  • opentracing-tracer key는 Tracer 라이브러리에 대한 경로를 지정합니다. Tracer 라이브러리를 Build할 때 라이브러리가 다운로드되어 Ingress Controller 이미지에 복사됩니다.
  • opentracing-tracer-config key는 Tracer 구성을 포함합니다. service_name 필드는 실제 Span과 관련된 서비스를 정의하고, reporter 필드는 Tracer의 주소와 포트(이 경우 이전 섹션의 All-In-One 템플릿에 의해 배포된 Jaeger 서비스의 주소와 포트)를 지정합니다. sampler 필드는 클라이언트 샘플링(Sampling) 구성을 지정합니다. 단순함을 위해 모든 Tracer를 샘플링하는 “Constant” 샘플러(const)를 구성합니다.
kind: ConfigMap
apiVersion: v1
metadata:
  name: nginx-config
  namespace: nginx-ingress
data:
  opentracing: "True"
  opentracing-tracer: "/usr/local/lib/libjaegertracing_plugin.so"
  opentracing-tracer-config: |
    {
      "service_name": "nginx-ingress",
      "sampler": {
        "type": "const",
        "param": 1
      },
      "reporter": {
        "localAgentHostPort": "jaeger-agent.default.svc.cluster.local:6831"
      }
    }

구성을 적용하려면 다음을 실행하십시오.

$ kubectl apply –f nginx-config.yaml

4. Sample 애플리케이션 배포

단순함을 위해 NGINX 인스턴스를 Backend 애플리케이션으로 사용하고 있습니다. 모든 HTTP 트래픽을 App2로 리디렉션하도록 구성된 두 개의 App1 서로 다른 애플리케이션이 있습니다.

두 애플리케이션 모두 ConfigMap과 볼륨을 사용하여 Pod 내에서 실행되는 NGINX 인스턴스를 구성합니다. ConfigMap 중 하나에 NGINX 구성이 있습니다. 다른 하나는 각 NGINX 인스턴스에 대한 Tracer 구성을 지정하는 데 사용됩니다(앞에서 언급한 바와 같이 요청에 대한 정보를 전송하려면 Tracer에 액세스할 수 있어야 하므로 각 애플리케이션에 해당 구성을 포함합니다).

4-1. App1 배포

첫 번째 애플리케이션(app1.yaml)에 대한 YAML Manifest는 다음과 같습니다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: app1-config
data:
  nginx.conf: |-
    user nginx;
    worker_processes  1;
    load_module modules/ngx_http_opentracing_module.so;
    error_log  /var/log/nginx/error.log warn;
    pid        /var/run/nginx.pid;
    events {
        worker_connections  1024;
    }
    http {
        opentracing_load_tracer /usr/local/lib/libjaegertracing_plugin.so 
                                /etc/jaeger-config.json;
        opentracing on;
        server {
            listen 80;
            server_name example.com;
            location / {
                opentracing_propagate_context;
                proxy_set_header Host $host;
                proxy_pass http://app2-svc:80;
            }
        }
    }
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: jaeger-config-app1
data:
  jaeger-config.json: |-
    {
      "service_name": "app1",
      "sampler": {
        "type": "const",
        "param": 1
      },
      "reporter": {
        "localAgentHostPort": "jaeger-agent.default.svc.cluster.local:6831"
      }
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app1
  template:
    metadata:
      labels:
        app: app1
    spec:
      containers:
      - name: app1
        image: opentracing/nginx-opentracing
        ports:
        - containerPort: 80
        volumeMounts:
          - name: config-app1
            mountPath: /etc/nginx/nginx.conf
            subPath: nginx.conf
            readOnly: true
          - name: config-jaeger
            mountPath: /etc/jaeger-config.json
            subPath: jaeger-config.json
            readOnly: true
      volumes:
      - name: config-app1
        configMap:
          name: app1-config
      - name: config-jaeger
        configMap:
          name: jaeger-config-app1
---
apiVersion: v1
kind: Service
metadata:
  name: app1-svc
spec:
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: app1

첫 번째 애플리케이션을 배포하려면 다음 명령을 실행합니다.

$ kubectl apply –f app1.yaml

4-2. App2 배포

두 번째 애플리케이션(app2.yaml)에 대한 YAML Manifest는 다음과 같습니다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: app2-config
data:
  nginx.conf: |-
    user nginx;
    worker_processes  1;
    load_module modules/ngx_http_opentracing_module.so;
    error_log  /var/log/nginx/error.log warn;
    pid        /var/run/nginx.pid;
    events {
        worker_connections  1024;
    }
    http {
        opentracing_load_tracer /usr/local/lib/libjaegertracing_plugin.so 
                                /etc/jaeger-config.json;
        opentracing on;
        server {
            listen 80;
            server_name example.com;
            location / {
                opentracing_propagate_context;
                opentracing_tag app app2;
                return 200 "Success!\n";
            }
        }
    }
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: jaeger-config-app2
data:
  jaeger-config.json: |-
    {
      "service_name": "app2",
      "sampler": {
          "type": "const",
          "param": 1
      },
      "reporter": {
          "localAgentHostPort": "jaeger-agent.default.svc.cluster.local:6831"
      }
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app2
  template:
    metadata:
      labels:
        app: app2
    spec:
      containers:
      - name: app2
        image: opentracing/nginx-opentracing
        ports:
        - containerPort: 80
        volumeMounts:
          - name: config-app2
            mountPath: /etc/nginx/nginx.conf
            subPath: nginx.conf
            readOnly: true
          - name: config-jaeger
            mountPath: /etc/jaeger-config.json
            subPath: jaeger-config.json
            readOnly: true
      volumes:
      - name: config-app2
        configMap:
          name: app2-config
      - name: config-jaeger
        configMap:
          name: jaeger-config-app2
---
apiVersion: v1
kind: Service
metadata:
  name: app2-svc
spec:
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: app2

두 번째 애플리케이션을 배포하려면 다음 명령을 실행합니다.

$ kubectl apply -f app2.yaml

4-3. Ingress 리소스 배포

클러스터 외부에서 App1에 액세스할 수 있도록 다음 Ingress 리소스(opentracing-ingress.yaml)를 생성합니다.

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: opentracing-ingress
  annotations:
    nginx.org/location-snippets: |
      opentracing_propagate_context;
spec:
  rules:
  - host: example.com
    http:
      paths:
      - path: /
        backend:
          serviceName: app1-svc
          servicePort: 80

Ingress Controller 레벨에서 tracing context propagation을 활성화하기 위해 nginx.org/location-snippets 주석을 어떻게 사용했는지 확인하십시오. Snippet 주석은 Ingress Controller의 최종 NGINX 구성에 사용자 지정 코드를 추가하는 방법입니다.

마지막으로 다음 명령을 실행하여 입력 리소스를 적용합니다.

$ kubectl apply -f opentracing-ingress.yaml

App1과 App2가 모두 실행 중인지 확인합니다.

$ kubectl get pods
NAME                      READY   STATUS    RESTARTS   AGE
app1-68fd9db45c-szqpr     1/1     Running   0          53m
app2-67c7788789-lvbgw     1/1     Running   0          53m

5. OpenTracing 요청 추적

이제 Ingress Controller를 통해 App1에 요청하기만 하면 됩니다.

  • IC_HTTP_PORT는 Ingress Controller pod(기본적으로 80)의 HTTP 포트입니다.
  • IC_IP_ADDRESS는 Ingress Controller pod의 IP 주소입니다. Minikube를 사용하는 경우 Minikube IP 주소입니다.
$ curl --resolve example.com:IC_HTTP_PORT:IC_IP_ADDRESS http://example.com:IC_HTTP_PORT/ --insecure
Success!

6. 추적 검토

다음 명령을 실행하여 Jaeger UI에 대한 액세스를 활성화합니다. 여기서 JAEGER_POD는 Jaeger Tracer 배포의 All-In-One 템플릿에서 생성한 Pod의 “type/name” 값입니다.

$ kubectl port-forward JAEGER_POD 16686:16686

모든 요청은 새로운 추적을 생성합니다. 방금 요청한 추적을 보려면 브라우저에서 http://localhost:16686의 Jaeger UI를 열고 왼쪽 열의 Service 필드에 nginx-egress를 입력한 다음 열 하단의 Find Traces 버튼을 클릭합니다.

OpenTracing 방법1

검색 창의 오른쪽 열에서 nginx-ingress를 클릭하여 추적을 선택합니다. 열려 있는 창(아래)에서 요청에 대한 세 개의 Span을 볼 수 있습니다. 갈색은 Ingress Controller, 파란색은 App1, 노란색은 App2를 나타냅니다.

OpenTracing 방법2

HTTP 상태 코드, 호스트 이름, IP 주소 및 Jaeger 버전을 포함하여 Span에 대한 자세한 정보를 표시하려면 Span을 클릭하십시오.

OpenTracing 방법3

7. 결론

Kubernetes 서비스에 대해 OpenTracing을 활성화하면 사용자와 사용자팀이 애플리케이션에서 발생하는 현상을 더 빨리 이해하고 문제를 빠르게 디버깅할 수 있습니다.

또한 Ingress Controller Pod에서 시작하는 요청을 추적할 수 있으므로 모든 서비스를 통과할 때 클러스터 외부에서 전송된 요청을 완벽하게 파악할 수 있습니다.

솔루션 개요를 확인하고 NGINX Plus 성능을 직접 테스트 및 사용해 보려면 지금 30일 무료 평가판을 신청하거나 사용 사례에 대해 최신 소식을 빠르게 전달받고 싶으시면 아래 뉴스레터를 구독하세요.