OpenShift v4.x – Self-hosted Load Balancer for OpenShift – Yongbok Blog
Yongbok Blog

OpenShift v4.x – Self-hosted Load Balancer for OpenShift

OpenShift 환경에서 Pod의 서비스 포트를 External IP로 서비스를 해야 할 경우나
on-premise 환경에서 물리 로드밸런서 장비가 없을 경우 Keepalived를 통해 사용이 가능하다.

본 문서에서는 OperatorHub에 대한 구성 방법에 대해서는 생략한다.

1. External CIDR 설정

 

External IP를 사용하기 위해서는 OpenShift 네트워크 설정에서 External IP 대역을 설정해야 한다.
RedHat CoreOS에서 Primary 네트워크 대역을 설정 하면 된다.

[root@bastion ~]# oc edit network cluster
spec:
....
......
  externalIP:
    autoAssignCIDRs:
    - 192.168.30.0/24
    policy:
      allowedCIDRs:
      - 192.168.30.0/24
.....
......

설정 후 API / Controller가 recreate 되므로 반드시 작업시에는 maintenance 시간에 작업이 되어야 한다.
시간이 꽤 걸리니 완료 될때까지 기다린다.

2. Keepalived Operator

 

OLM(Operator Lifecycle Manager)을 사용하여 Keepalived 설치와 구성을 진행한다.

2.1. Keepalived Operator 설치

 

Keepalived Operator의 subscriptions을 생성하여 설치를 진행 한다.

[root@bastion ~]# vi keepalived-operator-subscriptions.yaml
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
  labels:
    operators.coreos.com/keepalived-operator.keepalived-operator: ""
  name: keepalived-operator
  namespace: keepalived-operator
spec:
  channel: alpha
  installPlanApproval: Automatic
  name: keepalived-operator
  source: keepalived-operator-catalog
  sourceNamespace: openshift-marketplace
  startingCSV: keepalived-operator.v1.3.3
[root@bastion ~]# oc create -f keepalived-operator-subscriptions.yaml

2.2. Keepalived Operator 설치 확인

 

[root@bastion ~]# oc get csv -n keepalived-operator
NAME                         DISPLAY               VERSION   REPLACES   PHASE
keepalived-operator.v1.3.3   Keepalived Operator   1.3.3                Succeeded

2.3. Security Context Constraints 설정

 

Keepalived Pod가 HostNetwork를 사용하기 위해서는 Service Account에 privileged 권한이 있어야 한다.

[root@bastion ~]# oc adm policy add-scc-to-user privileged -z default -n keepalived-operator

2.4. Keepalived Group 생성

 

Keepalived를 사용할 Group Instance를 생성한다.
Group Instance는 keepalived를 사용할 목적에 따라서 구분하여 생성이 가능하다.

[root@bastion ~]# vi keepalived-group.yaml
apiVersion: redhatcop.redhat.io/v1alpha1
kind: KeepalivedGroup
metadata:
  # Keepalived Group의 이름을 정의 한다.
  name: nginx-keepalived-group
  namespace: keepalived-operator
spec:
  # VRRP의 인스턴스를 구분하기 위해 사용되며,
  # Keepalived Pod들간의 충돌 방지를 위한 ID를 설정한다.
  # (Keepalived에서는 virtual_router_id를 뜻한다.)
  # 이 값은 0~255 까지 가질수 있다.
  blacklistRouterIDs:
    - 1
    - 2
    - 3
    - 4
  # Keepalived를 사용할 이미지를 지정한다.
  image: 'registry.redhat.io/openshift4/ose-keepalived-ipfailover:v4.6'
  # CoreOS 상의 Primary NIC 이름을 지정한다.
  interface: enp1s0
  # Keepalived Pod가 구동될 노드를 지정한다.
  nodeSelector:
    node-role.kubernetes.io/worker: ''
[root@bastion ~]# oc create -f keepalived-group.yaml

2.5. Keepalived Pod 확인

 

Keepalived Group으로 지정한 이름으로 Pod가 구동 된다.

[root@bastion ~]# oc get pod -o wide -n keepalived-operator
NAME                                                      READY   STATUS    RESTARTS   AGE     IP              NODE                        NOMINATED NODE   READINESS GATES
nginx-keepalived-group-5ljww                              3/3     Running   0          2m   192.168.30.16   worker01.ocp4.ybkim.local   <none>           <none>
nginx-keepalived-group-bjjz2                              3/3     Running   0          2m   192.168.30.17   worker02.ocp4.ybkim.local   <none>           <none>
nginx-keepalived-group-ggwtl                              3/3     Running   0          2m   192.168.30.18   worker03.ocp4.ybkim.local   <none>           <none>
nginx-keepalived-group-wwr6s                              3/3     Running   0          2m   192.168.30.19   worker04.ocp4.ybkim.local   <none>           <none>

3. 서비스 Pod 생성

 

Keepalived를 테스트할 서비스 Pod를 생성한다.
생성은 namespace -> service -> deployment 순으로 진행 한다.

