Split Brain 은 Clustering 또는 다중 노드/스토리지 구성의 각종 솔루션들(주로 고가용성과 부하분산, 즉 HA 용도), 예를 들어 Redis-Sentinel, MariaDB Galera Cluster, GlusterFS 파일 분산복제 설정, Oracle RAC, vSphere HA 구성 등에서 중요한 장애 유발 요인이며, Production 환경에서 점검해야 할 중요한 아키텍처적 회피 대상 항목에 해당한다.




1. Split Brain 이 도대체 무엇일까?


문자 그대로 "뇌가 양쪽으로 분단 된" 모양 또는 상황 그자체를 표현한다. IT와 무관한 비유를 해 보자면, 머리 둘 달린 용이 왼쪽으로 갈지 오른쪽으로 갈지 몰라서 갈팡질팡하는 형국이라고 할 수도 있겠다. 실제로 인간의 뇌는 좌뇌와 우뇌로 구성되는데, 양쪽 뇌를 연결해주는 '뇌량'을 절단해 버리면 왼쪽 뇌와 오른쪽 뇌의 인지 부조화와 같은 심각한 부작용이 나타난다고 알려져 있다.


이번 글에서는 MySQL 호환 데이터베이스 클러스터링 솔루션인 MariaDB Galera Cluster 의 기본 3대 구성에서 장애/점검 등을 이유로 1 대가 빠진 상태를 구체적인 예를 들어 설명해 보도록 한다. 3대 구성이 기본인 이유는, Leader 의 선출(투표에 의한)시 과반수 이상의 득표가 가능한 구조여야 하기 때문이다. 즉 3명(Quorom[각주:1]=3)이 투표에 참여하여 2 이상의 득표자가 Leader가 되는 경우를 생각해 보면 되겠다.   



위의 첫 번 째 그림은 전형적인 Split Brain 의 가능성이 내재된 상황을 표현하고 있다. 그렇다면 이 상황에서 100% Split Brain 이 발생한다고 단정할 수 있는가 하면 그건 아니다. 즉 물리적인 연결 구성과 함께 Network Topology를 보고 판단해야 하기 때문이다. 다음 그림을 보자.



만약 처음에 보았던 논리적 Diagram의 실제 네트워크 구성이 이 그림과 같이 단일 네트워크로 이루어져 있고 DB Client(또는 WAS)에서 Load Balancer를 거쳐서 Galera-1, Galera-2 로 접속이 되는 방식이라면, Split Brain은 발생하지 않는다. 따라서 Galera-1 서버가 다운되거나 네트워크 접속이 끊기면, DB Client는 정상적으로 Galera-2 로 접속되고 Galera-2는 자신을 DB Master로 인식하여 정상 작동을 하게 된다. 위의 구성도를 Topology로 표현하면 아래의 그림과 유사한 모양이 된다.




그렇다면 도대체 어떤 상황에서 Split Brain 이 발생할 수 있다는 말일까? 다음의 2개의 그림이 바로 그에 대한 답이 된다.



위의 네트워크 구성도에서, Galera-1과 Galera-2 사이의 데이터 복제 및 Alive-check는 Switch-2의 DB망을 통해서 이루어 지며, 실제 Production 환경에서의 전형적 네트워크 구성도의 예시를 들어 보자면 개략적으로 다음 그림과 같은 모습이 될 것이다.




위의 2가지 네트워크 구성도의 Topology를 그려 보면, 공통적으로 아래의 그림과 같이 나타난다. 이 상황에서 Galera-1, Galera-2 사이의 경로가 끊어지면, 바로 Split Brain 상황이 발생하게 되는데, DB Client 입장에서는 2개 DB 모두로의 접속 자체는 가능하지만, 데이터베이스 관련 각종 쿼리(use database, select, insert 문 등)가 실패 되는 현상이 발생하게 된다. 다시 전문용어를 써서 상세히 표현하자면, "네트워크의 부분적인 장애로 DB접속자(Client, WAS)에게는 2개의 DB 서버 모두 정상적으로 보이지만, 각 DB 서버는 상대방이 비정상이라고 판단하여 데이터 동기화 및 읽기 쓰기가 비정상 상태에 빠지는 것" 이라고 할 수 있으며, 다른 말로 Network Partition 또는 Cluster Partition 이라고도 한다.


위의 구성도를 Topology로 표현하면 아래의 그림과 유사한 모양이 된다.




2. 정상적 Cluster 구성상태의 점검/확인 방법


* MariaDB Galera Cluster 의 설치 과정은 다음 링크를 참고한다

☞ http://bryan.wiki/246


위의 섹션 1에서 Split-Brain 상황이 발생한 구조와 같이 2대의 MariaDB를 사용하는 경우(3대 중 1대의 동작이 중지되어 2대가 남은 상태)와 동일한 구성을 갖추기 위해 다음의 2개 VM을 준비하고 /etc/my.cnf.d/server.cnf 내용을 각각 설정하여 정상적인 2대 구성의 Cluster 환경을 갖춘다. 



