IngressLink 멀티 클러스터 Blue-Green 트래픽 전환 – BIG-IP CIS 환경

이번 포스트에서는 BIG-IP CIS Standalone Default Mode로 구성된 멀티 클러스터 환경에서 IngressLink 를 활용하여 Blue-Green 트래픽 전환을 구성하는 방법을 살펴보겠습니다.

각 클러스터에 배포된 NGINX Plus Ingress Controller를 BIG-IP의 Pool Member로 구성하고, IngressLink의 multiClusterServices 가중치(weight)를 점진적으로 조정하여 클러스터 간 트래픽을 단계적으로 전환하는 과정을 확인합니다.

본 포스트의 과정은 OpenShift 기반 2개의 클러스터 환경에서 진행되며, 각 클러스터에는 NGINX Plus Ingress Controller가 배포되어 있고, BIG-IP CIS가 Standalone Default Mode로 구성되어 있다고 가정합니다.

기본적인 환경 구성 및 설치 과정은 아래 포스트를 참고하시기 바랍니다.

목차

1. IngressLink란?
2. 환경/버전 정보
3. 백엔드 Pod, NGINX Ingress Controller VirtualServer 구성
4. IngressLink 활용 멀티 클러스터 Blue-Green 트래픽 전환
 4-1. CIS 멀티클러스터 IngressLink 기본 구성
 4-2. 가중치 변경을 통한 멀티 클러스터 Blue-Green 트래픽 전환
5. 결론
IngressLink 구성

IngressLink는 BIG-IP CIS(Container Ingress Services)와 NGINX Ingress Controller(혹은 NGINX Plus Ingress Controller)를 연동하기 위한 Kubernetes Custom Resource입니다.

IngressLink를 통해 BIG-IP는 Kubernetes 클러스터 내 NGINX Ingress Controller를 백엔드로 구성하고, 외부 트래픽을 클러스터로 전달합니다. 이 과정에서 BIG-IP는 싱글 클러스터 기준으로 L4 로드 밸런서 역할을 수행하며, 실제 HTTP/HTTPS 기반의 라우팅은 NGINX Ingress Controller가 담당합니다.

즉, BIG-IP는 트래픽 진입(L4), NGINX는 애플리케이션 라우팅(L7)을 담당합니다.

IngressLink 구성 방식과 동작 구조에 대한 자세한 내용은 F5 IngressLink 를 통해 NGINX Plus Ingress Controller 로드 밸런싱 포스트를 참고하시기 바랍니다.

2. 환경/버전 정보

구성 요소버전
Red Hat Openshift Cluster 14.18.24
Red Hat Openshift Cluster 24.21.6
CNIOVN-Kubernetes
F5 BIG-IP VE17.5.0
F5 Container Ingress Services Operatorv1.21.0
F5 BIG-IP CIS2.20.3
AS3(Application Services 3) Extensionv3.56.0
NGINX Plus Ingress Controller5.4.1

3. 백엔드 Pod, NGINX Ingress Controller VirtualServer 구성

각각의 OpenShift 클러스터에 NGINX Plus Ingress Controller를 통해 트래픽이 전달될 백엔드 Pod와, 트래픽을 전달하기 위한 NGINX Ingress Controller의 VirtualServer 리소스를 구성하겠습니다.

Curl을 사용한 반복 요청의 응답을 통해 트래픽 분산을 확인하기 위해, 클러스터별 백엔드 Pod의 응답은 서로 다르게 구성했습니다.

Deployment를 통해 구성된 Pod는 8080 포트를 통해 요청을 수신하며, Service의 80 포트를 통해 Pod의 8080 포트로 연결됩니다. default 네임스페이스에 배포했습니다.

클러스터 별로 http-echo 이미지를 사용하여 텍스트 응답을 다음과 같이 구성했습니다.

  • cluster1: [Cluster A – BLUE] response from app v1
  • cluster2: [Cluster B – GREEN] response from app v1