3.1. Project(namespace) 생성

 

[root@bastion ~]# vi nginx-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  annotations:
    openshift.io/description: "Nginx Test Project"
    openshift.io/display-name: "nginx"
  labels:
    kubernetes.io/metadata.name: nginx
  name: nginx
spec:
  finalizers:
  - kubernetes
[root@bastion ~]# oc create -f nginx-namespace.yaml

3.2. Nginx Service 생성

 

[root@bastion ~]# cat nginx-svc.yaml
kind: Service
apiVersion: v1
metadata:
  annotations:
    openshift.io/generated-by: ybkim
  name: nginx
  namespace: nginx
  labels:
    app: nginx
spec:
  ports:
    - name: web
      protocol: TCP
      port: 80
      targetPort: 80
  selector:
    app: nginx
    deploymentconfig: nginx
  type: ClusterIP
[root@bastion ~]# oc create -f nginx-svc.yaml

3.3 Nginx Deployment 생성

 

[root@bastion ~]# cat nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    openshift.io/generated-by: ybkim
  labels:
    app: nginx
  name: nginx
  namespace: nginx
spec:
  replicas: 4
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
        deploymentconfig: nginx
    spec:
      containers:
      - image: registry.ocp4.ybkim.local/library/nginx:stable
        imagePullPolicy: IfNotPresent
        name: nginx
        ports:
        - name: web
          containerPort: 80
          protocol: TCP
      nodeSelector:
        node-role.kubernetes.io/worker: ''
[root@bastion ~]# oc create -f nginx-deployment.yaml

4. Pod 외부 IP 노출

 

keepalived를 사용하여 Pod를 외부 IP로 노출하기 위해서는 Service annotation 부분에 해당 label이 지정 되어야 한다.
해당 Label은 Keepalived Operator에서 생성한 Group 이름을 Value 값으로 지정하면 된다.

4.1. Service 확인

 

[root@bastion ~]# oc get svc -n nginx
NAME    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
nginx   ClusterIP   172.30.237.69   <none>        80/TCP    30s

4.2. Annotate 설정

 

[root@bastion ~]# oc annotate svc nginx -n nginx \
keepalived-operator.redhat-cop.io/keepalivedgroup=keepalived-operator/nginx-keepalived-group

또는 Service를 직접 수정하여 아래와 같이 추가 해주면 된다.

[root@bastion ~]# nginx-svc.yaml
kind: Service
apiVersion: v1
metadata:
  annotations:
    openshift.io/generated-by: ybkim
    keepalived-operator.redhat-cop.io/keepalivedgroup: keepalived-operator/nginx-keepalived-group
  name: nginx
  namespace: nginx
  labels:
    app: nginx
spec:
  ports:
    - name: web
      protocol: TCP
      port: 80
      targetPort: 80
  selector:
    app: nginx
    deploymentconfig: nginx
  type: ClusterIP
[root@bastion ~]# oc replace -f nginx-svc.yaml

4.3. Service Type을 LoadBalancer로 설정

 

실제로 Pod를 External IP로 노출하기 위해서는 Service Type을 ClusterIP에서 LoadBalancer 로 지정해야,
External IP를 할당 요청 하면서 External IP CIDR에 정의된 범위의 IP가 자동 할당 된다.

[root@bastion ~]# oc patch svc nginx -n nginx --type=merge --patch '{"spec": {"type": "LoadBalancer"}}'

또는 Service를 직접 수정하여 아래와 같이 추가 해주면 된다.

[root@bastion ~]# nginx-svc.yaml
kind: Service
apiVersion: v1
metadata:
  annotations:
    openshift.io/generated-by: ybkim
    keepalived-operator.redhat-cop.io/keepalivedgroup: keepalived-operator/nginx-keepalived-group
  name: nginx
  namespace: nginx
  labels:
    app: nginx
spec:
  ports:
    - name: web
      protocol: TCP
      port: 80
      targetPort: 80
  selector:
    app: nginx
    deploymentconfig: nginx
  type: LoadBalancer
[root@bastion ~]# oc replace -f nginx-svc.yaml

4.4. External IP 확인

 

LoadBalancer로 변경 후 Service를 확인 해보면 External IP가 할당 되었다.

[root@bastion nginx]# oc get svc -n nginx
NAME    TYPE           CLUSTER-IP       EXTERNAL-IP                   PORT(S)        AGE
nginx   LoadBalancer   172.30.237.69   192.168.30.242,192.168.30.242   80:31742/TCP   2m45s

External IP로 192.168.30.242가 할당 되었고, 이 IP로 TCP/80, TCP/31742 두개의 포트로 접근이 가능할 수 있게 되었다. 

5. 장애 테스트

 

실제 External IP를 할당하는 방식은 Keepalived에서 VRRP(Virtual Router Redundancy Protocol)을 사용하여,
Primary NIC에 Secondary IP로 할당하는 방식이다.

