🛠️TOOL/☸️Kubernetes

Argo Workflows의 설치 및 Ingress를 통한 외부 접속 (with. Public Cloud)

KangManJoo 2024. 10. 27. 19:16

이번 포스팅에서는 퍼블릭 클라우드에 배포된 K8S 클러스터에 Argo Workflow를 배포하고, 외부에서 접속가능하도록 하는 과정을 진행해보겠습니다.

같은 환경에서 진행하고 싶으신 분들은 다음 링크의 terraform 파일을 사용하시면 됩니다.
➡️ 클라우드 세팅용 테라폼
혹은 배포된 클라우드에서 다음 스크립트 파일을 다운받아 실행해주세요.
➡️쿠버네티스 배포용 스크립트 파일

목표 아키텍처

최종 구성 아키텍처는 다음과 같습니다.

image.png

Argo Workflow 설치

먼저 다음과 같이 argoWorkflow를 설치해줍니다.

kubectl create namespace argo

kubectl apply -n argo -f https://github.com/argoproj/argo-workflows/releases/download/v3.4.4/install.yaml

kubectl patch deployment \
  argo-server \
  --namespace argo \
  --type='json' \
  -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/args", "value": [
  "server",
  "--auth-mode=server"
]}]'

마지막 부분은 컨테이너 인수 값을 변경해 argo 서버가 서버 모드 인증을 사용하도록 설정합니다.

argo-server는 기본적으로 클라이언트 인증이며, UI는 클라이언트가 인증을 위해 그들의 쿠버네티스 bear token을 제공해야 합니다.

하지만 테스트 환경이기 때문에 UI 로그인을 우회하도록 인증 모드를 서버로 전환하면 argo workflow는 내부적으로 토큰을 발행해 사용자를 인증할 수 있습니다.

ingress 설치

argo-workflow의 외부 노출은 ingress를 통해서 해줄 것입니다.
먼저 nginx ingress controller를 설치해줍니다.

kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.5.1/deploy/static/provider/cloud/deploy.yaml

외부노출

이제 외부 노출을 위해서 workflow의 서비스를 만들고, ingress를 만들어주어야 합니다.
하지만 workflow의 서비스는 다음과 같이 workflow의 설치와 함께 이미 만들어져 있습니다.
port등 바꾸고 싶은 설정은 edit을 통해 변경해줍니다.

이제 다음 명령어로 인그레스의 기본 틀을 생성해줍니다.
자세한 옵션들은 kubectl create ingress -h 옵션으로 확인 가능합니다.

kubectl create ingress ingress-workflow --rule="/workflow=argo-server:2746" -n argo

그러나 아직은 외부에서 접속이 불가능합니다.
ingress-controller를 확인해보면 클라우드의 로드밸런서를 사용하지 않아 다음과 같이 아직 공인 ip가 부여되지 않은 것을 확인할 수 있습니다.

만약 Load Balancer 타입을 사용할 경우 퍼블릭 클라우드의 로드밸런서를 통해 자동으로 공인 IP를 부여할 수 있지만, 그렇게 되면 클라우드 비용이 부담스러워집니다.

따라서 ingress-controller의 타입을 다음과 같이 Node Port로 변경해주었습니다.

기존 스펙

spec:
  type: LoadBalancer
  allocateLoadBalancerNodePorts: true
  clusterIP: 10.233.41.91
  clusterIPs:
  - 10.233.41.91
  externalTrafficPolicy: Local
  healthCheckNodePort: 31699
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - appProtocol: http
    name: http
    nodePort: 31595
    port: 80
    protocol: TCP
    targetPort: http
  - appProtocol: https
    name: https
    nodePort: 32759
    port: 443
    protocol: TCP
    targetPort: https
  selector:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  sessionAffinity: None

변경된 스펙

spec:
  clusterIP: 10.233.41.91
  clusterIPs:
  - 10.233.41.91
  externalTrafficPolicy: Cluster
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  ipFamilyPolicy: SingleStack
  ports:
  - appProtocol: http
    name: http
    nodePort: 31595
    port: 80
    protocol: TCP
    targetPort: http
  - appProtocol: https
    name: https
    nodePort: 32759
    port: 443
    protocol: TCP
    targetPort: https
  selector:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
  sessionAffinity: None
  type: NodePort

여기서 주의할 점은 externalTrafficPolicyCluster 로 설정해주어야 외부 트래픽을 처리할 때 노드에서 수신 후 클러스터 내의 다른 노드에 있는 파드로 전달할 수 있습니다.
따라서 어떤 노드의 IP로 접근하여도 UI를 반환하도록 할 수 있습니다.

그리고 잊지 않고 테라폼 코드를 통해 gcp 인스턴스들의 방화벽 포트를 열어줍니다.

resource "google_compute_firewall" "allow-ingress-nodeports" {
  name    = "allow-ingress-nodeports"
  network = var.network

  allow {
    protocol = "tcp"
    ports    = ["31595", "32759"]
  }

  source_ranges = ["0.0.0.0/0"]
  target_tags   = ["kube-worker"]
}

포트가 열린 것을 확인했으나, 여전히 접속이 되지 않았습니다.

ingressClassName

ingress nginx controller 파드의 로그를 확인해봅니다.

로그를 분석해보면 ingressClassName 필드가 누락되어 있어 Ingress Controller가 이를 무시하고 있는 것으로 확인되었습니다.