[maria1]

  • CentOS 7.3, MariaDB-server
  • NIC1(ens160): 192.168.30.105 - DB Client와 DB간 연결 네트워크
  • NIC2(ens192): 10.199.30.105 - DB간 연결 네트워크

#

# * Galera-related settings

#

[galera]

# Mandatory settings

wsrep_on=ON

wsrep_provider=/usr/lib64/galera/libgalera_smm.so

wsrep_cluster_address='gcomm://'

#wsrep_cluster_address='gcomm://10.199.30.105,10.199.30.106'

wsrep_cluster_name='galera'

wsrep_node_address='10.199.30.105'

wsrep_node_name='maria1'

wsrep_sst_method=rsync


binlog_format=row

default_storage_engine=InnoDB

innodb_autoinc_lock_mode=2

#

# Allow server to accept connections on all interfaces.

#

bind-address=0.0.0.0

#

# Optional setting

#wsrep_slave_threads=1

innodb_flush_log_at_trx_commit=2



[maria2]

  • CentOS 7.3, MariaDB-server
  • NIC1(ens160): 192.168.30.106 - DB Client와 DB간 연결 네트워크
  • NIC2(ens192): 10.199.30.106 - DB간 연결 네트워크

#

# * Galera-related settings

#

[galera]

# Mandatory settings

wsrep_on=ON

wsrep_provider=/usr/lib64/galera/libgalera_smm.so

wsrep_cluster_address='gcomm://10.199.30.105,10.199.30.106'

wsrep_cluster_name='galera'

wsrep_node_address='10.199.30.106'

wsrep_node_name='maria2'

wsrep_sst_method=rsync


binlog_format=row

default_storage_engine=InnoDB

innodb_autoinc_lock_mode=2

#

# Allow server to accept connections on all interfaces.

#

bind-address=0.0.0.0

#

# Optional setting

#wsrep_slave_threads=1

innodb_flush_log_at_trx_commit=2



Cluster의 정상적인 동작상황에서는 다음의 4가지 점검 사항을 확인해 보고, 필요 시 튜닝 또는 설정값 조정을 진행해야 한다.


1. Cluster Integrity Check

 Maria1

Maria2 

select * from information_schema.GLOBAL_STATUS where VARIABLE_NAME like 'wsrep_cluster_state_uuid' or VARIABLE_NAME like 'wsrep_cluster_conf_id' or VARIABLE_NAME like 'wsrep_cluster_size' or VARIABLE_NAME like 'wsrep_cluster_status';

+--------------------------+--------------------------------------+

| VARIABLE_NAME | VARIABLE_VALUE |

+--------------------------+--------------------------------------+

| WSREP_CLUSTER_CONF_ID | 2 |

| WSREP_CLUSTER_SIZE | 2 |

| WSREP_CLUSTER_STATE_UUID | b04a71b5-161b-11e7-b1e1-bbd969deabbb |

| WSREP_CLUSTER_STATUS | Primary |

+--------------------------+--------------------------------------+

select * from information_schema.GLOBAL_STATUS where VARIABLE_NAME like 'wsrep_cluster_state_uuid' or VARIABLE_NAME like 'wsrep_cluster_conf_id' or VARIABLE_NAME like 'wsrep_cluster_size' or VARIABLE_NAME like 'wsrep_cluster_status';

+--------------------------+--------------------------------------+

| VARIABLE_NAME | VARIABLE_VALUE |

+--------------------------+--------------------------------------+

| WSREP_CLUSTER_CONF_ID | 2 |

| WSREP_CLUSTER_SIZE | 2 |

| WSREP_CLUSTER_STATE_UUID | b04a71b5-161b-11e7-b1e1-bbd969deabbb |

| WSREP_CLUSTER_STATUS | Primary |

+--------------------------+--------------------------------------+

 수행결과 캡처

 


 항목별 점검/확인 포인트

  • WSREP_CLUSTER_STATE_UUID : 모든 노드에서 동일해야 함. 값이 다른 노드는 Cluster 에서 제외되었음을 의미
  • WSREP_CLUSTER_CONF_ID : 위와 같음. 단 Cluster가 단절되었다가 복구될 때마다 +1 씩 값이 증가함
  • WSREP_CLUSTER_SIZE : Cluster 내의 노드 갯수
  • WSREP_CLUSTER_STATUS : 모든 쓰기 가능한 노드는 Primary 이어야 함



2. Node Status Check

 Maria1

Maria2 

select * from information_schema.GLOBAL_STATUS where VARIABLE_NAME like 'wsrep_ready' or VARIABLE_NAME like 'wsrep_connected' or VARIABLE_NAME like 'wsrep_local_state_comment';

+---------------------------+----------------+

| VARIABLE_NAME | VARIABLE_VALUE |

+---------------------------+----------------+

| WSREP_CONNECTED | ON |

| WSREP_LOCAL_STATE_COMMENT | Synced |

| WSREP_READY | ON |