cluster1-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-blue
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: backend-blue
version: v1
template:
metadata:
labels:
app: backend-blue
version: v1
spec:
containers:
- name: http-echo
image: hashicorp/http-echo:latest
args:
- "-listen=:8080"
- "-text= [Cluster A - BLUE] response from app v1."
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: backend-svc
namespace: default
spec:
type: ClusterIP
selector:
app: backend-blue
version: v1
ports:
- name: http
port: 80
targetPort: 8080
# cluster1
$ oc apply -f cluster1-app.yaml
deployment.apps/backend-blue created
service/backend-svc created
------
$ oc get po,svc -n default
NAME READY STATUS RESTARTS AGE
pod/backend-blue-7764f9764f-4rvlc 1/1 Running 0 40s
pod/backend-blue-7764f9764f-zk4rf 1/1 Running 0 40s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/backend-svc ClusterIP 172.30.183.86 <none> 80/TCP 40s
service/kubernetes ClusterIP 172.30.0.1 <none> 443/TCP 9d
service/openshift ExternalName <none> kubernetes.default.svc.cluster.local <none> 9d
cluster2-app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-green
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: backend-green
version: v1
template:
metadata:
labels:
app: backend-green
version: v1
spec:
containers:
- name: http-echo
image: hashicorp/http-echo:latest
args:
- "-listen=:8080"
- "-text= [Cluster B - GREEN] response from app v1."
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: backend-svc
namespace: default
spec:
type: ClusterIP
selector:
app: backend-green
version: v1
ports:
- name: http
port: 80
targetPort: 8080
# cluster2
$ oc apply -f cluster2-app.yaml
deployment.apps/backend-green created
service/backend-svc created
------
$ oc get po,svc -n default
NAME READY STATUS RESTARTS AGE
pod/backend-green-5888bd4f95-5pcqc 1/1 Running 0 23s
pod/backend-green-5888bd4f95-ccl6q 1/1 Running 0 23s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/backend-svc ClusterIP 172.30.201.113 <none> 80/TCP 24s
service/kubernetes ClusterIP 172.30.0.1 <none> 443/TCP 9d
service/openshift ExternalName <none> kubernetes.default.svc.cluster.local <none> 8d

배포한 Pod로 NGINX Plus Ingress Controller가 트래픽을 전달하기 위해 NGINX Ingress Controller의 VirtualServer 리소스를 클러스터에 배포합니다. Pod와 동일한 default 네임스페이스에 배포합니다.

Pod의 경우 클러스터 별 분산/전환을 응답으로 확인하기 위해 다른 구성으로 배포했으나, VirtualServer는 두 클러스터에서 모두 동일한 Service를 대상으로 전달하므로 동일 구성을 2개의 클러스터에 모두 배포합니다.

와일드카드 인증서를 Secret으로 생성하여 tls 옵션을 적용했습니다.

tls-backend-vs.yaml
apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
name: tls-backend-vs
namespace: default
spec:
host: tls-ocp-apps.devopssong.site
tls:
secret: tls-secret
upstreams:
- name: backend-upstream
service: backend-svc
port: 80
connect-timeout: 5s
read-timeout: 30s
send-timeout: 30s
routes:
- path: /
action:
proxy:
upstream: backend-upstream

배포 후 Valid 상태임을 확인합니다.

# cluster1,2
$ oc apply -f tls-backend-vs.yaml
virtualserver.k8s.nginx.org/tls-backend-vs created
------
$ oc get virtualservers.k8s.nginx.org -n default
NAME STATE HOST IP PORTS AGE
tls-backend-vs Valid tls-ocp-apps.devopssong.site 9s

앞서 구성한 환경을 기반으로, BIG-IP CIS의 IngressLink를 활용하여 멀티 클러스터 간 트래픽을 어떻게 분산하고 제어할 수 있는지 확인해보겠습니다.

먼저 multiClusterServices 옵션을 사용하여 두 개의 클러스터를 동시에 대상으로 지정했을 때의 기본 분산 동작을 확인하고, 이후 weight 값을 조정하여 트래픽을 점진적으로 전환하는 과정을 살펴봅니다.

멀티 클러스터 환경에서 IngressLink를 구성하려면 multiClusterServices 필드에 각 클러스터의 이름, NGINX Ingress Controller가 배포된 네임스페이스, 서비스 이름을 지정합니다.

ingresslink.yaml
apiVersion: cis.f5.com/v1
kind: IngressLink
metadata:
name: nginx-ingress
namespace: nginx-ingress
spec:
iRules:
- /Common/Proxy_Protocol_iRule
partition: ocp-cluster-1 # BIG-IP 파티션 지정
selector:
matchLabels:
app: ingresslink # 대상이 될 NGINX Ingress Controller Service의 레이블
virtualServerName: cluster1-ingress # BIG-IP에 생성될 VirtualServer 이름
virtualServerAddress: 192.168.40.229 # BIG-IP에 생성될 VIP
# 클러스터 별 NGINX Ingress Controller 지정
multiClusterServices:
- clusterName: cluster1-1
namespace: nginx-ingress
service: npic-nginx-ingress-controller
- clusterName: cluster1-2
namespace: nginx-ingress
service: npic-nginx-ingress-controller
tls:
reference: secret # Kubernetes Secret 참조
clientSSLs: # Client > BIG-IP 구간 인증서
- wc-secret
serverSSLs: # BIG-IP > NGINX Ingress Controller 구간 암호화 채널 구성 용도
- wc-secret

