차이
문서의 선택한 두 판 사이의 차이를 보여줍니다.
양쪽 이전 판 이전 판 다음 판 | 이전 판 | ||
octavia_lb_구현_및_분석 [2024/10/10 04:50] – koov | octavia_lb_구현_및_분석 [2024/10/10 06:29] (현재) – koov | ||
---|---|---|---|
줄 907: | 줄 907: | ||
==== AmphoraePostNetworkPlug ==== | ==== AmphoraePostNetworkPlug ==== | ||
- | '' | + | '' |
+ | {{: | ||
+ | Member를 추가한 후 Amphora의 네트워크 상태를 다시 확인합니다 | ||
+ | <WRAP prewrap> | ||
+ | <code bash> | ||
+ | root@amphora-cd444019-ce8f-4f89-be6b-0edf76f41b77: | ||
+ | eth1 Link encap: | ||
+ | inet addr: | ||
+ | inet6 addr: fe80:: | ||
+ | UP BROADCAST RUNNING MULTICAST | ||
+ | RX packets: | ||
+ | TX packets: | ||
+ | collisions: | ||
+ | RX bytes: | ||
+ | eth1: | ||
+ | inet addr: | ||
+ | UP BROADCAST RUNNING MULTICAST | ||
+ | eth2 Link encap: | ||
+ | inet addr: | ||
+ | inet6 addr: fe80:: | ||
+ | UP BROADCAST RUNNING MULTICAST | ||
+ | RX packets:8 errors:2 dropped:0 overruns:0 frame:2 | ||
+ | TX packets:8 errors:0 dropped:0 overruns:0 carrier:0 | ||
+ | collisions: | ||
+ | RX bytes:2156 (2.1 KB) TX bytes:808 (808.0 B) | ||
+ | </ | ||
+ | </ | ||
+ | 구성 파일은 다음과 같습니다 | ||
+ | <WRAP prewrap> | ||
+ | <code bash> | ||
+ | # Generated by Octavia agent | ||
+ | auto eth2 | ||
+ | iface eth2 inet static | ||
+ | address 192.168.1.3 | ||
+ | broadcast 192.168.1.255 | ||
+ | netmask 255.255.255.0 | ||
+ | mtu 1450 | ||
+ | post-up / | ||
+ | post-down / | ||
+ | </ | ||
+ | </ | ||
+ | ==== ListenersUpdate ==== | ||
+ | 최종적으로 haproxy 구성 변경은 '' | ||
+ | <WRAP prewrap> | ||
+ | <code vim> | ||
+ | # Configuration for loadbalancer 01197be7-98d5-440d-a846-cd70f52dc503 | ||
+ | global | ||
+ | daemon | ||
+ | user nobody | ||
+ | log /dev/log local0 | ||
+ | log /dev/log local1 notice | ||
+ | stats socket / | ||
+ | maxconn 1000000 | ||
+ | defaults | ||
+ | log global | ||
+ | retries 3 | ||
+ | option redispatch | ||
+ | peers 1385d3c4615e4a92aea1c4fa51a75557_peers | ||
+ | peer l_Ustq0qE-h-_Q1dlXLXBAiWR8U 172.16.1.7: | ||
+ | peer O08zAgUhIv9TEXhyYZf2iHdxOkA 172.16.1.3: | ||
+ | |||
+ | frontend 1385d3c4-615e-4a92-aea1-c4fa51a75557 | ||
+ | option httplog | ||
+ | maxconn 1000000 | ||
+ | bind 172.16.1.10: | ||
+ | mode http | ||
+ | default_backend 8196f752-a367-4fb4-9194-37c7eab95714 | ||
+ | timeout client 50000 | ||
+ | |||
+ | backend 8196f752-a367-4fb4-9194-37c7eab95714 | ||
+ | mode http | ||
+ | balance roundrobin | ||
+ | fullconn 1000000 | ||
+ | option allbackups | ||
+ | timeout connect 5000 | ||
+ | timeout server 50000 | ||
+ | server b6e464fd-dd1e-4775-90f2-4231444a0bbe 192.168.1.14: | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | 실제로, member를 추가하는 것은 '' | ||
+ | |||
+ | ===== L7policy, L7rule 및 Health Monitor의 생성 프로세스 분석 ===== | ||
+ | L7policy 객체의 의미는 전송 동작 유형(예: pool로 전송, URL로 전송 또는 전송 거부)을 설명하는 데 사용되며, | ||
+ | {{: | ||
+ | |||
+ | L7Rule 객체의 의미는 데이터 전송의 매칭 필드를 나타내며, | ||
+ | {{: | ||
+ | |||
+ | Health Monitor 객체는 Pool 내 Member의 상태를 모니터링하는 데 사용되며, | ||
+ | {{: | ||
+ | |||
+ | 왜 이 세 가지(L7policy, | ||
+ | |||
+ | ==== 예시 1. 기본 풀로 전송 ==== | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code bash> | ||
+ | $ openstack loadbalancer healthmonitor create --name healthmonitor1 --type PING --delay 5 --timeout 10 --max-retries 3 8196f752-a367-4fb4-9194-37c7eab95714 | ||
+ | |||
+ | $ openstack loadbalancer l7policy create --name l7p1 --action REDIRECT_TO_POOL --redirect-pool 8196f752-a367-4fb4-9194-37c7eab95714 1385d3c4-615e-4a92-aea1-c4fa51a75557 | ||
+ | |||
+ | $ openstack loadbalancer l7rule create --type HOST_NAME --compare-type STARTS_WITH --value " | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | '' | ||
+ | <WRAP prewrap> | ||
+ | <code vim> | ||
+ | # Configuration for loadbalancer 01197be7-98d5-440d-a846-cd70f52dc503 | ||
+ | global | ||
+ | daemon | ||
+ | user nobody | ||
+ | log /dev/log local0 | ||
+ | log /dev/log local1 notice | ||
+ | stats socket / | ||
+ | maxconn 1000000 | ||
+ | external-check | ||
+ | |||
+ | defaults | ||
+ | log global | ||
+ | retries 3 | ||
+ | option redispatch | ||
+ | |||
+ | peers 1385d3c4615e4a92aea1c4fa51a75557_peers | ||
+ | peer l_Ustq0qE-h-_Q1dlXLXBAiWR8U 172.16.1.7: | ||
+ | peer O08zAgUhIv9TEXhyYZf2iHdxOkA 172.16.1.3: | ||
+ | |||
+ | |||
+ | frontend 1385d3c4-615e-4a92-aea1-c4fa51a75557 | ||
+ | option httplog | ||
+ | maxconn 1000000 | ||
+ | # frontend http:// | ||
+ | bind 172.16.1.10: | ||
+ | mode http | ||
+ | # ACL 전송규칙 | ||
+ | acl 8d9b8b1e-83d7-44ca-a5b4-0103d5f90cb9 req.hdr(host) -i -m beg server | ||
+ | # if ACL 8d9b8b1e-83d7-44ca-a5b4-0103d5f90cb9 충족되면 backend 8196f752-a367-4fb4-9194-37c7eab95714 | ||
+ | use_backend 8196f752-a367-4fb4-9194-37c7eab95714 if 8d9b8b1e-83d7-44ca-a5b4-0103d5f90cb9 | ||
+ | # 어떤 ACL 규칙도 일치하지 않으면 backend 8196f752-a367-4fb4-9194-37c7eab95714 로 전송 | ||
+ | default_backend 8196f752-a367-4fb4-9194-37c7eab95714 | ||
+ | timeout client 50000 | ||
+ | |||
+ | backend 8196f752-a367-4fb4-9194-37c7eab95714 | ||
+ | # http 프로토콜 사용 | ||
+ | mode http | ||
+ | # RR 알고리즘 사용 | ||
+ | balance roundrobin | ||
+ | timeout check 10s | ||
+ | option external-check | ||
+ | # ping-wrapper.sh 스크립트를 사용하여 server 상태 모니터링 | ||
+ | external-check command / | ||
+ | fullconn 1000000 | ||
+ | option allbackups | ||
+ | timeout connect 5000 | ||
+ | timeout server 50000 | ||
+ | # 뒷단의 실제 서버(real server),서비스포트 80,모니터링규칙 inter 5s fall 3 rise 3 | ||
+ | server b6e464fd-dd1e-4775-90f2-4231444a0bbe 192.168.1.14: | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | Health Check Script ('' | ||
+ | <WRAP prewrap> | ||
+ | <code bash> | ||
+ | #!/bin/bash | ||
+ | if [[ $HAPROXY_SERVER_ADDR =~ ":" | ||
+ | /bin/ping6 -q -n -w 1 -c 1 $HAPROXY_SERVER_ADDR > /dev/null 2>&1 | ||
+ | else | ||
+ | /bin/ping -q -n -w 1 -c 1 $HAPROXY_SERVER_ADDR > /dev/null 2>&1 | ||
+ | fi | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | ==== 예시 2. 공유 풀로 전송 ==== | ||
+ | <WRAP prewrap> | ||
+ | <code bash> | ||
+ | $ openstack loadbalancer healthmonitor create --name healthmonitor1 --type PING --delay 5 --timeout 10 --max-retries 3 822f78c3-ea2c-4770-bef0-e97f1ac2eba8 | ||
+ | |||
+ | $ openstack loadbalancer l7policy create --name l7p1 --action REDIRECT_TO_POOL --redirect-pool 822f78c3-ea2c-4770-bef0-e97f1ac2eba8 1385d3c4-615e-4a92-aea1-c4fa51a75557 | ||
+ | |||
+ | $ openstack loadbalancer l7rule create --type HOST_NAME --compare-type STARTS_WITH --value " | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | '' | ||
+ | <WRAP prewrap> | ||
+ | <code vim> | ||
+ | # Configuration for loadbalancer 01197be7-98d5-440d-a846-cd70f52dc503 | ||
+ | global | ||
+ | daemon | ||
+ | user nobody | ||
+ | log /dev/log local0 | ||
+ | log /dev/log local1 notice | ||
+ | stats socket / | ||
+ | maxconn 1000000 | ||
+ | external-check | ||
+ | |||
+ | defaults | ||
+ | log global | ||
+ | retries 3 | ||
+ | option redispatch | ||
+ | |||
+ | peers 1385d3c4615e4a92aea1c4fa51a75557_peers | ||
+ | peer l_Ustq0qE-h-_Q1dlXLXBAiWR8U 172.16.1.7: | ||
+ | peer O08zAgUhIv9TEXhyYZf2iHdxOkA 172.16.1.3: | ||
+ | |||
+ | |||
+ | frontend 1385d3c4-615e-4a92-aea1-c4fa51a75557 | ||
+ | option httplog | ||
+ | maxconn 1000000 | ||
+ | bind 172.16.1.10: | ||
+ | mode http | ||
+ | acl 8d9b8b1e-83d7-44ca-a5b4-0103d5f90cb9 req.hdr(host) -i -m beg server | ||
+ | use_backend 8196f752-a367-4fb4-9194-37c7eab95714 if 8d9b8b1e-83d7-44ca-a5b4-0103d5f90cb9 | ||
+ | acl c76f36bc-92c0-4f48-8d57-a13e3b1f09e1 req.hdr(host) -i -m beg server | ||
+ | use_backend 822f78c3-ea2c-4770-bef0-e97f1ac2eba8 if c76f36bc-92c0-4f48-8d57-a13e3b1f09e1 | ||
+ | default_backend 8196f752-a367-4fb4-9194-37c7eab95714 | ||
+ | timeout client 50000 | ||
+ | |||
+ | backend 8196f752-a367-4fb4-9194-37c7eab95714 | ||
+ | mode http | ||
+ | balance roundrobin | ||
+ | timeout check 10s | ||
+ | option external-check | ||
+ | external-check command / | ||
+ | fullconn 1000000 | ||
+ | option allbackups | ||
+ | timeout connect 5000 | ||
+ | timeout server 50000 | ||
+ | server b6e464fd-dd1e-4775-90f2-4231444a0bbe 192.168.1.14: | ||
+ | |||
+ | backend 822f78c3-ea2c-4770-bef0-e97f1ac2eba8 | ||
+ | mode http | ||
+ | balance roundrobin | ||
+ | timeout check 10s | ||
+ | option external-check | ||
+ | external-check command / | ||
+ | fullconn 1000000 | ||
+ | option allbackups | ||
+ | timeout connect 5000 | ||
+ | timeout server 50000 | ||
+ | server 7da6f176-36c6-479a-9d86-c892ecca6ae5 192.168.1.6: | ||
+ | </ | ||
+ | </ | ||
+ | 볼 수 있듯이, listener에 공유 풀을 추가한 후 '' | ||
+ | |||
+ | ===== Amphora의 보안 통신 구현 ===== | ||
+ | ==== 자체 CA를 통해 구현된 SSL 통신 ==== | ||
+ | 계속해서 '' | ||
+ | |||
+ | <WRAP center round info 80%> | ||
+ | **참고**: 실제 운영 환경에서는 클라이언트 인증서를 발급하는 CA와 서버 인증서를 발급하는 CA가 달라야 합니다. 그래야 해킹된 amphora의 서버 인증서를 사용해 다른 인스턴스를 제어하지 못하게 할 수 있습니다. | ||
+ | </ | ||
+ | |||
+ | Octavia와 Dashboard가 동일한 인증서를 사용한다면 OpenStack 관리/API 네트워크를 공개하는 것과 다름없습니다. 간단히 말해서, Octavia가 자체 CA 인증서를 사용하는 데에는 두 가지 중요한 이유가 있습니다: | ||
+ | |||
+ | - '' | ||
+ | - 악의적인 사용자가 amphora를 ' | ||
+ | |||
+ | Octavia는 또한 OpenSSL을 사용하여 CA 센터를 생성하는 자동화 스크립트를 제공합니다. 다음 명령어를 실행하면 완료됩니다. | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code bash> | ||
+ | $ source / | ||
+ | </ | ||
+ | </ | ||
+ | CA 센터에 대해 좀 더 설명하자면, | ||
+ | {{: | ||
+ | |||
+ | Octavia가 자체적으로 구축한 CA 센터 | ||
+ | <WRAP prewrap> | ||
+ | <code bash> | ||
+ | $ ll / | ||
+ | total 44 | ||
+ | -rw-r--r-- 1 stack stack 1294 Oct 26 12:51 ca_01.pem | ||
+ | -rw-r--r-- 1 stack stack 989 Oct 26 12:51 client.csr | ||
+ | -rw-r--r-- 1 stack stack 1708 Oct 26 12:51 client.key | ||
+ | -rw-r--r-- 1 stack stack 4405 Oct 26 12:51 client-.pem | ||
+ | -rw-r--r-- 1 stack stack 6113 Oct 26 12:51 client.pem | ||
+ | -rw-r--r-- 1 stack stack 71 Oct 26 12:51 index.txt | ||
+ | -rw-r--r-- 1 stack stack 21 Oct 26 12:51 index.txt.attr | ||
+ | -rw-r--r-- 1 stack stack 0 Oct 26 12:51 index.txt.old | ||
+ | drwxr-xr-x 2 stack stack 20 Oct 26 12:51 newcerts | ||
+ | drwx------ 2 stack stack 23 Oct 26 12:51 private | ||
+ | -rw-r--r-- 1 stack stack 3 Oct 26 12:51 serial | ||
+ | -rw-r--r-- 1 stack stack 3 Oct 26 12:51 serial.old | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | |||
+ | 아래는 CA 인증과 관련된 설정 항목들을 나열한 것입니다. | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code vim> | ||
+ | # create new amphora flow에 적용되는 **TASK: | ||
+ | [certificates] | ||
+ | ca_private_key_passphrase = foobar | ||
+ | ca_private_key = / | ||
+ | ca_certificate = / | ||
+ | |||
+ | # AmphoraAPIClient에 적용되며, | ||
+ | [haproxy_amphora] | ||
+ | server_ca = / | ||
+ | client_cert = / | ||
+ | |||
+ | # Task: | ||
+ | [controller_worker] | ||
+ | client_ca = / | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | 먼저 SSL 통신을 설정하는 과정을 간단히 정리한 후, 구체적인 구현을 자세히 살펴보겠습니다: | ||
+ | |||
+ | - Amphora를 생성할 때 CA 센터에 서버 인증서 서명을 요청하며, | ||
+ | - '' | ||
+ | |||
+ | ==== Amphora Agent 인증서 로딩 ==== | ||
+ | 먼저 amphora에 대한 인증서 생성 구현을 살펴봅니다. | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code py> | ||
+ | # file: / | ||
+ | |||
+ | class GenerateServerPEMTask(BaseCertTask): | ||
+ | """ | ||
+ | |||
+ | Use the amphora_id for the CN | ||
+ | """ | ||
+ | |||
+ | def execute(self, | ||
+ | cert = self.cert_generator.generate_cert_key_pair( | ||
+ | cn=amphora_id, | ||
+ | validity=CERT_VALIDITY) | ||
+ | |||
+ | return cert.certificate + cert.private_key | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | Octavia Certificates는 '' | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code py> | ||
+ | # file: / | ||
+ | |||
+ | @classmethod | ||
+ | def generate_cert_key_pair(cls, | ||
+ | | ||
+ | pk = cls._generate_private_key(bit_length, | ||
+ | csr = cls._generate_csr(cn, | ||
+ | cert = cls.sign_cert(csr, | ||
+ | cert_object = local_common.LocalCert( | ||
+ | certificate=cert, | ||
+ | private_key=pk, | ||
+ | private_key_passphrase=passphrase | ||
+ | ) | ||
+ | return cert_object | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | 위의 **LocalCertGenerator.generate_cert_key_pair**의 의미는 다음과 같습니다: | ||
+ | |||
+ | - Amphora 개인 키 생성 | ||
+ | - Amphora 인증서 서명 요청(CSR) 생성 | ||
+ | - CA 센터에 Amphora 서버 인증서 서명을 요청 | ||
+ | |||
+ | 이는 일반적인 인증서 생성 절차에 속하며, **create_certificates.sh** 스크립트와의 차이점은 Octavia Certificates가 **cryptography** 라이브러리를 사용하여 구현되었다는 점입니다. | ||
+ | |||
+ | **TASK: | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code vim> | ||
+ | # file: / | ||
+ | |||
+ | [amphora_agent] | ||
+ | agent_server_ca = / | ||
+ | agent_server_cert = / | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | Gunicorn HTTP 서버가 시작될 때 인증서 파일을 로드하며, | ||
+ | <WRAP prewrap> | ||
+ | <code vim> | ||
+ | options = { | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | } | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | * '' | ||
+ | * '' | ||
+ | |||
+ | ==== AmphoraAPIClient가 인증서 요청을 전송 ==== | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code py> | ||
+ | class AmphoraAPIClient(object): | ||
+ | def __init__(self): | ||
+ | super(AmphoraAPIClient, | ||
+ | ... | ||
+ | self.session = requests.Session() | ||
+ | self.session.cert = CONF.haproxy_amphora.client_cert | ||
+ | self.ssl_adapter = CustomHostNameCheckingAdapter() | ||
+ | self.session.mount(' | ||
+ | ... | ||
+ | |||
+ | def request(self, | ||
+ | ... | ||
+ | LOG.debug(" | ||
+ | _request = getattr(self.session, | ||
+ | _url = self._base_url(amp.lb_network_ip) + path | ||
+ | LOG.debug(" | ||
+ | reqargs = { | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | reqargs.update(kwargs) | ||
+ | headers = reqargs.setdefault(' | ||
+ | ... | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | 위의 코드는 **requests** 라이브러리를 사용하여 HTTPS 요청을 실행하는 일반적인 구현입니다: | ||
+ | |||
+ | - '' | ||
+ | - '' | ||
+ | |||
+ | 마지막으로, | ||
+ | {{: | ||
+ | |||
+ | ===== Amphora의 장애 조치(페일오버) 구현 ===== | ||
+ | |||
+ | ==== Health Manager ==== | ||
+ | <WRAP center round info 80%> | ||
+ | Health Manager - 이 하위 구성 요소는 개별 amphora를 모니터링하여 정상적으로 작동하고 건강한 상태인지 확인합니다. 또한 amphora가 예상치 못하게 실패할 경우 장애 조치(페일오버) 이벤트를 처리합니다. | ||
+ | </ | ||
+ | |||
+ | 간단히 말해, Health Manager는 각 amphora의 상태를 모니터링하고, | ||
+ | |||
+ | 따라서 Health Manager Service를 이해하려면 먼저 이 서비스가 amphora의 상태를 어떻게 모니터링하는지 파악한 후, 장애 조치 프로세스의 세부 사항을 알아야 합니다. | ||
+ | |||
+ | ==== Amphora의 상태 모니터링 ==== | ||
+ | 먼저 프로그램의 진입점( **octavia/ | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code py> | ||
+ | # file: / | ||
+ | |||
+ | |||
+ | class UDPStatusGetter(object): | ||
+ | """ | ||
+ | |||
+ | The heartbeats are transmitted via UDP and this class will bind to a port | ||
+ | and absorb them | ||
+ | """ | ||
+ | def __init__(self): | ||
+ | self.key = cfg.CONF.health_manager.heartbeat_key | ||
+ | self.ip = cfg.CONF.health_manager.bind_ip | ||
+ | self.port = cfg.CONF.health_manager.bind_port | ||
+ | self.sockaddr = None | ||
+ | LOG.info(' | ||
+ | | ||
+ | self.sock = None | ||
+ | self.update(self.key, | ||
+ | |||
+ | self.executor = futures.ProcessPoolExecutor( | ||
+ | max_workers=cfg.CONF.health_manager.status_update_threads) | ||
+ | self.repo = repositories.Repositories().amphorahealth | ||
+ | |||
+ | def update(self, | ||
+ | """ | ||
+ | |||
+ | :param key: The hmac key used to verify the UDP packets. String | ||
+ | :param ip: The ip address the UDP server will read from | ||
+ | :param port: The port the UDP server will read from | ||
+ | :return: None | ||
+ | """ | ||
+ | self.key = key | ||
+ | for addrinfo in socket.getaddrinfo(ip, | ||
+ | ai_family = addrinfo[0] | ||
+ | self.sockaddr = addrinfo[4] | ||
+ | if self.sock is not None: | ||
+ | self.sock.close() | ||
+ | self.sock = socket.socket(ai_family, | ||
+ | self.sock.settimeout(1) | ||
+ | self.sock.bind(self.sockaddr) | ||
+ | if cfg.CONF.health_manager.sock_rlimit > 0: | ||
+ | rlimit = cfg.CONF.health_manager.sock_rlimit | ||
+ | LOG.info(" | ||
+ | self.sock.setsockopt(socket.SOL_SOCKET, | ||
+ | | ||
+ | break # just used the first addr getaddrinfo finds | ||
+ | if self.sock is None: | ||
+ | raise exceptions.NetworkConfig(" | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | **Class: | ||
+ | |||
+ | **참고**: 여기서 **amphora**와 **octavia-health-manager** 서비스 간의 네트워크 토폴로지 세부 사항을 강조해야 합니다. | ||
+ | |||
+ | * Octavia를 배포할 때, **ext-net**을 직접 **octavia**의 " | ||
+ | * Octavia 배포 시, 별도로 생성된 **tenant network**를 **lb-mgmt-net**으로 사용하는 경우, **CONF.health_manager.bind_ip**는 **lb-mgmt-net** IP 풀 내의 주소여야 합니다. 이 경우 **lb-mgmt-net**과 **OpenStack Management Network** 간의 통신 문제를 해결해야 합니다. **devstack**에서는 **lb-mgmt-net**의 포트 하나를 **ex-int**에 연결하여, | ||
+ | |||
+ | Devstack에서 로컬 네트워크를 연결하는 명령어 | ||
+ | <WRAP prewrap> | ||
+ | <code bash> | ||
+ | $ neutron port-create --name octavia-health-manager-standalone-listen-port \ | ||
+ | --security-group < | ||
+ | --device-owner Octavia: | ||
+ | --binding: | ||
+ | --tenant-id <octavia service> | ||
+ | |||
+ | $ ovs-vsctl --may-exist add-port br-int o-hm0 \ | ||
+ | -- set Interface o-hm0 type=internal \ | ||
+ | -- set Interface o-hm0 external-ids: | ||
+ | -- set Interface o-hm0 external-ids: | ||
+ | -- set Interface o-hm0 external-ids: | ||
+ | | ||
+ | # / | ||
+ | request subnet-mask, | ||
+ | do-forward-updates false; | ||
+ | |||
+ | $ ip link set dev o-hm0 address <Health Manager Listen Port MAC> | ||
+ | $ dhclient -v o-hm0 -cf / | ||
+ | |||
+ | |||
+ | o-hm0: flags=4163< | ||
+ | inet 192.168.0.4 | ||
+ | inet6 fe80:: | ||
+ | ether fa: | ||
+ | RX packets 1240893 | ||
+ | RX errors 0 dropped 45 overruns 0 frame 0 | ||
+ | TX packets 417078 | ||
+ | TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | 다시 주제로 돌아가서, | ||
+ | <WRAP prewrap> | ||
+ | <code vim> | ||
+ | def check(self): | ||
+ | try: | ||
+ | obj, srcaddr = self.dorecv() | ||
+ | except socket.timeout: | ||
+ | # Pass here as this is an expected cycling of the listen socket | ||
+ | pass | ||
+ | except exceptions.InvalidHMACException: | ||
+ | # Pass here as the packet was dropped and logged already | ||
+ | pass | ||
+ | except Exception as e: | ||
+ | LOG.warning(' | ||
+ | ' | ||
+ | ' | ||
+ | else: | ||
+ | self.executor.submit(update_health, | ||
+ | self.executor.submit(update_stats, | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | |||
+ | 계속해서 **amphora**가 어떻게 heartbeats를 전송하는지 살펴보겠습니다. | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code py> | ||
+ | # file: / | ||
+ | |||
+ | def main(): | ||
+ | # comment out to improve logging | ||
+ | service.prepare_service(sys.argv) | ||
+ | |||
+ | gmr.TextGuruMeditation.setup_autorun(version) | ||
+ | |||
+ | health_sender_proc = multiproc.Process(name=' | ||
+ | | ||
+ | | ||
+ | health_sender_proc.daemon = True | ||
+ | health_sender_proc.start() | ||
+ | |||
+ | # Initiate server class | ||
+ | server_instance = server.Server() | ||
+ | |||
+ | bind_ip_port = utils.ip_port_str(CONF.haproxy_amphora.bind_host, | ||
+ | | ||
+ | options = { | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | ' | ||
+ | } | ||
+ | AmphoraAgent(server_instance.app, | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | **amphora-agent** 서비스 프로세스가 시작될 때, **health_daemon.run_sender**가 로드되며, | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code py> | ||
+ | # file: / | ||
+ | |||
+ | def run_sender(cmd_queue): | ||
+ | LOG.info(' | ||
+ | sender = health_sender.UDPStatusSender() | ||
+ | |||
+ | keepalived_cfg_path = util.keepalived_cfg_path() | ||
+ | keepalived_pid_path = util.keepalived_pid_path() | ||
+ | |||
+ | while True: | ||
+ | |||
+ | try: | ||
+ | # If the keepalived config file is present check | ||
+ | # that it is running, otherwise don't send the health | ||
+ | # heartbeat | ||
+ | if os.path.isfile(keepalived_cfg_path): | ||
+ | # Is there a pid file for keepalived? | ||
+ | with open(keepalived_pid_path, | ||
+ | pid = int(pid_file.readline()) | ||
+ | os.kill(pid, | ||
+ | |||
+ | message = build_stats_message() | ||
+ | sender.dosend(message) | ||
+ | |||
+ | except IOError as e: | ||
+ | # Missing PID file, skip health heartbeat | ||
+ | if e.errno == errno.ENOENT: | ||
+ | LOG.error(' | ||
+ | ' | ||
+ | else: | ||
+ | LOG.error(' | ||
+ | 'to exception %s, skipping health heartbeat.', | ||
+ | except OSError as e: | ||
+ | # Keepalived is not running, skip health heartbeat | ||
+ | if e.errno == errno.ESRCH: | ||
+ | LOG.error(' | ||
+ | ' | ||
+ | else: | ||
+ | LOG.error(' | ||
+ | 'to exception %s, skipping health heartbeat.', | ||
+ | except Exception as e: | ||
+ | LOG.error(' | ||
+ | ' | ||
+ | |||
+ | try: | ||
+ | cmd = cmd_queue.get_nowait() | ||
+ | if cmd == ' | ||
+ | LOG.info(' | ||
+ | CONF.reload_config_files() | ||
+ | elif cmd == ' | ||
+ | LOG.info(' | ||
+ | break | ||
+ | except queue.Empty: | ||
+ | pass | ||
+ | time.sleep(CONF.health_manager.heartbeat_interval) | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | **run_sender** 함수는 **build_stats_message()**를 호출하여 heartbeats를 구성한 다음, **UDPStatusSender.dosend()**를 호출하여 데이터를 전송합니다. 중요한 점은, **keepalived** 서비스 프로세스가 정상적으로 실행되지 않으면 heartbeats를 전송하지 않는다는 것입니다. 즉, **keepalived**가 비정상인 **amphora**는 장애가 발생한 **amphora**로 처리됩니다. 데이터 전송은 여전히 UDP 소켓을 사용하며, | ||
+ | |||
+ | <WRAP prewrap> | ||
+ | <code vim> | ||
+ | # file: / | ||
+ | |||
+ | [health_manager] | ||
+ | bind_port = 5555 | ||
+ | bind_ip = 192.168.0.4 | ||
+ | controller_ip_port_list = 192.168.0.4: | ||
+ | </ | ||
+ | </ | ||
+ | 간단히 말하면, **octavia-health-manager**와 **amphora-agent**는 주기적인 heartbeats 프로토콜을 구현하여 **amphora**의 건강 상태를 모니터링합니다. | ||
+ | |||
+ | ==== failover(장애 조치) ==== | ||
+ | 장애 조치 메커니즘은 **health_manager.HealthManager.health_check()**에 의해 주기적으로 모니터링되고 트리거됩니다. | ||
+ | |||
+ | **health_check** 메서드는 주기적으로 **amphora_health** 테이블에서 "stale amphora" | ||
+ | <WRAP prewrap> | ||
+ | <code py> | ||
+ | # file: / | ||
+ | |||
+ | def get_stale_amphora(self, | ||
+ | """ | ||
+ | |||
+ | :param session: A Sql Alchemy database session. | ||
+ | :returns: [octavia.common.data_model] | ||
+ | """ | ||
+ | |||
+ | timeout = CONF.health_manager.heartbeat_timeout | ||
+ | expired_time = datetime.datetime.utcnow() - datetime.timedelta( | ||
+ | seconds=timeout) | ||
+ | |||
+ | amp = session.query(self.model_class).with_for_update().filter_by( | ||
+ | busy=False).filter( | ||
+ | self.model_class.last_update < expired_time).first() | ||
+ | |||
+ | if amp is None: | ||
+ | return None | ||
+ | |||
+ | amp.busy = True | ||
+ | |||
+ | return amp.to_data_model() | ||
+ | </ | ||
+ | </ | ||
+ | 만약 **stale amphora**가 존재하고 loadbalancer의 상태가 **PENDING_UPDATE**가 아니면, **failover amphora** 프로세스에 진입하게 됩니다. **failover amphora**의 taskflow는 **self._amphora_flows.get_failover_flow**입니다. | ||
+ | |||
+ | **failover**의 UML 다이어그램 | ||
+ | {{: | ||
+ | |||
+ | 분명히, 전체 **failover_flow**는 " | ||
+ | |||
+ | * delete old amphora | ||
+ | * MarkAmphoraPendingDeleteInDB | ||
+ | * MarkAmphoraHealthBusy | ||
+ | * ComputeDelete: | ||
+ | * WaitForPortDetach: | ||
+ | * MarkAmphoraDeletedInDB | ||
+ | |||
+ | **참고**: 만약 장애가 발생한 amphora가 **free amphora**라면, | ||
+ | |||
+ | * get a new amphora | ||
+ | * **get_amphora_for_lb_subflow**: | ||
+ | * **UpdateAmpFailoverDetails**: | ||
+ | * **ReloadLoadBalancer & ReloadAmphora**: | ||
+ | * **GetAmphoraeNetworkConfigs & GetListenersFromLoadbalancer & GetAmphoraeFromLoadbalancer**: | ||
+ | * **PlugVIPPort**: | ||
+ | * **AmphoraPostVIPPlug**: | ||
+ | * **update_amps_subflow\AmpListenersUpdate**: | ||
+ | * **CalculateAmphoraDelta**: | ||
+ | * **HandleNetworkDelta**: | ||
+ | * **AmphoraePostNetworkPlug**: | ||
+ | * **ReloadLoadBalancer** | ||
+ | * **MarkAmphoraMasterInDB** | ||
+ | * **AmphoraUpdateVRRPInterface**: | ||
+ | * **CreateVRRPGroupForLB**: | ||
+ | * **AmphoraVRRPUpdate**: | ||
+ | * **AmphoraVRRPStart**: | ||
+ | * **ListenersStart**: | ||
+ | * **DisableAmphoraHealthMonitoring**: | ||
+ | |||
+ | 간단히 정리하면, | ||
+ | |||
+ | 주의해야 할 점: | ||
+ | <WRAP center round info 80%> | ||
+ | 직관적으로는 **old amphora**를 삭제하기 전에 **new amphora**를 부팅하는 것이 더 나아 보일 수 있지만, 이는 복잡한 문제입니다. 만약 타겟 호스트가 자원 제한(anti-affinity 규칙 때문에)을 겪고 있다면, **old amphora** 삭제 후 부팅은 성공할 수 있지만, 먼저 부팅하려고 하면 실패할 수 있습니다. 이로 인해 비동기 API와의 통신에서 **LB**가 **ERROR** 상태로 끝날 수 있지만, **amphora**는 여전히 살아 있게 됩니다. 향후에는 이 문제를 처리하기 위해 실패 시 재시도 흐름을 고려하거나, | ||
+ | </ | ||
+ | |||
+ | 비록 장애 조치는 **old amphora**를 삭제한 후 **new amphora**를 얻는 것이지만, | ||
+ | |||
+ | ==== 장애 조치 테스트 ==== | ||
+ | MASTER amphora의 전원을 끄면 **octavia-health-manager** 서비스가 **amphora failover**를 트리거합니다. | ||
+ | <WRAP prewrap> | ||
+ | <code bash> | ||
+ | Nov 22 11:22:31 control01 octavia-health-manager[29147]: | ||
+ | Nov 22 11:22:31 control01 octavia-health-manager[29147]: | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | old amphorae | ||
+ | <WRAP prewrap> | ||
+ | <code bash> | ||
+ | | ||
+ | | b237b2b8-afe4-407b-83f2-e2e60361fa07 | amphora-bcff6f9e-4114-4d43-a403-573f1d97d27e | ACTIVE | lb-mgmt-net=192.168.0.11 | ||
+ | | 46eccf47-be10-47ec-89b2-0de44ea3caec | amphora-cd444019-ce8f-4f89-be6b-0edf76f41b77 | ACTIVE | lb-mgmt-net=192.168.0.9; | ||
+ | | bc043b23-d481-45c4-9410-f7b349987c98 | amphora-a1c1ba86-6f99-4f60-b469-a4a29d7384c5 | ACTIVE | lb-mgmt-net=192.168.0.3; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | new amphoras | ||
+ | <WRAP prewrap> | ||
+ | <code bash> | ||
+ | | 712ff785-c082-4b53-994c-591d1ec0bf7b | amphora-caa6ba0f-1a68-4f22-9be9-8521695ac4f4 | ACTIVE | lb-mgmt-net=192.168.0.13 | ||
+ | | 2ddc4ba5-b829-4962-93d8-562de91f1dab | amphora-4ff5d6fe-854c-4022-8194-0c6801a7478b | ACTIVE | lb-mgmt-net=192.168.0.23; | ||
+ | | b237b2b8-afe4-407b-83f2-e2e60361fa07 | amphora-bcff6f9e-4114-4d43-a403-573f1d97d27e | ACTIVE | lb-mgmt-net=192.168.0.11 | ||
+ | | bc043b23-d481-45c4-9410-f7b349987c98 | amphora-a1c1ba86-6f99-4f60-b469-a4a29d7384c5 | ACTIVE | lb-mgmt-net=192.168.0.3; | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | new amphora haproxy config | ||
+ | <WRAP prewrap> | ||
+ | <code vim> | ||
+ | # Configuration for loadbalancer 01197be7-98d5-440d-a846-cd70f52dc503 | ||
+ | global | ||
+ | daemon | ||
+ | user nobody | ||
+ | log /dev/log local0 | ||
+ | log /dev/log local1 notice | ||
+ | stats socket / | ||
+ | maxconn 1000000 | ||
+ | external-check | ||
+ | |||
+ | defaults | ||
+ | log global | ||
+ | retries 3 | ||
+ | option redispatch | ||
+ | |||
+ | peers 1385d3c4615e4a92aea1c4fa51a75557_peers | ||
+ | peer 3dVescsRZ-RdRBfYVLW6snVI9gI 172.16.1.3: | ||
+ | peer l_Ustq0qE-h-_Q1dlXLXBAiWR8U 172.16.1.7: | ||
+ | |||
+ | |||
+ | frontend 1385d3c4-615e-4a92-aea1-c4fa51a75557 | ||
+ | option httplog | ||
+ | maxconn 1000000 | ||
+ | bind 172.16.1.10: | ||
+ | mode http | ||
+ | acl 8d9b8b1e-83d7-44ca-a5b4-0103d5f90cb9 req.hdr(host) -i -m beg server | ||
+ | use_backend 8196f752-a367-4fb4-9194-37c7eab95714 if 8d9b8b1e-83d7-44ca-a5b4-0103d5f90cb9 | ||
+ | acl c76f36bc-92c0-4f48-8d57-a13e3b1f09e1 req.hdr(host) -i -m beg server | ||
+ | use_backend 822f78c3-ea2c-4770-bef0-e97f1ac2eba8 if c76f36bc-92c0-4f48-8d57-a13e3b1f09e1 | ||
+ | default_backend 8196f752-a367-4fb4-9194-37c7eab95714 | ||
+ | timeout client 50000 | ||
+ | |||
+ | backend 8196f752-a367-4fb4-9194-37c7eab95714 | ||
+ | mode http | ||
+ | balance roundrobin | ||
+ | timeout check 10s | ||
+ | option external-check | ||
+ | external-check command / | ||
+ | fullconn 1000000 | ||
+ | option allbackups | ||
+ | timeout connect 5000 | ||
+ | timeout server 50000 | ||
+ | server b6e464fd-dd1e-4775-90f2-4231444a0bbe 192.168.1.14: | ||
+ | |||
+ | backend 822f78c3-ea2c-4770-bef0-e97f1ac2eba8 | ||
+ | mode http | ||
+ | balance roundrobin | ||
+ | timeout check 10s | ||
+ | option external-check | ||
+ | external-check command / | ||
+ | fullconn 1000000 | ||
+ | option allbackups | ||
+ | timeout connect 5000 | ||
+ | timeout server 50000 | ||
+ | server 7da6f176-36c6-479a-9d86-c892ecca6ae5 192.168.1.6: | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | new amphora keepalived config | ||
+ | <WRAP prewrap> | ||
+ | <code vim> | ||
+ | vrrp_script check_script { | ||
+ | script / | ||
+ | interval 5 | ||
+ | fall 2 | ||
+ | rise 2 | ||
+ | } | ||
+ | |||
+ | vrrp_instance 01197be798d5440da846cd70f52dc503 { | ||
+ | state MASTER | ||
+ | interface eth1 | ||
+ | virtual_router_id 1 | ||
+ | priority 100 | ||
+ | nopreempt | ||
+ | garp_master_refresh 5 | ||
+ | garp_master_refresh_repeat 2 | ||
+ | advert_int 1 | ||
+ | authentication { | ||
+ | auth_type PASS | ||
+ | auth_pass b76d77e | ||
+ | } | ||
+ | |||
+ | unicast_src_ip 172.16.1.3 | ||
+ | unicast_peer { | ||
+ | 172.16.1.7 | ||
+ | } | ||
+ | |||
+ | virtual_ipaddress { | ||
+ | 172.16.1.10 | ||
+ | } | ||
+ | track_script { | ||
+ | check_script | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | **new amphora**의 구성 파일과 네트워크 설정이 **old amphora**와 동일하며, | ||
+ | |||
+ | |||
+ | ===== Neutron-lbaas vs. LBaaS v2 API vs. Octavia vs. Octavia v2 API ===== | ||
+ | 사용자들이 가장 많이 묻는 질문 중 LBaaS v2 API와 Octavia v2 API를 혼동하는 것이 단연 1위입니다. 여기서 이 개념들을 간단히 구분해 보겠습니다. | ||
+ | |||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
===== 참조링크 ===== | ===== 참조링크 ===== | ||
* https:// | * https:// | ||
+ | * https:// | ||
* https:// | * https:// | ||