+---------------------------+----------------+

select * from information_schema.GLOBAL_STATUS where VARIABLE_NAME like 'wsrep_ready' or VARIABLE_NAME like 'wsrep_connected' or VARIABLE_NAME like 'wsrep_local_state_comment';

+---------------------------+----------------+

| VARIABLE_NAME | VARIABLE_VALUE |

+---------------------------+----------------+

| WSREP_CONNECTED | ON |

| WSREP_LOCAL_STATE_COMMENT | Synced |

| WSREP_READY | ON |

+---------------------------+----------------+

 수행결과 캡처

 


 항목별 점검/확인 포인트

  • WSREP_READY : true(ON) 이면 해당 노드는 SQL 처리가 가능한 상태
  • WSREP_CONNECTED : true(ON) 이면 정상, WSREP_LOCAL_STATE_COMMENT 값이 'Synced'.
  • false(OFF) 이면 해당 노드가 정상적으로 Cluster 에 참여하지 못한 상태로 WSREP_LOCAL_STATE_COMMENT 값을 참조해야 함
  • WSREP_LOCAL_STATE_COMMENT : 동기화 진행중이면 Joining/Waiting for SST/Joined 중 하나의 값




3. Replication Status  Check

 Maria1

Maria2 

select * from information_schema.GLOBAL_STATUS where VARIABLE_NAME like 'wsrep_flow_control_paused' or VARIABLE_NAME like 'wsrep_cert_deps_distance';

+---------------------------+----------------+

| VARIABLE_NAME | VARIABLE_VALUE |

+---------------------------+----------------+

| WSREP_CERT_DEPS_DISTANCE | 0.000000 |

| WSREP_FLOW_CONTROL_PAUSED | 0.000000 |

+---------------------------+----------------+

select * from information_schema.GLOBAL_STATUS where VARIABLE_NAME like 'wsrep_flow_control_paused' or VARIABLE_NAME like 'wsrep_cert_deps_distance';

+---------------------------+----------------+

| VARIABLE_NAME | VARIABLE_VALUE |

+---------------------------+----------------+

| WSREP_CERT_DEPS_DISTANCE | 0.000000 |

| WSREP_FLOW_CONTROL_PAUSED | 0.000000 |

+---------------------------+----------------+

 수행결과 캡처

 


 항목별 점검/확인 포인트

  • WSREP_FLOW_CONTROL_PAUSED : 마지막 상태점검 시간부터 현재까지의 전체 시간 중 Replication이 중지된 시간의 비율. 0에 가까울수록 Lag이 적음을 의미하고 1이면 완전 중지. DB의 전체적 성능이 저하되었다고 판단될 경우 Cluster 전체 노드 중, 이 값이 가장 큰 노드(Slave Lag이 커서 전체 성능에 영향을 줌)을 제거해야 함
  • WSREP_CERT_DEPS_DISTANCE : 병렬처리 가능한 트랜잭션 수. 튜닝시는 이 값보다 적당히 크게 wsrep_slave_threads 정수값을 설정할 수 있음




4. Slow Cluster  Check

 Maria1

Maria2 

select * from information_schema.GLOBAL_STATUS where VARIABLE_NAME like 'wsrep_flow_control_sent' or VARIABLE_NAME like 'wsrep_local_recv_queue_avg' or VARIABLE_NAME like 'wsrep_local_send_queue_avg';

+----------------------------+----------------+

| VARIABLE_NAME | VARIABLE_VALUE |

+----------------------------+----------------+

| WSREP_FLOW_CONTROL_SENT | 0 |

| WSREP_LOCAL_RECV_QUEUE_AVG | 0.000000 |

| WSREP_LOCAL_SEND_QUEUE_AVG | 0.000000 |

+----------------------------+----------------+

select * from information_schema.GLOBAL_STATUS where VARIABLE_NAME like 'wsrep_flow_control_sent' or VARIABLE_NAME like 'wsrep_local_recv_queue_avg' or VARIABLE_NAME like 'wsrep_local_send_queue_avg';

+----------------------------+----------------+

| VARIABLE_NAME | VARIABLE_VALUE |

+----------------------------+----------------+

| WSREP_FLOW_CONTROL_SENT | 0 |

| WSREP_LOCAL_RECV_QUEUE_AVG | 0.000000 |

| WSREP_LOCAL_SEND_QUEUE_AVG | 0.000000 |

+----------------------------+----------------+

 수행결과 캡처



 항목별 점검/확인 포인트

  • WSREP_FLOW_CONTROL_SENT : 이 값이 클수록 처리 성능이 낮음
  • WSREP_LOCAL_RECV_QUEUE_AVG : 이 값이 클수록 처리 성능이 낮음
  • WSREP_LOCAL_SEND_QUEUE_AVG : 이 값이 클수록 네트워크 링크의 속도가 낮음(OS, 네트워크의 물리 구성 등 여러가지 영향 분석 필요)


위의 1, 2번 항목 점검에 의해 maria1, maria2 두개의 노드는 정상적 Cluster 상태를 유지하고 있음을 확인할 수 있다.