multiClusterServices에는 CIS의 local_cluster_name으로 지정한 로컬 클러스터와, Extended ConfigMap에 정의한 외부 클러스터를 clusterName으로 지정합니다. 해당 값의 구성은 Standalone CIS Default Mode – BIG-IP CIS Multi-Cluster 구성 포스트의 3-2와 4번 과정을 참고하세요.

각 항목의 service에는 각 클러스터에 배포된 NGINX Plus Ingress Controller의 Service 이름을 지정합니다.

tls 옵션 구성

멀티 클러스터 환경에서 IngressLink는 BIG-IP가 각 클러스터의 NGINX Ingress Controller를 Pool로 구성하고, iRule을 통해 트래픽을 분산합니다. 이때 BIG-IP가 정상적인 트래픽 분산 및 장애 대응을 수행하려면 백엔드의 HTTP 응답을 직접 확인할 수 있어야 합니다.

그러나 L4 Passthrough 방식에서는 TLS가 종단되지 않기 때문에 HTTP 응답을 확인할 수 없습니다. 따라서 BIG-IP에서 TLS를 종단(Terminate)하고, NGINX Ingress Controller로 다시 암호화하는 re-encrypt 구성이 필요하며, 이를 위해 IngressLink의 tls 옵션을 설정합니다.

  • Client SSL Profile: 클라이언트와의 HTTPS 수신을 처리하며, clientSSLs에 지정한 Secret의 인증서가 적용됩니다.
  • Server SSL Profile: BIG-IP와 NGINX Ingress Controller 간 트래픽을 다시 암호화하기 위한 채널을 구성합니다.

이번 구성에서는 앞서 생성한 와일드카드 인증서를 wc-secret으로 구성하여 clientSSLs와 serverSSLs에 동일하게 적용했습니다.

멀티클러스터 환경의 TLS 옵션에 대한 자세한 내용은 IngressLink TLS – Secret, BIG-IP Reference와 F5 CIS Multi-Cluster 포스트를 참고하세요.

위와 같이 IngressLink를 배포할 경우, BIG-IP에 생성되는 Virtual Server에는 기본적으로 cookie Persistence Profile이 적용됩니다. 이 경우 동일 클라이언트의 요청이 항상 동일한 Pool Member로 전달되므로 트래픽 분산 동작을 확인하기 어려울 수 있습니다.

세션 지속성을 비활성화 하려면 아래와 같이 iRule에 persist none을 추가합니다. 기존에 IngressLink를 위해 구성한 Proxy_Protocol_iRule에 적용했습니다.

Proxy_Protocol_iRule
#### 신규 추가 구성 : iRule을 통해 세션 지속성 비활성화
when CLIENT_ACCEPTED {
persist none
}
#### 기존 구성
when SERVER_CONNECTED {
TCP::respond "PROXY TCP[IP::version] [IP::client_addr] [clientside {IP::local_addr}] [TCP::client_port] [clientside {TCP::local_port}]\r\n"
}

curl을 사용한 단순 반복 요청의 경우 매 요청마다 새로운 TCP 연결을 맺고 쿠키를 저장하지 않으므로 Cookie Persistence의 영향을 받지 않습니다. 브라우저나 쿠키를 유지하는 클라이언트로 테스트할 경우에는 위 iRule 수정을 적용하시기 바랍니다.

CIS Pod가 배포된 cluster1에 IngressLink를 배포합니다. CIS에 의해 정상적으로 BIG-IP에 반영되면, IngressLink에 정의했던 IP가 표시됩니다.

$ oc apply -f ingresslink.yaml
ingresslink.cis.f5.com/nginx-ingress created
------
$ oc get ingresslinks.cis.f5.com -n nginx-ingress
NAME IPAMVSADDRESS AGE
nginx-ingress 192.168.40.229 107s

BIG-IP의 GUI를 확인하면 생성된 Virtual Server와 Pool을 확인할 수 있습니다.

IngressLink 멀티 클러스터 VirtualServer
IngressLink 멀티 클러스터 Pool

반복 curl 요청을 전송하고, 응답을 통해 기본 분산 비율을 확인합니다.
정확한 비율로 분산 되진 않지만, 약 50:50의 비율로 분산 됩니다.

$ for i in {1..100}; do curl -s https://tls-ocp-apps.devopssong.site; done | sort | uniq -c
44 [Cluster A - BLUE] response from app v1.
56 [Cluster B - GREEN] response from app v1.
------
$ for i in {1..100}; do curl -s https://tls-ocp-apps.devopssong.site; done | sort | uniq -c
54 [Cluster A - BLUE] response from app v1.
46 [Cluster B - GREEN] response from app v1.

4-2. 가중치 변경을 통한 멀티 클러스터 Blue-Green 트래픽 전환

