PtoLB, 팩스택부터 로드밸런서까지
이번 장에서는 뉴트론에서 제공하는 로드밸런서를 사용했던 내용을 기록하려고 한다.
도입
오픈스택을 시작하면서 계속되는 시행착오를 겪는 중이다. 의존성이나 순서, 그 때 그때의 패키지 상황에 따라서 너무 다른 상황을 맞이했다. 이번에는 그런 일을 방지하고 싶었다. 그래서 마치 '실험'을 진행하듯이 잘 격리된 환경에서 잘 정의된(well defined) 절차에 따라 진행해보려 했다. 또한 '로드밸런서'라는 목적에 충실하고, 다른 작업은 최소화하기로 했다.
개발 환경
Virtual machine | Spec | OS | Openstack Version |
---|---|---|---|
Virtualbox 5.0.14 | RAM : 8GB | Centos 7 | Liberty |
CPU core : 2 |
- 운영체제는 설치 후 아무 동작(패키지 설치 등의)도 하지 않았다.
네트워크 설정
Adapter : 1
Type : NAT
name : enp0s3
in /etc/sysconfig/network-scripts/ifcfg-enp0s3
...
ONBOOT=yes
...
Adapter : 2
Type : Host only network
Address : 10.0.10.1
name : enp0s8
in /etc/sysconfig/network-scripts/ifcfg-enp0s8
TYPE=Ethernet
BOOTPROTO=static
NAME=enp0s8
DEVICE=enp0s8
ONBOOT=yes
IPADDR=10.0.10.10
NETMASK=255.255.255.0
호스트 설정
in /etc/hostname
hostname
in /etc/hosts
...
host-ip hostname
선행 패키지(prerequisite) 설치
# yum install -y centos-release-openstack-liberty && \
yum -y update && yum install -y openstack-packstack
Packstack 실행 준비
packstack --gen-answer [file name]
명령을 사용하면 응답(answer) 파일을 만들 수 있다. 여기서는 응답(answer) 파일을 대신 만들어주는 작은 실행기(evaluator)를 만들었다.
default_ip="10.0.2.15" # 여기서는 기본 설정되는 NAT 주소 대신 Host only IP를 사용
all_in_one_ip="10.1.10.10" # Adapter 2 : Host only IP
ans_file="answer-$(date +"%Y%m%d-%H:%M:%S").txt" # 스크립트 실행 시점의 시간을 파일명에 적용
# Answer file generation
packstack --gen-answer=$ans_file \
--default-password="openstack" \
--keystone-admin-passwd="openstack" \
--nagios-install=n \ # 사용하지 않는 패키지들은 설치 제외
--os-swift-install=n \
--os-ceilometer-install=n \
--os-neutron-lbaas-install=y # 로드밸런서 설치
# IP configurations( Some options not working, therefore forced to change )
sed -i "s/$default_ip/$all_in_one_ip/g" $ans_file
- 작업 상황 중 packstack 일부 옵션은 설정을 해도 적용이 되지 않아서
sed
로 추가 변환했다.
(예를 들면,--install-hosts "$all_in_one_ip"
은 적용이 되지 않았다.) - 팩스택을 이용하면 쉽게 기초 설정을 마칠 수 있을 것 같지만 설정을 많이 할 수록 오류가 날 가능성이 같이 늘어나는 것 같다. 그리고 팩스택의 설정이 곧 모든 설정을 완성한 것은 아니라서 설치 후에 '빈 틈'을 매꿔 설정해야 한다. ( 오류들을 구글링해보면 팩스택이나 devstack의 설치 과정때문에 고통 받는 사람들이 많다. )
- 하지만 수동 설치는 순서, 생략, 오타 등 여러 오류의 가능성이 존재한다. 일단 손가락이 아프다.
팩스택 설치
이제 설치하면 된다.
packstack --answer-file "$ans_file"
실제 환경에서는 아래 예처럼 된다.
packstack --answer-file answer-20160517-01:21:02.txt
...
Applying 10.0.10.10_postscript.pp
10.0.10.10_postscript.pp: [ DONE ]
Applying Puppet manifests [ DONE ]
Finalizing [ DONE ]
**** Installation completed successfully ******
...
네트워크 설명
여기서는 뉴트론 설치시 기본으로 있는 네트워크인 'public', 'private'를 사용하자. 서브넷 할당부터 라우터 연결(association)까지 다 되어있다.
# neutron net-list
+--------------------------------------+---------+------------------------------------------------------+
| id | name | subnets |
+--------------------------------------+---------+------------------------------------------------------+
| 205dda97-5ed7-4b34-82d0-361022aa298e | public | 243406d1-bec2-43ed-9108-eee41ae90c6a 172.24.4.224/28 |
| dda7b324-245f-41ee-a514-3960f332f693 | private | 64f44ec8-10eb-4e35-b483-78b74765da1e 10.0.0.0/24 |
+--------------------------------------+---------+------------------------------------------------------+
좀 더 자세히 보자.
# neutron net-show public | grep type
| provider:network_type | vxlan |
# neutron net-show public | grep router
| router:external | True |
public 네트워크는 이름처럼 외부 라우터(qg 인터페이스)와 연결이 되어있다. 곧 이것을 통해 인스턴스가 외부와 통신할 수 있게 할 것이다.
로드 밸런서 설치
아래 패키지들을 설치한다.
- 팩스택으로 lbaas를 설치했더라도 필요한 패키지가 없을 수 있으니 확인해보자.
HAProxy는 유닉스 기반 환경에서 사용 가능한 공짜 오픈소스 로드밸런서이다. 이것을 설치하는 이유는 오픈스택에서 HAProxy를 연동하여 로드밸런싱을 구현하기 때문이다.# yum install -y haproxy openstack-neutron-lbaas
기본 설정이 잘 되어 있어서 따로 설정해줄 것은 아래 한 줄이다.
in /etc/neutron/neutron.conf
service_plugins = router,lbaas # service_plugins에 lbaas를 추가해주자
풀과 풀 멤버
갑자기 풀(pool)이 나온 이유는 로드밸런서가 일을 넘겨 줄 대상 집단이 필요하기 때문이다. 여기서 '풀'이란 풀 멤버의 모임(group)이다.
그렇다면 풀멤버는 무엇일까? 이것은 ip와 포트로 구성된 형태로 외부에서 들어오는 입력을 수용(listen)하는 애들(엔티티)이다. 예를 들면 인스턴스 위에 돌아가는 웹서버가 그것이 될 수 있다.
그럼 만들어보자.
# neutron lb-pool-create --lb-method ROUND_ROBIN --name rr --protocol HTTP --subnet-id [PRIVATE_SUBNET ID]
'rr'이라는 이름을 가지는 풀 하나를 만들었다. 이 풀은 라운드로빈 방식을 따르며, http를 수용한다. 로드밸런서의 통신 방향은 '밖에서 안으로(ingress)'만 들어오므로 서브넷은 내부망(PRIVATE SUBNET)이다.
# neutron lb-member-create --address [INSTANCE IP] --protocol-port 80 rr
풀 멤버를 만들었다. 주소는 인스턴스 아이피로 이어주자. rr은 방금 만든 풀 이름이다.
만약 인스턴스가 없다면 아래처럼 만들어주자.
nova boot i --image cirros --flavor m1.tiny --nic net-id=[private ip] --min-count 2 --max-count 2
# nova list
+--------------------------------------+------+--------+------------+-------------+---------------------------------+
| ID | Name | Status | Task State | Power State | Networks |
+--------------------------------------+------+--------+------------+-------------+---------------------------------+
| 7e8958ed-a05a-4b24-a2c3-f01d5886473e | i-1 | ACTIVE | - | Running | private=10.0.0.9, 172.24.4.231 |
| 6e6d4a7f-9550-4f12-ad20-33921859b8f1 | i-2 | ACTIVE | - | Running | private=10.0.0.10, 172.24.4.232 |
Floating ipprivate=10.0.0.9, 172.24.4.231
에서 '172.
'으로 시작하는 부분은 floating IP이다. public 네트워크 쪽에서 이어준(associating) 아이피로, qg 인터페이스를 통해 출입할 때 필요한 아이피이다. 이것을 통해서도 사용자가 인스턴스에 접속할 수가 있다. ( ssh, ping 등 ). 설정 방법은 아래에서 새 인스턴스를 만들 때 다루기로 하자.
# ip netns exec qrouter-1380a0bd-21de-4305-8288-551477f55356 ip a
...
19: qr-ac2a6bc4-ac: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN
...
inet 10.0.0.1/24 brd 10.0.0.255 scope global qr-ac2a6bc4-ac
...
20: qg-9f6fcb58-95: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN
...
inet 172.24.4.226/28 brd 172.24.4.239 scope global qg-9f6fcb58-95
valid_lft forever preferred_lft forever
inet 172.24.4.231/32 brd 172.24.4.231 scope global qg-9f6fcb58-95
valid_lft forever preferred_lft forever
inet 172.24.4.232/32 brd 172.24.4.232 scope global qg-9f6fcb58-95
valid_lft forever preferred_lft forever
...
qg 인터페이스에 floating ip가 연결되었음(association)을 알 수 있다.
이제 인스턴스에 접속해보자.
# ip netns exec qrouter-1380a0bd-21de-4305-8288-551477f55356 ssh [email protected]
아마 안될 것이다. 시큐리티 그룹에서 허용하지 않았기 때문이다.
시큐리티 그룹 설정
'lbaas'
라는 이름의 시큐리티 그룹을 만들자. 이 그룹에 들어오는(ingress) ssh, ping, http를 허용하게 하자.
# neutron security-group-create lbaas
# neutron security-group-rule-create --protocol tcp --port-range-min 80 --port-range-max 80 --direction ingress lbaas
# neutron security-group-rule-create --protocol tcp --port-range-min 22 --port-range-max 22 --direction ingress lbaas
# neutron security-group-rule-create --protocol icmp --direction ingress lbaas
이제 인스턴스에 연결하자.
# nova add-secgroup [Instance ID] lbaas
# nova remove-secgroup [Instance ID] default
# ip netns exec qrouter-1380a0bd-21de-4305-8288-551477f55356 ssh [email protected]
$
# ip netns exec qrouter-1380a0bd-21de-4305-8288-551477f55356 ping 172.24.4.231
PING 172.24.4.231 (172.24.4.231) 56(84) bytes of data.
64 bytes from 172.24.4.231: icmp_seq=1 ttl=64 time=1.10 ms
64 bytes from 172.24.4.231: icmp_seq=2 ttl=64 time=0.572 ms
이제 잘 된다.
# ip netns exec qrouter-1380a0bd-21de-4305-8288-551477f55356 curl -X GET http://172.24.4.231
curl: (7) Failed connect to 172.24.4.231:80; Connection refused
하지만 정작 이번 실습을 위한 http 연결은 문제가 있다.
새 이미지로 다시 시작
이런 오류의 원인은 cirros 인스턴스의 내부에서 80번 포트로 서버를 열어주지 않았다는 데 있다. 서버를 돌리기에 적합한 새 이미지를 만들어 다시 시작하자. 여기선 ubuntu를 사용한다.
# curl -O http://uec-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-disk1.img
외부에서 다운을 받아서,
# glance image-create --name ubuntu \
--container-format bare \
--disk-format qcow2 \
--file trusty-server-cloudimg-amd64-disk1.img \
--visibility public
+------------------+--------------------------------------+
| Property | Value |
+------------------+--------------------------------------+
| checksum | e903d04033149e800fc90400dee1abb8 |
| container_format | bare |
| created_at | 2016-05-18T12:02:27Z |
| disk_format | qcow2 |
| id | 64623909-2a44-469d-a179-5f5730527a0b |
| min_disk | 0 |
| min_ram | 0 |
| name | ubuntu |
| owner | d28e582c20a048df9c42590ccc3e08cb |
| protected | False |
| size | 259850752 |
| status | active |
| tags | [] |
| updated_at | 2016-05-18T12:02:28Z |
| virtual_size | None |
| visibility | public |
+------------------+--------------------------------------+
아니면 좀 더 단순하게 외부 url로부터 바로 만드는 방법도 있다.
# glance image-create --name ubuntu \
--container-format bare \
--disk-format qcow2 \
--copy-from http://uec-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-disk1.img
--visibility public
이제 인스턴스를 만들자.
# nova boot u \
--image ubuntu \
--flavor m1.medium \
--nic net-id=dda7b324-245f-41ee-a514-3960f332f693 \
--num-instances 2 \
--security-groups lbaas
인스턴스 생성 옵션에 시큐리티 그룹을 설정할 수가 있다. 그러나 이렇게 생성을 하면 보안 설정과 무관하게 ssh 접속은 불가능하다. 왜일까? 그 이유는 두 노드(여기서는 호스트와 인스턴스) 사이에 공유하는 ssh 인증 키가 없기 때문이다. 그동안 cirros나 잘 설정되어 있는 ubuntu를 쓰고 있어서 이것을 생각조차 안하고 있었다. ( AWS EC2를 사용하고 있다면 접속 시에 .pem
key를 송신자 측에서 전송하던 것을 생각해보자. )
- 물론, 호스트의 주소와 비밀번호로 접속할 수도 있다. 이 때는 옵션에 실행할 스크립트를 추가해주어야 한다.
--user-data $filename
.
키를 만들었으니 인스턴스 생성 옵션에 추가해보자.nova keypair-add lbaas > lbaas.pem chmod 600 lbaas.pem
이제 잘 될까?# nova boot u \ --image ubuntu \ --flavor m1.medium \ --nic net-id=dda7b324-245f-41ee-a514-3960f332f693 \ --num-instances 2 \ --security-groups lbaas \ --key_name lbaas
안 된다. 시스템 설치 중이다.[root@c7 ~(keystone_admin)]# ip netns exec qrouter-1380a0bd-21de-4305-8288-551477f55356 ssh -i lbaas.pem [email protected] ssh: connect to host 10.0.0.15 port 22: No route to host
생각보다 오래 걸린다. 인스턴스 상태 로그를 확인해보거나, 마음 편하게 10분 정도 후에 다시 시도해보면 아래처럼 될 것이다.# ip netns exec qrouter-1380a0bd-21de-4305-8288-551477f55356 ssh -i lbaas.pem [email protected] ssh: connect to host 172.24.4.233 port 22: Connection refused
... Warning: Permanently added '172.24.4.233' (ECDSA) to the list of known hosts. Welcome to Ubuntu 14.04.4 LTS (GNU/Linux 3.13.0-86-generic x86_64) ... ubuntu@u-1:~$
Floating IP 연결
아까 생략했던 Floating IP 연결을 해보자.
# nova list | grep u-
| f28deba9-3062-4b05-9af9-25eed8726a2c | u-1 | ACTIVE | - | Running | private=10.0.0.15 |
| 5488343b-b700-4e49-912c-1c0889abadb5 | u-2 | ACTIVE | - | Running | private=10.0.0.16 |
상황을 보니 2개를 이어야 겠다.
# neutron floatingip-create public
# neutron floatingip-create public
단순하게 2개 만든다.
# nova add-floating-ip u-1 172.24.4.233
# nova add-floating-ip u-2 172.24.4.234
연결했으니 확인해보면,
# nova list | grep u-
| 296a2b4e-6058-4329-aaaf-49b7f4ab23c5 | u-1 | ACTIVE | - | Running | private=10.0.0.15, 172.24.4.233 |
| 9a2ed9db-8586-47be-9236-70aed1a01e2f | u-2 | ACTIVE | - | Running | private=10.0.0.16, 172.24.4.234 |
잘 됐다.
VIP 설정
여기서 Virtual IP란 로드밸런서에 붙는(asscoating) 아이피이다. 로드밸런서 자체로는 행동(로드밸런싱)을 할 수가 없기 때문에 VIP를 붙여야 한다. 들어오는 연결(conneciton) 요청은 VIP로 들어와서 로드밸런서의 규칙에 따라 각 풀 멤버에 전달된다.
# neutron lb-vip-create --name v --protocol-port 80 --protocol HTTP \
--subnet-id 64f44ec8-10eb-4e35-b483-78b74765da1e rr
Created a new vip:
+---------------------+--------------------------------------+
| Field | Value |
+---------------------+--------------------------------------+
| address | 10.0.0.17 |
| admin_state_up | True |
| connection_limit | -1 |
| description | |
| id | 76e90bb9-3643-462a-b90d-f0dd8b1c84a2 |
| name | v |
| pool_id | 43a1c232-28f6-4bc3-9a03-7a053ef3c876 |
| port_id | 39c98d05-4b61-4b01-b826-4f4f42341d6b |
| protocol | HTTP |
| protocol_port | 80 |
| session_persistence | |
| status | PENDING_CREATE |
| status_description | |
| subnet_id | 64f44ec8-10eb-4e35-b483-78b74765da1e |
| tenant_id | d28e582c20a048df9c42590ccc3e08cb |
+---------------------+--------------------------------------+
VIP가 private 서브넷 중 하나에 할당되었다.
라운드 로빈 테스트
HTTP 서버 실행
먼저 인스턴스에 http 서버를 켜주자.
# ip netns exec qrouter-1380a0bd-21de-4305-8288-551477f55356 ssh -i lbaas.pem [email protected]
$ echo "u-1" > ~/index.html
$ sudo python -m SimpleHTTPServer 80
Serving HTTP on 0.0.0.0 port 80 ...
- 만약,
sudo: unable to resolve host [HOST NAME]
이런 오류가 뜬다면 호스트 아이피가 설정이 안된 것이니 해주자.
여기서는 아래와 같이 했다.$ sudo sed -i '1s/^/[HOST IP] [HOST NAME]\n/' /etc/hosts
$ sudo sed -i '1s/^/10.0.0.15 u-1\n/' /etc/hosts
- 나머지 인스턴스들에 대해서도 동일한 작업을 수행한다.
풀 멤버 할당
이젠 안쓰는 cirros 멤버는 멤버에서 빼고 사용할 인스턴스를 멤버에 추가하자.
# neutron lb-member-delete dda565cb-1a7d-4caf-842a-66d1a240043e
# neutron lb-member-delete eefc7a8d-ea89-4c3e-91b3-74e8cddb37a7
# neutron lb-member-create --address 10.0.0.15 --protocol-port 80 rr
# neutron lb-member-create --address 10.0.0.16 --protocol-port 80 rr
# neutron lb-member-list
+--------------------------------------+-----------+---------------+--------+----------------+--------+
| id | address | protocol_port | weight | admin_state_up | status |
+--------------------------------------+-----------+---------------+--------+----------------+--------+
| c56a8257-d28e-473c-80de-172bfc66c3de | 10.0.0.16 | 80 | 1 | True | ACTIVE |
| e73ae538-5eb2-4ee6-a056-f21bee35896b | 10.0.0.15 | 80 | 1 | True | ACTIVE |
+--------------------------------------+-----------+---------------+--------+----------------+--------+
서버 GET 요청
직접 서버 호스트에 GET 요청을 해보자.
클라이언트
# ip netns exec qrouter-1380a0bd-21de-4305-8288-551477f55356 curl http://172.24.4.233
u-1
서버
172.24.4.226 - - [18/May/2016 13:22:17] "GET / HTTP/1.1" 200 -
라운드 로빈 테스트
드디어 여기까지 왔다. 이제 VIP에 GET 요청을 던져보자. 10번 던져보자.
# cat round_robin.sh
#!/bin/bash
for i in {1..10}
do
ip netns exec qrouter-1380a0bd-21de-4305-8288-551477f55356 \
curl http://10.0.0.17;
done
간단한 스크립트를 이용했다.
# sh round_robin.sh
u-2
u-1
u-2
u-1
u-2
u-1
u-2
u-1
u-2
u-1
멤버가 둘 뿐이라 아쉽지만 각 풀 멤버에 순서대로 전달됨을 볼 수 있다.
TMUX 이용
이 글을 읽는 분들은 뭔가 생략되었음을 느낄 수 있다. 웹 서버 실행기(apache, nginx) 없이 어떻게 테스트를 해야할까? 여기서는 TMUX를 사용했다.
# yum install tmux
- 만약 이 패키지를 못찾는다면
yum update
를 해주자. 하지만 centos 버전에 따라 수동 설치가 필요할 수도 있다. 아래 링크를 참조하자. https://gist.github.com/sturadnidge/4185338 https://gist.github.com/Root-shady/d48d5282651634f464af
조금 더 기술적으로
이 쯤에서 로드밸런서가 사용하는 방법에 대해서 설명하면 좋을 것 같다.
[root@c7 ~(keystone_admin)]# neutron lb-pool-create -h | grep method
[--description DESCRIPTION] --lb-method
--lb-method {ROUND_ROBIN,LEAST_CONNECTIONS,SOURCE_IP}
오픈스택의 로드밸런서는 위에 옵션처럼 ROUND_ROBIN, LEAST_CONNECTIONS, SOURCE_IP의 3가지 방법을 사용한다. 아쉽게도 이런 알고리즘에 대해서 나도 정확히 알고 있지는 않지만 Learning OpenStack Networking (Neutron) - Second Edition에 나와 있는 내용을 토대로 간략하게 정리해보았다.
- 여기서는 연결은 요청(request)이나 일(load)이라고 봐도 무방하다.
라운드로빈(ROUND_ROBIN)
로드밸런서는 대열(line) 안에 있는 다음 서버에 새로운 연결을 건낸다(pass). 시간이 지나며 모든 연결은 부하가 분산된 모든 노드(machine) 사이에 고르게 분배된다. 라운드로빈은 자원을 최소로 사용하는 알고리즘이다. 하지만 이것은 언제 노드가 과부하되는지 알 수가 없다. 그래서 이것을 방지하기 위해서 모든 풀 멤버는 동일한 프로세싱 속도, 연결 속도, 메모리 크기를 가져야 한다.
최소 연결(LEAST_CONNECTIONS)
로드밸런서는 새로운 연결을 가장 적은 연결 수를 가진 서버에 연결을 건낸다. 실시간으로 밸런싱이 달라지기 때문에 동적 알고리즘으로 여겨진다. ( 시스템이 각 서버에 붙은 연결의 수를 계속 파악(keep track)한다는 점에서, 그리고 트래픽을 고르게 분배한다는 점에서 ). 더 높은 성능을 가진 풀멤버는 더 빠르게 연결을 처리할 수 있기 때문에 더 많은 트래픽을 받는다.
- 아직 사용해보지는 않았지만 풀 멤버의 'weight' 속성을 이용해서 결정할 수 있을 것 같다.
# neutron lb-member-show c56a8257-d28e-473c-80de-172bfc66c3de | grep weight | weight | 1
소스 아이피(SOURCE_IP)
같은 아이피를 가진 모든 연결을 같은 풀 멤버에 보낸다. 연결들은 초기에는 라운드로빈으로, 그 다음부터 같은 아이피로부터 연달아 일어나는 연결들을 통해 분배가 된다. 이 방법은 어플리케이션이 클라이언트가 특정 서버에 지속적으로 연결을 하려고 할 때 유용할 것이다. 예를 들면 로컬 서버에 세션 정보를 가지고 있어야 하는 온라인 쇼핑 장바구니 같은 경우가 있다.
References
Learning OpenStack Networking (Neutron) - Second Edition
http://docs.openstack.org/developer/devstack/guides/devstack-with-lbaas-v2.html
https://www.rdoproject.org/networking/lbaas
http://docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-policy.html#routing-policy-weighted