3. Split Brain 상태의 유발, 복구 및 쿼리 실행 결과 확인


maria1과 maria2 사이의 DB간 네트워크를 단절시키는 장애 상황을 시뮬레이션하기 위해 2대 중 1대에서 ifdown ens192(또는 iptables -A INPUT -d 10.199.30.106 -s 10.199.30.105 -j REJECT)를 실행하고, 양 DB서버간 복제/동기화를 위한 접속이 불가능함을 확인한다. 


아래 화면캡처 중간 부분의 DB client 측 터미널 화면에서 maria1, maria2 양쪽 DB 서버 모두에게 DB 접속은 가능하지만 실제 DB query는 실패 됨을 확인할 수 있다.




장애 요인(DB간 네트워크 단절)을 제거하여(원상 복구된 상황),  각 DB 서버의 상태를 점검, 데이터 복제 여부를 확인한 결과는 다음과 같이 나타나게 된다.





- Barracuda -


  1. 쿼럼; 정족수, 즉 투표=vote 에 의한 의사결정이 이루어 지기 위한 최소한의 구성원 수. 클러스터링 개념에서는 과반수=majority 득표를 위한 노드 또는 참여자 수로, 일반적으로는 최소 3개가 되어야 한다. [본문으로]
저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


GitHub 에 새로운 repository를 생성할 때 어떻게 하시나요? 주로 github 접속/로그인-New Repository 클릭-이름입력-Create Repository 클릭-로컬 git 디렉토리 생성-git init-git add-commit-push 과정을 통해서 진행할텐데, 과정 자체가 어려운 건 아니지만 번거로운 점이 적지 않습니다. 이 과정을 간단한 스크립트를 만들어서 진행하면 어떨까요?