기존에 구성한 IngressLink 리소스의 multiClusterServices 구성에 weight를 통해 가중치를 설정하고, 가중치를 변경하며 클러스터간의 트래픽이 Blue-Green으로 전환되는 것을 확인하겠습니다.

기존 IngressLink의 yaml 구성에서 cluster1-1에 100의 weight을 설정하고, cluster1-2에 0의 weight을 설정하여 재배포 합니다.
CIS가 BIG-IP에 변경된 설정을 반영하기까지 약간의 시간이 소요됩니다.

ingresslink.yaml
...
multiClusterServices:
- clusterName: cluster1-1
namespace: nginx-ingress
service: npic-nginx-ingress-controller
weight: 100 # 새로 추가된 클러스터 별 가중치
- clusterName: cluster1-2
namespace: nginx-ingress
service: npic-nginx-ingress-controller
weight: 0
...
$ oc apply -f ingresslink.yaml
ingresslink.cis.f5.com/nginx-ingress configured

반복 curl 요청을 전송하고, 응답을 통해 분산 비율을 확인합니다.
구성한 가중치 옵션으로 인해 모든 트래픽은 cluster1의 NGINX Plus Ingress Controller로 전달됩니다.

$ for i in {1..100}; do curl -s https://tls-ocp-apps.devopssong.site; done | sort | uniq -c
100 [Cluster A - BLUE] response from app v1.

cluster1-1에 90의 weight을 설정하고, cluster1-2에 10의 weight을 설정하여 재배포 합니다.

ingresslink.yaml
...
multiClusterServices:
- clusterName: cluster1-1
namespace: nginx-ingress
service: npic-nginx-ingress-controller
weight: 90
- clusterName: cluster1-2
namespace: nginx-ingress
service: npic-nginx-ingress-controller
weight: 10
...

반복 요청 결과는 다음과 같습니다.

$ for i in {1..100}; do curl -s https://tls-ocp-apps.devopssong.site; done | sort | uniq -c
90 [Cluster A - BLUE] response from app v1.
10 [Cluster B - GREEN] response from app v1.

cluster1-1에 50의 weight을 설정하고, cluster1-2에 50의 weight을 설정하여 재배포 합니다.

ingresslink.yaml
...
multiClusterServices:
- clusterName: cluster1-1
namespace: nginx-ingress
service: npic-nginx-ingress-controller
weight: 50
- clusterName: cluster1-2
namespace: nginx-ingress
service: npic-nginx-ingress-controller
weight: 50
...

반복 요청 결과는 다음과 같습니다.

$ for i in {1..100}; do curl -s https://tls-ocp-apps.devopssong.site; done | sort | uniq -c
49 [Cluster A - BLUE] response from app v1.
51 [Cluster B - GREEN] response from app v1.

cluster1-1에 0의 weight을 설정하고, cluster1-2에 100의 weight을 설정하여 재배포 합니다.

ingresslink.yaml
...
multiClusterServices:
- clusterName: cluster1-1
namespace: nginx-ingress
service: npic-nginx-ingress-controller
weight: 0
- clusterName: cluster1-2
namespace: nginx-ingress
service: npic-nginx-ingress-controller
weight: 100
...

반복 요청 결과는 다음과 같습니다.

$ for i in {1..100}; do curl -s https://tls-ocp-apps.devopssong.site; done | sort | uniq -c
100 [Cluster B - GREEN] response from app v1.

IngressLink 리소스의 weight 값을 점진적으로 변경하여, 각 클러스터의 NGINX Plus Ingress Controller로 전달되는 트래픽의 비율을 조정하고, Blue-Green 방식으로 클러스터로 전달되는 트래픽이 전환되는 것을 확인할 수 있습니다.

5. 결론

이번 포스트에서는 BIG-IP CIS의 IngressLink를 멀티 클러스터 환경에서 구성하고, 가중치 조정을 통한 Blue-Green 트래픽 전환을 확인했습니다.

multiClusterServicesweight 값을 변경하여 IngressLink를 재배포하는 것만으로 클러스터 간 트래픽 비율을 유연하게 조정할 수 있으며, weight: 100 / weight: 0 구성으로 특정 클러스터로의 완전한 트래픽 전환도 가능합니다. 이를 통해 신규 버전 배포 시 트래픽을 점진적으로 전환할 수 있으며, 필요 시 특정 클러스터로 트래픽을 빠르게 집중시키는 방식으로도 활용할 수 있습니다.

BIG-IP CIS와 F5 NGINX Ingress Controller를 활용하여 Kubernetes/OpenShift 환경의 트래픽을 통합 관리하고 싶으시다면 NGINX STORE를 통해 문의하세요.

NGINX STORE를 통한 솔루션 도입 및 기술지원 무료 상담 신청

* indicates required