ingressClass는 클러스터 내에 여러 개의 ingress Controller가 존재할 때, ingress 리소스가 어떤 컨트롤러에서 처리될지를 지정하는 리소스입니다.

ingress 리소스를 수정해줍니다.

이전 스펙

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  creationTimestamp: "2024-10-11T07:12:11Z"
  generation: 1
  name: ingress-workflow
  namespace: argo
  resourceVersion: "1853049"
  uid: 634c2ef8-0a9b-4294-9c78-45249480b313
spec:
  rules:
  - http:
      paths:
      - backend:
          service:
            name: argo-server
            port:
              number: 2746
        path: /workflow
        pathType: Exact
status:
  loadBalancer: {}

수정한 스펙

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-workflow
  namespace: argo
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx  # IngressClassName 추가
  rules:
  - http:
      paths:
      - backend:
          service:
            name: argo-server
            port:
              number: 2746
        path: /workflow
        pathType: Exact

그러나 IP로 접속을 해도 여전히 502 bad gateway가 발생했습니다.

image.png

workflow pod의 로그를 확인해보니 다음과 같이 접속 시도를 한 로그는 확인 가능했습니다.
time="2024-10-11T13:17:40.143Z" level=info duration="126.852µs" method=GET path=index.html size=473 status=0

즉, ingress controller를 통해 workflow 서버로 요청은 잘 전달되었으나 요청이 '제대로' 도착하지 않고 있었습니다.

Ingress 재수정

Ingress를 다음과 같이 수정해줍니다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    nginx.ingress.kubernetes.io/backend-protocol: HTTPS
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
    nginx.ingress.kubernetes.io/rewrite-target: /$2
    nginx.ingress.kubernetes.io/ssl-passthrough: "true"
  creationTimestamp: "2024-10-11T12:16:22Z"
  generation: 5
  name: ingress-workflow
  namespace: argo
  resourceVersion: "1956557"
  uid: 5e3ddb60-2f32-4982-8be7-a876d34bd8f6
spec:
  ingressClassName: nginx
  rules:
  - http:
      paths:
      - backend:
          service:
            name: argo-server
            port:
              number: 2746
        path: /workflow(/|$)(.*)
        pathType: Prefix
status:
  loadBalancer:
    ingress:
    - ip: 10.233.41.91

변경 사항

  • nginx.ingress.kubernetes.io/backend-protocol: HTTPS: Ingress Controller가 백엔드 서비스(argo-server)와의 통신에 HTTPS를 사용하도록 지정합니다.
  • nginx.ingress.kubernetes.io/force-ssl-redirect: "true": HTTP로 들어오는 요청을 HTTPS로 리디렉트하여, 모든 외부 트래픽이 HTTPS를 사용하도록 강제합니다.
  • nginx.ingress.kubernetes.io/ssl-passthrough: "true": Ingress Controller가 SSL을 해제하지 않고, 클라이언트와 백엔드 간의 SSL 연결을 그대로 전달합니다. 즉, Ingress Controller는 SSL을 처리하지 않고 직접 백엔드로 보냅니다.
  • nginx.ingress.kubernetes.io/rewrite-target: /$2: 요청이 /workflow 경로로 들어올 때, $2를 사용하여 정규 표현식에서 캡처된 하위 경로를 유지하면서 /workflow를 제거한 후 백엔드로 전달합니다. (ex. /workflow/page 요청은 /page로 workflow 서버에 전달됩니다.)
  • path: /workflow(/|$)(.*), pathType: Prefix: /workflow로 시작하는 모든 경로가 백엔드로 전달되며, 하위 경로도 허용됩니다. 예를 들어, /workflow/page와 같은 하위 경로가 모두 이 규칙과 매칭됩니다.

workflow UI 404 에러?

ingress 수정 후 502 badgateway는 해결했으나, 여전히 argo workflow의 UI를 불러오는 데에는 실패했습니다.

오류가 난 부분을 확인해보니, /workflow 경로로 접속 후 UI의 main.JS에 대한 요청을 다시 보내게 되는데, https://<공인 IP>:32759/workflow/main.js 가 아닌 https://<공인 IP>:32759/main.js 으로 보내고 있었습니다.

argo-workflow deployment 수정

이 에러는 argo-server pod의 deployment을 수정해 해결해줍니다.

kubectl edit deployment argo-server -n argo

spec.template.spec.containers.env 에 다음 내용을 추가해줍니다.

env:
- name: BASE_HREF
  value: /workflow/

BASE_HREF/workflow/로 설정되어 있으면, Argo Workflows UI는 모든 정적 리소스를 /workflow/ 경로 아래에서 찾습니다. 따라서 Ingress 설정에서 /workflow 경로를 사용하여 UI를 제공할 때, Argo Workflows가 올바르게 파일을 로드하도록 경로를 맞출 수 있습니다.

이제 웹 브라우저에서도 정상 접속이 되는 것을 확인할 수 있었습니다.
추후 편리한 사용을 위해 도메인 등록까지 해줄 예정입니다.

PS) 에러가 발생할 때마다 함께 고민해 주신 오픈소스 컨트리뷰톤 Argo CD 프로젝트의 멘토, 멘티 분들께 감사드립니다 🙇🏻‍♂️

reference