keepalived pod가 구동되어 있는 모든 노드에 Secondary IP가 모두 할당 되는 방식이 아니라,
한 곳에만 할당 되었다가 failover시 다른 노드로 절체 되는 HA(High Availability) 방식이다.

5.1. 서비스 체크 스크립트 생성

 

External IP로 curl을 1/sec 마다 수행하여 failover.log 파일에 시간을 기록하여, failover의 시간을 확인 한다.

[root@bastion ~]# vi failover-test.sh
#!/bin/bash
DATE_NOW=$(date +%Y-%m-%d-%H:%M:%S)
sleep 1 | curl -sI http://$1 > /dev/null | echo "$DATE_NOW - 200 OK" >> failover.log
[root@bastion ~]# chmod a+x failover-test.sh

5.2. 서비스 체크 스크립트 실행

 

– External IP 확인

 

[root@bastion ~]# export EXTERNAL_IP="$(oc get svc nginx -o jsonpath='{.spec.externalIPs[*]}')"
[root@bastion ~]# for i in {1..300}; do bash failover-test.sh $EXTERNAL_IP; done

5.3. 노드 장애

 

의도적으로 노드를 장애 발생시켜서 VIP의 failover가 되는지, 시간은 얼마나 걸리는지 확인 해본다.

– VIP가 할당된 노드 확인

 

[root@bastion ~]# for node in {worker01,worker02,worker03,worker04}; do \
ssh core@$node "hostname -f && ip a s enp1s0 | grep inet | grep '\/32'"; done
worker01.ocp4.ybkim.local
worker02.ocp4.ybkim.local
worker03.ocp4.ybkim.local
worker04.ocp4.ybkim.local
    inet 192.168.30.242/32 scope global enp1s0

– worker04 노드 종료

 

[root@bastion ~]# ssh core@worker04 "sudo shutdown -h now"; done

5.4. 로그 확인

 

로그상에서는 2/sec의 순단 현상이 있는 것으로 확인이 되었다.
서비스 체크 스크립트를 만들때 sleep 1을 주었기 때문에 실제로는 1/sec 미만이라 볼 수 있다.

– 서비스 로그

 

[root@bastion ~]# cat failover.log
2021-08-02-20:12:36 - 200 OK
2021-08-02-20:12:38 - 200 OK

– Keepalived Pod 로그

 

VRRP를 통해 통신 상황을 감지하고 있다가 MASTER(Worker04) 노드와 통신이 되지 않아,
IP를 할당해도 되는지 확인하는 Gratuitous ARP 패킷을 통해 worker01 노드로 IP를 할당하고,
Priority 값을 100으로 설정하여 BACKUP(worker01) 노드가 MASTER로 승격 됐다.
(keepalived에서는 낮은 Priority 값이 MASTER가 된다.)

[root@bastion ~]# oc logs -n keepalived-operator -f nginx-keepalived-group-5ljww -c keepalived
Mon Aug  2 11:29:11 2021: (nginx/nginx) Receive advertisement timeout
Mon Aug  2 11:29:11 2021: (nginx/nginx) Entering MASTER STATE
Mon Aug  2 11:29:11 2021: (nginx/nginx) setting VIPs.
Mon Aug  2 11:29:11 2021: Sending gratuitous ARP on enp1s0 for 192.168.30.242
Mon Aug  2 11:29:11 2021: (nginx/nginx) Sending/queueing gratuitous ARPs on enp1s0 for 192.168.30.242
Mon Aug  2 11:29:11 2021: Sending gratuitous ARP on enp1s0 for 192.168.30.242
Mon Aug  2 11:29:11 2021: Sending gratuitous ARP on enp1s0 for 192.168.30.242
Mon Aug  2 11:29:11 2021: Sending gratuitous ARP on enp1s0 for 192.168.30.242
Mon Aug  2 11:29:11 2021: Sending gratuitous ARP on enp1s0 for 192.168.30.242
Mon Aug  2 11:29:11 2021: (nginx/nginx) Received advert from 192.168.30.17 with lower priority 100, ours 100, forcing new election

– 절체 시간

 

노드 장애 발생 후 VIP가 절체 되는 시간은 millisecond 단위로 절체 되며,
실제 서비스 순단은 상황에 따라서 오차범위는 있을수 있으며, 평균 1/sec 미만으로 보면 된다.

6. 그외 정보

 

6.1. External IP 할당 시간

 

Service Type을 LoadBalancer로 변경 후 External IP는 바로 할당되지만,
실제 노드단에 VIP가 할당 되는 시간은 최대 30/sec 뒤에 할당 될수도 있다.

6.2. External IP 삭제 시간

 

External IP를 재생성 했거나, Keepalived Group을 삭제 했을 경우,
환경에 따라 다소 상이하나, 대략 15~20/sec

7. RefURL

 

[1]: GitHub – Keepalived Operator
[2]: OpenShift Docs – Self-hosted Load Balancer for OpenShift: an Operator Based Approach

Exit mobile version