준비사항

  • GitHub Account: github.com 에 가입(It's free!)
  • Git client와 curl이 설치된 linux box



스크립트 작성 & 실행


여기서 개발자의 작업 PC는 CentOs/RedHat linux 박스로 하고, 우선 Github에 올릴 소스 작업 디렉토리가 필요하겠지요. 기존 작업 디렉토리가 아닌 완전히 새로운 것으로 해 볼텐데요, 이를 GitHub의 자신의 계정과 연결하는 식으로 진행할 겁니다. 응용하면, 기존 디렉토리도 당연히 비슷한 방식으로 연결 가능하겠지요.


현재 작업 PC의 계정이 git config 로 git 서버에 연결된 적이 없다는 것을 전제로 진행합니다.


스크립트를 만듭니다(아래 스크립트를 응용하면 GitHub Account와 Email, 프로젝트명을 실행인자로 전달해서 수행하는 것도 가능하겠네요. 필요하다면 ... ^^). 스크립트를 수행하면 현재 디렉토리 아래에 해당 작업 디렉토리가 생성되고 이것이 바로 GitHub repository와 연결 됩니다. 


# vi github-repo-set.sh

#!/bin/bash
# Set your GitHub username and email
export GITHUB_USERNAME="YourGithubAccount"
export GITHUB_EMAIL="youraccount@domain.com"

git config --global user.name "${GITHUB_USERNAME}"
git config --global user.email "${GITHUB_EMAIL}"
git config --global credential.helper cache
git config --global credential.helper 'cache --timeout=3600'

curl -u "${GITHUB_USERNAME}" https://api.github.com/user/repos -d '{"name":"my-test-project-001"}'

mkdir my-test-project-001
echo "# my-test-project-001" > my-test-project-001/README.md
cd my-test-project-001
git init
git add .
git commit -a -m "Create my-test-project-001 repository"
git remote add origin https://github.com/${GITHUB_USERNAME}/my-test-project-001.git
git push -u origin master

# chmod a+x github-repo-set.sh

# ./github-repo-set.sh

Enter host password for user 'YourGithubAccount': - 로그인 암호 입력 -

{

  "id": 123456789,

  "name": "my-test-project-001",

  "full_name": "YourGithubAccount/my-test-project-001",

  "owner": {

    "login": "YourGithubAccount",

    "id": 123456789,

    "url": "https://api.github.com/users/YourGithubAccount",

    "html_url": "https://github.com/YourGithubAccount",

...

  "git_url": "git://github.com/YourGithubAccount/my-test-project-001.git",

  "ssh_url": "git@github.com:YourGithubAccount/my-test-project-001.git",

  "clone_url": "https://github.com/YourGithubAccount/my-test-project-001.git",

...

}

Initialized empty Git repository in /yourdirectory/my-test-project-001/.git/

[master (root-commit) d5f9e80] Create my-test-project-001 repository

 1 file changed, 1 insertion(+)

 create mode 100644 README.md

Username for 'https://github.com': YourGithubAccount 입력

Password for 'https://YourGithubAccount@github.com': - 로그인 암호 입력 -

Counting objects: 3, done.

Writing objects: 100% (3/3), 253 bytes | 0 bytes/s, done.

Total 3 (delta 0), reused 0 (delta 0)

To https://github.com/YourGithubAccount/my-test-project-001.git

 * [new branch]      master -> master

Branch master set up to track remote branch master from origin.

* cache --timeout=3600 부분은, Git 서버에 로그인한 세션이 1시간 동안 유지되는 것으로 필요에 따라 줄이거나 늘이면 되겠습니다



이제 GitHub 에 접속, 로그인해서 만들어진 프로젝트 repo를 확인해 봅니다. 아래 이미지는 저의 GitHub 계정인 DragOnMe 에 생성된 프로젝트의 캡처 화면입니다. 참 쉽죠잉~ ^^




- Barracuda -




저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들

Tag Github


K8s(쿠버네티스) 클러스터 내에 구현된 Application을 외부로 노출하여 서비스할 경우에 주로 Nginx의 LB 기능을 이용한 Ingress Load Balancer를 사용하게 된다. 기본적으로 제공되는 이러한 Nginx 컨테이너 방식 외에도 Haproxy, Traefik Ingress등의 3rd party 솔루션들이 존재하는데, 다음 part-2 에서는 최근 hot하게 뜨고 있는 Traefik Ingress를 사용한 구현을 다룰 예정이다.



필요 요소와 서비스 구성


  • Kubernetes Cluster(Local cluster, minikube or dind-cluster not in the cloud like AWS or GCP)
  • Backend Service(like web or specific app server)
  • 테스트 수행용 컨테이너(curl로 클러스터 내부 접속 테스트)
  • Local DNS(Cluster 외부, named 서버)


예제로 사용하는 백엔드 서비스는 단순한 web server로, 앞선 포스팅(http://bryan.wiki/287) 에서 만들어 본 웹서버 컨테이너 이미지(drlee001/kubeweb)을 사용한다. 





위 그림은 본 구현에서 적용된 전체 요소들의 논리적인 구성과 관계를 나타낸 것이다. 텍스트가 작아서 잘 보이지 않을 경우, 이미지를 클릭하여 원본 크기로 자세히 들여다 보도록 하자.



테스트 수행용 컨테이너 기동


클러스터 내부의 각 서비스에 대한 접속 테스트를 curl 로 수행할 때 사용할 Pod로, 네트워크를 테스트하기 위해 많이 사용하는 drlee001/net-tester 컨테이너를 기동해 둔다.


<00-test-pod.yaml>

apiVersion: v1
kind: Pod
metadata:
  name: net-tester-for-ingress
spec:
  containers:
  - image: drlee001/net-tester
    imagePullPolicy: IfNotPresent
    name: net-tester-container
  restartPolicy: Never

[root@kubemaster nginx-ingress-sample]# kubectl create -f 00-test-pod.yaml

[root@kubemaster nginx-ingress-sample]# kubectl get pods | grep net-tester

net-tester-for-ingress                      1/1       Running   0          13h



Backend Service 기동


Load Balancing 의 대상이 되는 예제 서비스로, 이미 만들어진 drlee001/kubeweb 컨테이너를 deploy 하고 ClusterIP 로 service를 기동해 둔다. 여기서는 2개의 서로 다른 서비스에 대해 하나의 Ingress를 통해 Load balancing이 가능하도록 deploy-test-1(port 7777로 접속) 과 deploy-test-2(port 8888로 접속)으로 구분하여 구축해 두도록 한다.


<01-deploy-2backends.yaml>

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: deploy-test-1
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: web-front-end
        department: group1
    spec:
      containers:
      - name: tiny-webserver-1
        image: drlee001/kubeweb
        env:
        - name: PORT_ARGS
          value: "--port=7777"
        ports:
        - containerPort: 7777
          name: web-port
          protocol: TCP
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: deploy-test-2
spec:
  replicas: 2
  template:
    metadata:
      labels:
        app: web-front-end
        department: group2
    spec:
      containers:
      - name: tiny-webserver-2
        image: drlee001/kubeweb
        env:
        - name: PORT_ARGS
          value: "--port=8888"
        ports:
        - containerPort: 8888
          name: web-port
          protocol: TCP

[root@kubemaster nginx-ingress-sample]# kubectl create -f 01-deploy-2backends.yaml

deployment "deploy-test-1" created

deployment "deploy-test-2" created

[root@kubemaster nginx-ingress-sample]# kubectl get deployment | grep deploy-test

deploy-test-1              2         2         2            2           13h

deploy-test-2              2         2         2            2           13h



다음으로, 2개의 서로 다른 deployment 에 대응하는 Cluster 내부 접속 endpoint 를 제공할 서비스를 구축한다.


<02-svc-2backends.yaml>

apiVersion: v1
kind: Service
metadata:
  name: backend-svc-1
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: web-port
  selector:
    app: web-front-end
    department: group1
---
apiVersion: v1
kind: Service
metadata:
  name: backend-svc-2
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: web-port
  selector:
    app: web-front-end
    department: group2

[root@kubemaster nginx-ingress-sample]# kubectl create -f 02-svc-2backends.yaml

service "backend-svc-1" created

service "backend-svc-2" created

[root@kubemaster nginx-ingress-sample]# kubectl get svc | grep backend-svc

backend-svc-1                          ClusterIP   10.133.108.20    <none>        80/TCP                         13h

backend-svc-2                          ClusterIP   10.131.128.20    <none>        80/TCP                         13h



Default backend 서비스 기동


Ingress LB를 통해서 들어온 request가, 등록된 Ingress rule(나중에 정의해서 사용할)에 매칭되지 않을 경우의 적당한 response 를 위해서, 이러한 request 를 받아줄 별도의 내부 서비스가 필요하다. 이를 default backend(일종의 랜딩페이지) 라고 하는데, port 8080으로 들어온 http 접속에 대해 하위 path가 '/' 인 경우 404(not found)를, '/healthz' 인 경우 200(ok) 를 응답하는 간단한 web server 이다. 여기서는 구글이 제공하는 defaultbackend 컨테이너를 사용한다.


<03-default-backend-for-ingress.yaml>

apiVersion: v1
kind: Service
metadata:
  name: default-http-backend
spec:
  type: ClusterIP
  ports:
  - port: 80
    protocol: TCP
    targetPort: 8080
  # Matches Deployment
  selector:
    app: default-http-backend
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: default-http-backend
spec:
  replicas: 2
  template:
    metadata:
      # Matches Deployment
      labels:
        app: default-http-backend
    spec:
      terminationGracePeriodSeconds: 60
      containers:
      - name: default-http-backend
        # Any image is permissable as long as:
        # 1. It serves a 404 page at /
        # 2. It serves 200 on a /healthz endpoint
        image: gcr.io/google_containers/defaultbackend:1.0
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 30
          timeoutSeconds: 5
        ports:
        - containerPort: 8080
        resources:
          limits:
            cpu: 10m
            memory: 20Mi
          requests:
            cpu: 10m
            memory: 20Mi

[root@kubemaster nginx-ingress-sample]# kubectl create -f 03-default-backend-for-ingress.yaml

deployment "default-http-backend" created

service "default-http-backend" created

[root@kubemaster nginx-ingress-sample]# kubectl get svc | grep default-http

default-http-backend                   ClusterIP   10.142.34.97     <none>        80/TCP                         20h



테스트용 컨테이너로 서비스 정상 동작 확인


[root@kubemaster nginx-ingress-sample]# kubectl get svc

NAME                                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                        AGE

backend-svc-1                          ClusterIP   10.133.108.20    <none>        80/TCP                         20h

backend-svc-2                          ClusterIP   10.131.128.20    <none>        80/TCP                         20h

default-http-backend                   ClusterIP   10.142.34.97     <none>        80/TCP                         20h


[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl http://backend-svc-1

<b> Pod/Hostname: deploy-test-1-76754b9b75-9p2lx Port: 7777</b><br/><br/>


[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl http://backend-svc-2

<b> Pod/Hostname: deploy-test-2-6f57b84975-9jnf5 Port: 8888</b><br/><br/>


[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl http://default-http-backend

 default backend - 404



Nginx Ingress Controller 설정값 저장을 위한 Configmap 등록


Nginx는 웹서버 기능과 함께 로드밸런서 역할도 수행할 수 있게 만들어져 있다. 편리하게도 kubernetes 클러스터 내에 이 설정 값을 configmap 형태로 등록해 두고, 필요에 따라 설정값을 바꿔가면서 운영할 수 있는데, 진행 과정 상 Nginx Ingress Controller를 띄우기 전에 미리 등록해 두어야 한다. 


<04-configmap-nginx-ingress-controller.yaml>

apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-ingress-controller-conf
  labels:
    app: nginx-ingress-lb
    group: lb
data:
  # for VTS page of the Nginx load balancer
  enable-vts-status: 'true'
  enable-sticky-sessions: 'true'

[root@kubemaster nginx-ingress-sample]# kubectl create -f 04-configmap-nginx-ingress-controller.yaml

configmap "nginx-ingress-controller-conf" created



Nginx Ingress Controller 실행


<05-deploy-nginx-ingress-controller.yaml>

# Add: SA, ClusterRole, ClusterRoleBinding
# Name Space: default
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: ingress
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: system:ingress
rules:
- apiGroups:
  - ""
  resources: ["configmaps","secrets","endpoints","events","services"]
  verbs: ["list","watch","create","update","delete","get"]
- apiGroups:
  - ""
  - "extensions"
  resources: ["services","nodes","ingresses","pods","ingresses/status"]
  verbs: ["list","watch","create","update","delete","get"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: ingress
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: system:ingress
subjects:
  - kind: ServiceAccount
    name: ingress
    # Could be like 'kube-system' ...
    namespace: default
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx-ingress-controller
  # Could be like 'kube-system' ...
  namespace: default
spec:
  replicas: 2
  revisionHistoryLimit: 3
  template:
    metadata:
      labels:
        app: nginx-ingress-lb
    spec:
      serviceAccountName: ingress
      terminationGracePeriodSeconds: 60
      containers:
        - name: nginx-ingress-controller
          image: gcr.io/google_containers/nginx-ingress-controller:0.9.0-beta.2
          imagePullPolicy: IfNotPresent
          readinessProbe:
            httpGet:
              path: /healthz
              port: 18080
              scheme: HTTP
          livenessProbe:
            httpGet:
              path: /healthz
              port: 18080
              scheme: HTTP
            initialDelaySeconds: 10
            timeoutSeconds: 5
          args:
            - /nginx-ingress-controller
            - --default-backend-service=$(POD_NAMESPACE)/default-http-backend
            - --configmap=$(POD_NAMESPACE)/nginx-ingress-controller-conf
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          ports:
            - containerPort: 80
            - containerPort: 18080

[root@kubemaster nginx-ingress-sample]# kubectl create -f 05-deploy-nginx-ingress-controller.yaml

deployment "nginx-ingress-controller" created


[root@kubemaster nginx-ingress-sample]# kubectl get deployment nginx-ingress-controller

NAME                       DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE

nginx-ingress-controller   2         2         2            2           20h



여기까지 진행하였다면 다음과 같은 여러 개의 pod 가 실행되고 있음을 확인할 수 있다.


[root@kubemaster nginx-ingress-sample]# kubectl get pods -o wide

NAME                                        READY     STATUS    RESTARTS   AGE       IP              NODE

default-http-backend-7c7764588c-8w4qg       1/1       Running   0          20h       10.38.0.6       kubenode3

default-http-backend-7c7764588c-qtbfk       1/1       Running   0          20h       10.32.0.5       kubenode1

deploy-test-1-76754b9b75-9p2lx              1/1       Running   0          21h       10.40.0.5       kubenode2

deploy-test-1-76754b9b75-hwnwc              1/1       Running   0          21h       10.38.0.5       kubenode3

deploy-test-2-6f57b84975-9jnf5              1/1       Running   0          21h       10.40.0.6       kubenode2

deploy-test-2-6f57b84975-zhrs8              1/1       Running   0          21h       10.32.0.3       kubenode1

net-tester-for-ingress                      1/1       Running   0          21h       10.32.0.2       kubenode1

nginx-ingress-controller-6679d99f68-gzkhb   1/1       Running   0          20h       10.40.0.7       kubenode2

nginx-ingress-controller-6679d99f68-wnshp   1/1       Running   0          20h       10.32.0.6       kubenode1


이 중에서 실제로 로드밸런서 역할을 담당할 pod가 nginx-ingress-controller-*-* 같이 생긴 pod들이다(사실 하나만 띄워도 되지만, 고가용성-HA-를 위해서는 2개 이상을 사용). net-tester를 통해서 각각의 nginx-ingress-controller 에 직접 접속이 되는지 확인해 보자. 아직은 따로 Ingress Rule을 적용하지 않았기 때문에 '404' 응답이 나오면 정상적인 상태이다(실은, 이 '404' 응답은 그림에서 보이듯이 default-http-backend 에서 보내주는 것이다).


[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl http://10.40.0.7

default backend - 404

[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl http://10.32.0.6

default backend - 404 



Ingress Rule 디플로이


이제 모든 준비는 끝났고, Nginx Ingress Controller 가 로드밸런서 역할을 수행할 수 있도록 Ingress Rule(Policy, 또는 Ingress Object) 를 디플로이하고 서비스로 노출(Expose)시키는 단계와, curl을 통한 접속 테스트, DNS 등록을 통한 실제 접속을 해 보는 일만 남았다. 아래 yaml을 보면 총 3개의 도메인에 대한 rule이 존재하는데, Nginx 또는 Apache httpd 등으로 웹서버를 운영해 보았다면, 하나의 웹서버에서 서로 다른 도메인에 대해 각각 특정한 웹페이지를 연결해 주는  'VirtualHost' 의 동작 방식과 유사함을 보게 될 것이다.


<06-ingress-rule.yaml>

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx-ingress
  namespace: default
spec:
  rules:
  - host: kubeweb-7777.bryan.local
    http:
      paths:
      - backend:
          serviceName: backend-svc-1
          servicePort: 80
  - host: kubeweb-8888.bryan.local
    http:
      paths:
      - backend:
          serviceName: backend-svc-2
          servicePort: 80
  - host: kubeweb.bryan.local
    http:
      paths:
      - path: /svc1
        backend:
          serviceName: backend-svc-1
          servicePort: 80
      - path: /svc2
        backend:
          serviceName: backend-svc-2
          servicePort: 80
      - path: /nginx_status
        backend:
          serviceName: nginx-ingress-nodeport
          servicePort: 18080

[root@kubemaster nginx-ingress-sample]# kubectl create -f 06-ingress-rule.yaml

ingress "nginx-ingress" created

[root@kubemaster nginx-ingress-sample]# kubectl get ingress

NAME            HOSTS                                                                   ADDRESS   PORTS     AGE

nginx-ingress   kubeweb-7777.bryan.local,kubeweb-8888.bryan.local,kubeweb.bryan.local             80        20h


여기까지는 Kubernetes 클러스터 내부에서의 연결 통로만 설정이 완료된 상태다.


[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl -H Host:kubeweb-7777.bryan.local http://10.40.0.7

<b> Pod/Hostname: deploy-test-1-76754b9b75-hwnwc Port: 7777</b><br/><br/>

[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl -H Host:kubeweb-8888.bryan.local http://10.40.0.7

<b> Pod/Hostname: deploy-test-2-6f57b84975-zhrs8 Port: 8888</b><br/><br/>

* Host Header 를 조작하여 접속을 시도하는 세션에 도메인명을 담아서 접속하면 Ingress Rule 에 따라 2개의 서비스 각각에 분기 접속되는 것을 확인할 수 있다(위의 06-ingress-rule.yaml에서 정의된 첫 번 째, 두 번 째 rule에 매칭된다)


[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl -H Host:kubeweb.bryan.local http://10.40.0.7

default backend - 404 

[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl -H Host:kubeweb.bryan.local http://10.40.0.7/svc1

<b> Pod/Hostname: deploy-test-1-76754b9b75-9p2lx Port: 7777</b><br/><br/> 

[root@kubemaster nginx-ingress-sample]# kubectl exec -it net-tester-for-ingress -- curl -H Host:kubeweb.bryan.local http://10.40.0.7/svc2

<b> Pod/Hostname: deploy-test-2-6f57b84975-9jnf5 Port: 8888</b><br/><br/>

* 위와 비슷하지만 이번에는 kubeweb,bryan.local 도메인에 대한 URL path 별로 접속이 분기됨을 확인할 수 있다(06-ingress-rule.yaml에서 정의된 세 번 째 rule에 매칭된다)



Ingress 서비스 노출(Expose)


Kubernetes Cluster 의 외부에서 웹브라우저 등을 통해서 내부에 존재하는 backend-svc-1, backend-svc-2 에 접속하기 위해서는 NodePort 방식으로 접속하면 된다(AWS나 Google Cloud Engine과 같은 퍼블릭클라우드에 Kubernetes 가 설치된 경우에는 Loadbalancer 방식으로 접속하면 된다).


<07-svc-expose-by-nodeport.yaml>

apiVersion: v1
kind: Service
metadata:
  name: nginx-ingress-nodeport
spec:
  type: NodePort
  ports:
    - port: 80
      nodePort: 30100
      name: http
    - port: 18080
      nodePort: 30101
      name: http-mgmt
  selector:
    app: nginx-ingress-lb

[root@kubemaster nginx-ingress-sample]# kubectl create -f 07-svc-expose-by-nodeport.yaml

service "nginx-ingress-nodeport" created

[root@kubemaster nginx-ingress-sample]# kubectl get svc nginx-ingress-nodeport -o wide

NAME                     TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)                        AGE       SELECTOR

nginx-ingress-nodeport   NodePort   10.138.77.194   <none>        80:30100/TCP,18080:30101/TCP   21h       app=nginx-ingress-lb


이제 클러스터 외부에서 웹브라우저를 통해 NodePort 로 접속을 시도해 보아야 할텐데, 그 전에 한 가지 더 할 일이 있다. 클러스터 외부의 DNS의 존파일에 여기서 사용할 도메인의 A 레코드들을 등록해 주어야 한다. 다음과 같이 DNS 서버에 추가로 작업해 주고 'systemctl restart named' 로 named 서비스를 재시작한다.


[root@bryan-dns ~]# vi /var/named/bryan.local.zone 

...

; For testing nginx-ingress

kubeweb-7777 IN A 10.255.10.171

IN A 10.255.10.172

IN A 10.255.10.173


kubeweb-8888 IN A 10.255.10.171

IN A 10.255.10.172

IN A 10.255.10.173


kubeweb IN A 10.255.10.171

IN A 10.255.10.172

IN A 10.255.10.173



웹브라우저로 Ingress 접속 확인








다음 캡처 이미지는 ConfigMap 에서 enable-vts-status 로 사용하도록 하고 ingress-rule.yaml 에서 연결되도록 설정하고 NodePort로 expose시킨 Ingress Status 모니터링 화면이다.



* Reference: dasblinkenlichten.com 포스팅 내용과 아이디어를 다수 참조 하였음


* 다음 포스팅인 Part-2 에서는 Traefik Ingress를 활용한 Ingress 로드밸런서를 구현해 보고, Nginx 방식에 비해 어떤 점이 다르고 또 장점은 무엇인지 생각하는 시간을 가져보기로 한다. 구현과 테스트 방식은 이번 Part-1 과 유사하며, 양 쪽에 놓고 비교해 보는 자료로 활용되기를 기대한다.



- Barracuda -




저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들