
### CONTROLLER
shutdown -h now
Compute Node를 설치하기 이전 현재 Rocky Linux Host 컴퓨터가 오래된 만큼 Controller Node를 안정적인 Window Host에 완전하게 백업할 필요성을 느껴 아래와 같은 과정으로 통해 백업하였다.
virsh dumpxml controller > /var/lib/libvirt/images/controller.xml
Controller Node VM을 그대로 다른 서버에서 복원하려면 디스크 파일뿐만 아니라 XML 설정도 필요하기 때문에 우선 virsh dumpxml을 통해 controller KVM의 XML 파일을 생성해주었다.
$ virsh snapshot-info controller --snapshotname Controller-Node-Complete
Name: Controller-Node-Complete
Domain: controller
Current: yes
State: disk-snapshot
Location: external
Parent: Installing-Nova
Children: 0
Descendants: 0
Metadata: yes
이후 controller의 스냅샷이 external 임을 확인할 수 있었는데
Libvirt(virsh)의 스냅샷은 2가지 방식으로 저장된다.
qcow2 파일 내부에 저장됨controller.qcow2)만 백업하면 스냅샷도 같이 보존됨qcow2 안에 포함됨/var/lib/libvirt/images/controller.Controller-Node-Complete 같은 파일이 외부 스냅샷일 가능성이 높음$ ls /var/lib/libvirt/images
...
controller.Controller-Node-Complete
controller.environment-snapshot
controller.initial-snapshot
controller.Installing-Nova
controller.qcow2
controller.qcow2.bak
controller.xml
Rocky-9.2-x86_64-minimal.iso
따라서 이전까지 생성했던 모든 스냅샷과 Controller Node 관련 파일들을 모두 확인해주었고
tar -cvzf /home/judemin/controller_backup.tar.gz -C /var/lib/libvirt/images \
controller.Controller-Node-Complete \
controller.environment-snapshot \
controller.initial-snapshot \
controller.Installing-Nova \
controller.qcow2 \
controller.qcow2.bak \
controller.xml \
Rocky-9.2-x86_64-minimal.iso
/home/judemin/controller_backup.tar.gz 파일로 Controller Node와 관련된 모든 파일을 압축하였으며
### WINDOWS
scp judemin@<LinuxIP>:/home/judemin/controller_backup.tar.gz C:\Users\<WindowUserName>\Openstack-Backup
위와 같이 scp 명령어를 통해 Linux의 압축 파일을 Window로 복사하고
### WINDOWS
tar -xvzf C:\Users\<WindowUserName>\Openstack-Backup\controller_backup.tar.gz -C C:\Users\<WindowUserName>\Openstack-Backup\
x controller.Controller-Node-Complete
x controller.environment-snapshot
x controller.initial-snapshot
...
Window에서 tar 명령어를 통해 해당 압축 파일의 압축을 해제하여 백업을 완료할 수 있었다.
또한 해당 파일들을 Window 파일 탐색기를 통해 확인할 수 있다.
client_loop: send disconnect: Connection reset
judmin@xxx.xxx.xxx.xxx password:
Permission denied, please try again.
root@xxx.xxx.xxx.xxx password:
Permission denied, please try again.
Compute Node를 설정하기 위해 SSH를 통해 접속하려고 하자, 이전에 로그인된 상태에서 Connection reset 오류가 발생하고 이후 이전의 User 비밀번호들이 모두 사용 불가인 것을 확인하였다.
$ passwd
...
passwd authentication token manipulation error
따라서 passwd로 비밀번호 변경을 시도하였으나 위와 같은 오류가 발생하여
mount -o remount,rw /
root (/) 파일 시스템이 읽기 전용 (ro) 상태라면, 비밀번호 변경이나 시스템 수정이 불가능하므로
root (/) 파일 시스템을 읽기/쓰기 (rw) 모드로 다시 마운트하고
$ mount
/dev/mapper/rl-root on /proc/8401 type xfs (rw,relatime,seclabel,attr2,inode64,logbufs=8,logbsize=32k,noquota)
이를 확인해준 후
$ lsattr /etc/passwd
---------------------- /etc/passwd
$ lsattr /etc/shadow
--------a------------- /etc/shadow
lsattr로 /etc/passwd와 /etc/shadow를 확인해본 결과 /etc/shadow 파일에 append-only (a) 속성이 설정되어 해당 파일에 기존 내용을 덮어쓰거나 삭제할 수 없는 문제로 판단해
$ chattr -a /etc/shadow
$ lsattr /etc/shadow
---------------------- /etc/shadow
/etc/shadow 파일의 append-only 속성을 제거하고
$ passwd judemin
Changing password for user judemin.
New password:
Retype new password:
passwd: all authentication tokens updated successfully.
passwd로 비밀번호를 변경한 뒤 다시 정상적으로 접근할 수 있었다.
$ ps aux --sort=-%cpu | head -20
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 2060 595 0.0 2449196 7168 ? SNsl Feb09 632:19 /bin/dhpcd
이후 구축을 진행하던 도중 컴퓨터의 온도가 비정상적으로 높고 CPU의 사용량이 너무 높아 /bin/dhpcd 프로세스가 작동중인 것을 확인할 수 있었다.
Virsh(libvirt)에서 가상 네트워크를 사용할 때 기본적으로 dnsmasq가 DHCP 서버 역할을 하기에 정상적인 환경에서는 dnsmasq가 실행 중이어야 하고, dhpcd는 실행되지 않아야 한다.
ps aux | grep dnsmasq
dnsmasq 1840 0.0 0.0 10432 2320 ? S Feb09 0:00 /usr/sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/opstnat.conf --leasefile-ro --dhcp-script=/usr/libexec/libvirt_leaseshelper
root 1842 0.0 0.0 10328 1424 ? S Feb09 0:00 /usr/sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/opstnat.conf --leasefile-ro --dhcp-script=/usr/libexec/libvirt_leaseshelper
dnsmasq 1894 0.0 0.0 10432 2184 ? S Feb09 0:00 /usr/sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf --leasefile-ro --dhcp-script=/usr/libexec/libvirt_leaseshelper
root 1895 0.0 0.0 10328 904 ? S Feb09 0:00 /usr/sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf --leasefile-ro --dhcp-script=/usr/libexec/libvirt_leaseshelper
root 6405 0.0 0.0 6408 2176 pts/4 S+ 00:33 0:00 grep --color=auto dnsmasq
따라서 dnsmasq가 정상적으로 작동하고 있는지를 우선 확인하였다
$ ls -lah /bin/dhpcd
-rwxr-xr-x. 1 root root 2.3M Feb 7 02:32 /bin/dhpcd
이후 /bin/dhpcd 파일이 실제로 존재하는지 확인하고
$ file /bin/dhpcd
/bin/dhpcd: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, no section header
/bin/dhpcd의 정보를 출력해보았다.
정상적인 리눅스 실행 파일이라면 "dynamically linked" 또는 ELF 헤더가 포함되어 있어야 하는데, dhpcd는 이상한 실행 파일 패턴을 보이고 있기 때문에 멀웨어 파일이라고 판단하였다.
| 항목 | 설명 |
|---|---|
| Statically Linked | 모든 라이브러리를 내부에 포함하여 분석을 어렵게 만듦 (루트킷, 멀웨어 특성) |
| No Section Header | 정상적인 ELF 파일이 아니며, 분석을 어렵게 하기 위한 난독화(Obfuscation) 가능성 큼 |
| /bin 경로 | /bin에 DHCP 관련 실행 파일이 존재하는 것은 매우 비정상적 |
$ sudo crontab -l | grep dhpcd
23 * * * * /bin/dhpcd >/dev/null 2>/dev/null
또한 crontab에서도 해당 파일을 실행하는 명령어가 존재하여
sudo crontab -e
우선 이를 제거해주었고
$ cat /etc/rc.local | grep dhpcd
/bin/dhpcd >/dev/null 2>/dev/null
rc.local 파일에서 해당 멀웨어 파일에 대한 실행 명령을 삭제해주었다.
sudo kill -9 2060
sudo pkill -9 dhpcd
이후 해당 프로세스를 제거한 후
sudo rm -f /bin/dhpcd
/bin/dhpcd 실행파일 또한 삭제해주었다.
$ ps aux --sort=-%cpu | head -20
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 961 180 1.6 2490116 263892 ? Ssl Feb09 205:29 /bin/systemd-worker 43.226.237.68 212.113.112.44 138.124.19.102 148.72.168.29 154.86.156.69 61.74.135.124 221.179.57.254 8.219.93.186 67.159.24.26 173.231.184.126 35.247.243.10 180.97.195.28 36.41.172.79 192.210.241.208 134.209.188.18 54.159.112.99 119.84.66.98 87.120.165.243 84.21.173.166 45.128.99.140 213.159.67.143 119.84.66.55 41.225.238.233 103.124.101.54 129.227.58.70 50.2.36.119 178.128.125.32 209.141.57.99 212.132.93.112 103.145.145.79 170.84.39.236 223.75.204.39 79.120.74.12 14.199.52.62 45.95.146.8 103.195.101.126 125.124.83.191 23.94.194.210 61.53.69.210 45.61.185.64 139.159.102.236 185.181.210.57 188.235.173.12 207.244.252.183 43.239.110.69 125.124.99.83 107.155.37.142 84.21.173.97 57.128.179.23 45.95.147.221 64.227.163.222
이후에도 systemd-worker 라는 여러개의 호스트와 연결된 프로세스가 가동중인 것을 확인하고
$ sudo netstat -tulnp | grep systemd-worker
tcp6 0 0 :::1919 :::* LISTEN 961/systemd-worker
systemd-worker가 외부의 포트와 연결되어 있는 것을 추가적으로 확인한 후
systemctl list-units --type=service | grep systemd-worker
systemd-worker.service loaded active running systemd-worker.service
데몬에까지 포함되어 있는 것을 확인하여
sudo kill -9 961
sudo pkill -9 systemd-worker
해당 프로세스를 kill한 이후
sudo systemctl disable systemd-worker
sudo systemctl stop systemd-worker
sudo rm -f /etc/systemd/system/systemd-worker.service
최종적으로 데몬을 제거하고 데몬 파일 또한 삭제하여 이후 메모리와 CPU 사용량이 정상으로 돌아오는 것을 확인하였다.
루트킷(rootkit)은 악의적인 목적을 가진 공격자가 시스템에 침투하여 사용권한을 얻어낸 후 백도어 프로세스나 파일 등을 심어놓고 시스템 상에서 정상적인 관리자가 흔적을 볼 수 없도록 하는 프로그램을 의미
루트킷에 감염된 시스템은 심각한 경우 복구가 불가한 사태가 벌어질 수 있어, 예방차원에서 주기적으로 rkhunter를 이용한 점검이 필요
또한 이후 Openstack Host로의 공격을 효과적으로 탐지하고, 조치하기 위하여
sudo dnf install -y epel-release
sudo dnf install -y rkhunter
rkhunter 패키지를 설치하여 루트킷 프로그램을 탐지해내고자 한다
sudo rkhunter --update
패키지 설치 후 최신 보안 데이터베이스를 업데이트하고
sudo rkhunter --check
rkhunter를 통해 시스템 전체 검사를 실행하여 루트킷, 악성코드, 백도어가 있는지 확인한다.
sudo cat /var/log/rkhunter/rkhunter.log | grep -i warning
또한 검사 결과는 /var/log/rkhunter.log 파일에 저장되어 이를 확인해주고
$ sudo crontab -e
0 12 * * * /usr/bin/rkhunter --check --sk
cron을 설정하여 시스템을 주기적으로 검사할 수 있도록 하였다.
passwd root
passwd judemin
또한 avast의 Password Generator로 생성된 비밀번호로 현존하는 사용자의 비밀번호를 모두 변경해주어 추가적인 Brute Force 공격을 방지한다.
이후 다량의 로그인 실패 시도를 포착하여 Brute-forcing이나 자동 로그인 시도를 막기 위하여
sudo yum install fail2ban -y
Fail2Ban을 설치하여 여러 번 로그인 시도한 IP를 차단할 수 있도록 하였다
$ vim /etc/fail2ban/jail.conf
[sshd]
enabled = true
maxretry = 10
findtime = 600
bantime = 86400
설치 이후 /etc/fail2ban/jail.conf를 위와 같이 수정하였다
- maxretry = 10
실패한 로그인 시도가 10번 초과되면 차단
findtime = 600
600초(10분) 동안 maxretry 횟수를 초과하는 로그인 실패가 감지되면 차단
bantime = 86400
IP 차단 시간이 86400초(1일) 동안 지속
sudo systemctl enable fail2ban --now
sudo systemctl status fail2ban
이후 위와 같이 fail2ban 데몬을 실행하고
$ sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 2
| |- Total failed: 385
| `- Journal matches: _SYSTEMD_UNIT=sshd.service + _COMM=sshd + _COMM=sshd-session
`- Actions
|- Currently banned: 6
|- Total banned: 6
`- Banned IP list: 139.59.127.12 217.182.249.2 87.120.113.119 182.215.66.232 64.226.110.156 196.251.70.115
현재 호스트 머신을 공격중인 IP를 위 명령어로 확인할 수 있었다.
virt-install \
--name compute \
--ram 7000 \
--vcpus 6 \
--cpu host-passthrough \
--disk path=/home/libvirt/images/compute.qcow2,size=100,format=qcow2,bus=virtio,cache=none \
--os-variant rocky9.0 \
--location /var/lib/libvirt/images/Rocky-9.2-x86_64-minimal.iso \
--extra-args "inst.text console=ttyS0,115200n8" \
--network network=opstnat,model=virtio \
--graphics none
우선 설계와 같이 CPU와 MEM, Disk를 할당하여 KVM을 생성한다.
이때, CPU 성능을 극대화하기 위해 --cpu host-passthrough 옵션을 사용해 VM이 host CPU의 기능을 그대로 활용할 수 있도록 하였다.
df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 4.0M 0 4.0M 0% /dev
tmpfs 7.7G 0 7.7G 0% /dev/shm
tmpfs 3.1G 9.4M 3.1G 1% /run
/dev/mapper/rl-root 70G 30G 41G 43% /
/dev/nvme0n1p1 1014M 595M 420M 59% /boot
/dev/mapper/rl-home 398G 2.9G 396G 1% /home
tmpfs 1.6G 4.0K 1.6G 1% /run/user/0
tmpfs 1.6G 4.0K 1.6G 1% /run/user/1000
또한 여유 공간이 충분한 /home 파티션에 디스크 파일 경로 지정하여 설치를 진행하였다.
$ pwd
/home/libvirt/images
이때 /home/libvirt/images 해당 디렉터리를 생성해주어야 한다.
[root@compute ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 52:54:00:01:56:bd brd ff:ff:ff:ff:ff:ff
inet 192.168.100.102/24 brd 192.168.100.255 scope global dynamic noprefixroute enp1s0
valid_lft 3592sec preferred_lft 3592sec
inet6 fe80::5054:ff:fe01:56bd/64 scope link noprefixroute
valid_lft forever preferred_lft forever
이전 포스팅에서와 같이 Rocky Linux를 Text 모드로 설치하고 virsh net을 통해 Static IP 설정을 위와 같이 완료해주었다.
### COMPUTE
[root@compute ~]# ping controller
PING controller (192.168.100.101) 56(84) bytes of data.
64 bytes from controller (192.168.100.101): icmp_seq=1 ttl=64 time=0.411 ms
64 bytes from controller (192.168.100.101): icmp_seq=2 ttl=64 time=0.812 ms
### CONTROLLER
[root@controller ~]# ping compute
PING compute (192.168.100.102) 56(84) bytes of data.
64 bytes from compute (192.168.100.102): icmp_seq=1 ttl=64 time=0.436 ms
64 bytes from compute (192.168.100.102): icmp_seq=2 ttl=64 time=0.254 ms
또한 Compute Node와 Conrtoller Node에서 각각의 Hostname을 통해 네트눠크 상에서 접근이 가능한 것을 확인할 수 있었다.
SQL Database, Message Queue, Memcached, Etcd 등은 Controller Node에서만 실행되고 Compute Node는 Controller Node와 통신해서 필요한 데이터를 가져오는 방식이므로 개별적인 설치가 필요 없다.
또한 Cinder는 선택적으로 설치 가능하지만 현재 Controller Node에서 관리하기 때문에 Compute Node에서는 openstack-nova-compute와 neutron-openvswitch-agent만 설치하면 된다.
$ yum install -y chrony
$ vim /etc/chrony.conf
# Use public servers from the pool.ntp.org project.
# Please consider joining the pool (https://www.pool.ntp.org/join.html).
server controller iburst
또한 NTP 동기화를 위해 위와 같이 chrony를 설치하고 conf 파일을 수정한다.
systemctl enable chronyd.service
systemctl start chronyd.service
이후 chronyd.service 데몬을 재시작하면
$ chronyc sources
MS Name/IP address Stratum Poll Reach LastRx Last sample
===============================================================================
^? controller 0 6 0 - +0ns[ +0ns] +/- 0ns
chronyc 명령어를 통해 NTP의 소스를 controller로 잘 반영하고 있는 것을 볼 수 있다.
dnf install -y libvirt qemu-kvm virt-install virt-manager
우선 Libvirt와 관련 패키지를 설치하고
dnf config-manager --enable crb
dnf install -y centos-release-openstack-zed
dnf config-manager --set-enabled centos-openstack-zed
dnf repolist | grep openstack
이전 Controller Node와 같이 OpenStack Zed 저장소 추가한다.
dnf clean all
dnf makecache
dnf update -y
이후 패키지 목록을 업데이트하면 Openstack Service들을 설치할 준비가 끝난다.
yum install -y openstack-nova-compute
openstack-nova-compute 패키지를 설치해준 후
vim /etc/nova/nova.conf
/etc/nova/nova.conf를 아래 공식 문서에 맞게 수정하고
https://docs.openstack.org/nova/2023.1/install/compute-install-rdo.html
$ egrep -c '(vmx|svm)' /proc/cpuinfo
12
위 명령어를 통해 현재 vcpu 지원 현황을 알 수 있다.
systemctl enable libvirtd.service openstack-nova-compute.service
systemctl start libvirtd.service openstack-nova-compute.service
또한 이후 libvirtd와 openstack-nova-compute 데몬을 실행하여준다.
만약 위와 같은 오류가 난다면
### CONTROLLER
firewall-cmd --permanent --add-port=5672/tcp
firewall-cmd --reload
firewall-cmd --list-ports | grep 5672
Controller의 RabbitMQ 서버로의 접근이 불가하여 발생할 수 있기에 Controller의 방화벽에서 5672 포트를 연다.
$ telnet controller 5672
Trying 192.168.100.101...
Connected to controller.
Escape character is '^]'.
아후 telnet을 통해 정상적으로 Controller의 RabbitMQ 서버 (:5672)로 접근이 가능한 것을 확인한다.
$ vim vi /etc/nova/nova.conf
# transport_url = rabbit://openstack:<RABBIT_PASS>@controller
transport_url = rabbit://openstack:<RABBIT_PASS>@controller:5672/
공식 문서와는 다르게 /etc/nova/nova.conf를 위와 같이 수정하고
$ rabbitmqctl list_users
Listing users ...
user tags
openstack []
guest [administrator]
rabbitmqctl을 통해 openstack User의 권한을 확인해주었을 때 admin이 아닌 것을 확인해
rabbitmqctl set_permissions openstack ".*" ".*" ".*"
rabbitmqctl set_user_tags openstack administrator
$ rabbitmqctl list_users
user tags
openstack [administrator]
openstack User의 Permission과 태그를 변경하여준다.
$ rabbitmqctl list_vhosts
Listing vhosts ...
name
/
rabbitmqctl add_vhost openstack
rabbitmqctl set_permissions -p openstack openstack ".*" ".*" ".*"
또한 이후 RabbitMQ의 vhost를 추가하여주고
systemctl restart rabbitmq-server.service
rabbitmq-server를 재시작한다.
$ vi /etc/nova/nova.conf
...
auth_url =
이후 /etc/nova/nova.conf에서 auth_url를 이전에 Controller Node와 같이 설정하고
### CONTROLLER
openstack role add --project admin --user nova admin
nova User의 권한 문제를 방지하기 위해 admin 권한을 추가해주고
### CONTROLLER
firewall-cmd --permanent --add-port=5000/tcp
firewall-cmd --reload
추가적으로 Keystone에 접근할 수 있는 5000번 포트를 방화벽에서 허용한다.
### COMPUTE
$ vim /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.100.101 controller
최종적으로 Compute Node에 controller 호스트를 추가하고
### COMPUTE
systemctl restart openstack-nova-compute
openstack-nova-compute 데몬을 재시작하여 문제를 해결할 수 있었다.
$ vim /etc/nova/nova.conf
[DEFAULT]
...
compute_driver = libvirt.LibvirtDriver
이후 위와 같은 오류가 발생하면 compute_driver 관련 설정을 추가하여 해결할 수 있다.
$ systemctl status openstack-nova-compute
● openstack-nova-compute.service - OpenStack Nova Compute Server
Loaded: loaded (/usr/lib/systemd/system/openstack-nova-compute.service; en>
Active: active (running) since Mon 2025-02-10 02:28:15 EST; 7s ago
Main PID: 53926 (nova-compute)
Tasks: 23 (limit: 48950)
Memory: 126.2M
CPU: 1.830s
CGroup: /system.slice/openstack-nova-compute.service
└─53926 /usr/bin/python3 /usr/bin/nova-compute
mkdir -p /var/lib/nova/instances
chown -R nova:nova /var/lib/nova/instances
chmod 755 /var/lib/nova/instances
또한 만약 위와 같은 오류가 발생한다면 /var/lib/nova/instances에 대한 접근 권한이나 해당 디렉터리 자체가 존재하지 않는다는 것이므로 이를 생성해주고
$ ls -ld /var/lib/nova/instances
drwxr-xr-x. 2 nova nova 6 Apr 26 2024 /var/lib/nova/instances
/var/lib/nova/instances 디렉터리를 확인해준 이후
systemctl restart openstack-nova-compute
데몬을 재시작하여 이를 해결할 수 있었다.
$ su -s /bin/sh -c "nova-manage cell_v2 discover_hosts --verbose" nova
...
Getting computes from cell 'cell1': 4c904549-f28f-4d89-b47d-b1de8135152a
이후 새로운 Compute 노드를 추가할 때, 해당 Compute 노드를 등록하려면 Controller 노드에서 위 명령을 실행해야 하기 때문에 해당 명령어를 실행해주면
$ openstack compute service list
+--------------------------------------+----------------+------------+----------+---------+-------+----------------------------+
| ID | Binary | Host | Zone | Status | State | Updated At |
+--------------------------------------+----------------+------------+----------+---------+-------+----------------------------+
| 799ac5c1-3d7a-4517-b113-d39398c2f0c3 | nova-scheduler | controller | internal | enabled | up | 2025-02-10T07:29:41.000000 |
| 28892e63-324f-48ff-8813-1f7b4a4bee91 | nova-conductor | controller | internal | enabled | up | 2025-02-10T07:29:37.000000 |
| 259f0bee-aa49-4511-a110-9d077e8f5032 | nova-compute | compute | nova | enabled | up | 2025-02-10T07:29:40.000000 |
+--------------------------------------+----------------+------------+----------+---------+-------+----------------------------+
정상적으로 compute 호스트가 추가되고 이를 확인할 수 있다.
yum install -y openstack-neutron-openvswitch
Neutron을 설정하기 위해 우선 openstack-neutron-openvswitch 패키지를 설치하고
$ vim /etc/neutron/neutron.conf
공식 문서에 맞게 /etc/neutron/neutron.conf 파일을 수정한다
https://docs.openstack.org/neutron/2023.1/install/compute-install-rdo.html
ovs-vsctl add-br br-ex
이후 br-ex 브릿지를 추가하고
vim /etc/neutron/plugins/ml2/openvswitch_agent.ini
/etc/neutron/plugins/ml2/openvswitch_agent.ini 또한 동식 문서에 맞게 수정한 뒤
https://docs.openstack.org/neutron/2023.1/install/compute-install-option2-rdo.html
$ vim /etc/nova/nova.conf
auth_url = http://controller:5000
auth_type = password
project_domain_name = Default
user_domain_name = Default
region_name = RegionOne
project_name = admin
username = neutron
password = NEUTRON_PASS
/etc/nova/nova.conf에 Neutron 관련 설정 파일을 추가한다
systemctl restart openstack-nova-compute.service
systemctl enable neutron-openvswitch-agent.service
systemctl start neutron-openvswitch-agent.service
이후 관련 데몬들을 재시작하거나, 시작해주면 된다.
$ tail -n 50 /var/log/neutron/openvswitch-agent.log
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/neutron/cmd/eventlet/plugins/ovs_neutron_agent.py", line 27, in main
2025-02-10 03:15:32.769 54568 ERROR neutron agent_main.main()
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/neutron/plugins/ml2/drivers/openvswitch/agent/main.py", line 38, in main
2025-02-10 03:15:32.769 54568 ERROR neutron of_main.main()
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/main.py", line 34, in main
2025-02-10 03:15:32.769 54568 ERROR neutron app_manager.AppManager.run_apps([
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/os_ken/base/app_manager.py", line 371, in run_apps
2025-02-10 03:15:32.769 54568 ERROR neutron hub.joinall(services)
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/os_ken/lib/hub.py", line 112, in joinall
2025-02-10 03:15:32.769 54568 ERROR neutron t.wait()
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/eventlet/greenthread.py", line 181, in wait
2025-02-10 03:15:32.769 54568 ERROR neutron return self._exit_event.wait()
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/eventlet/event.py", line 132, in wait
2025-02-10 03:15:32.769 54568 ERROR neutron current.throw(*self._exc)
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/eventlet/greenthread.py", line 221, in main
2025-02-10 03:15:32.769 54568 ERROR neutron result = function(*args, **kwargs)
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/os_ken/lib/hub.py", line 74, in _launch
2025-02-10 03:15:32.769 54568 ERROR neutron raise e
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/os_ken/lib/hub.py", line 69, in _launch
2025-02-10 03:15:32.769 54568 ERROR neutron return func(*args, **kwargs)
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_oskenapp.py", line 44, in agent_main_wrapper
2025-02-10 03:15:32.769 54568 ERROR neutron LOG.exception("Agent main thread died of an exception")
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/oslo_utils/excutils.py", line 227, in __exit__
2025-02-10 03:15:32.769 54568 ERROR neutron self.force_reraise()
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/oslo_utils/excutils.py", line 200, in force_reraise
2025-02-10 03:15:32.769 54568 ERROR neutron raise self.value
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_oskenapp.py", line 41, in agent_main_wrapper
2025-02-10 03:15:32.769 54568 ERROR neutron ovs_agent.main(bridge_classes)
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py", line 2948, in main
2025-02-10 03:15:32.769 54568 ERROR neutron agent = OVSNeutronAgent(bridge_classes, ext_mgr, cfg.CONF)
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py", line 156, in __init__
2025-02-10 03:15:32.769 54568 ERROR neutron self.ovs = ovs_lib.BaseOVS()
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/neutron/agent/common/ovs_lib.py", line 144, in __init__
2025-02-10 03:15:32.769 54568 ERROR neutron self.ovsdb = impl_idl.api_factory()
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/neutron/agent/ovsdb/impl_idl.py", line 37, in api_factory
2025-02-10 03:15:32.769 54568 ERROR neutron _idl_monitor = n_connection.OvsIdlMonitor()
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/neutron/agent/ovsdb/native/connection.py", line 109, in __init__
2025-02-10 03:15:32.769 54568 ERROR neutron super(OvsIdlMonitor, self).__init__()
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/neutron/agent/ovsdb/native/connection.py", line 84, in __init__
2025-02-10 03:15:32.769 54568 ERROR neutron helper = self._get_ovsdb_helper(self._ovsdb_connection)
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/neutron/agent/ovsdb/native/connection.py", line 99, in _get_ovsdb_helper
2025-02-10 03:15:32.769 54568 ERROR neutron helpers.enable_connection_uri(connection)
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/oslo_privsep/priv_context.py", line 269, in _wrap
2025-02-10 03:15:32.769 54568 ERROR neutron self.start()
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/oslo_privsep/priv_context.py", line 283, in start
2025-02-10 03:15:32.769 54568 ERROR neutron channel = daemon.RootwrapClientChannel(context=self)
2025-02-10 03:15:32.769 54568 ERROR neutron File "/usr/lib/python3.9/site-packages/oslo_privsep/daemon.py", line 348, in __init__
2025-02-10 03:15:32.769 54568 ERROR neutron listen_sock.bind(sockpath)
2025-02-10 03:15:32.769 54568 ERROR neutron PermissionError: [Errno 13] Permission denied
2025-02-10 03:15:32.769 54568 ERROR neutron
하지만 위와 같은 오류가 발생하여
sudo mkdir -p /var/run/neutron
sudo chown neutron:neutron /var/run/neutron
sudo chmod 755 /var/run/neutron
우선 /var/run/neutron 디렉터리를 생성해주고
$ ls -l /var/run/openvswitch/db.sock
srwxr-x---. 1 openvswitch hugetlbfs 0 Feb 10 03:22 /var/run/openvswitch/db.sock
Open vSwitch의 데이터베이스 소켓이 정상적으로 생성되었는지 확인한 뒤
sudo chown openvswitch:openvswitch /var/run/openvswitch/db.sock
sudo chmod 660 /var/run/openvswitch/db.sock
해당 소켓의 소유자와 권한을 변경해준다.
setenforce 0
이후 혹시나 SELinux의 문제일 수도 있어 Permissive 모드로 변경한 후
sudo systemctl restart openvswitch
sudo systemctl restart neutron-openvswitch-agent
관련 데몬을 재시작하여
$ openstack network agent list
+--------------------------------------+--------------------+------------+-------------------+-------+-------+---------------------------+
| ID | Agent Type | Host | Availability Zone | Alive | State | Binary |
+--------------------------------------+--------------------+------------+-------------------+-------+-------+---------------------------+
| 1f9257e6-e17c-44f7-847e-7b42611db8af | Open vSwitch agent | compute | None | :-) | UP | neutron-openvswitch-agent |
| 5714217b-89ba-416f-8c16-ee556ed3e8e2 | Open vSwitch agent | controller | None | :-) | UP | neutron-openvswitch-agent |
| 70b36a3e-68fd-4f1b-8ee4-cb5645140281 | Metadata agent | controller | None | :-) | UP | neutron-metadata-agent |
| df911242-e20c-4f08-ac59-45a4e0dc2e9f | L3 agent | controller | nova | :-) | UP | neutron-l3-agent |
| f4726158-927e-434c-8591-ca340534d40b | DHCP agent | controller | nova | :-) | UP | neutron-dhcp-agent |
+--------------------------------------+--------------------+------------+-------------------+-------+-------+---------------------------+
위와 같이 compute 호스트의 neutron-openvswitch-agent를 확인할 수 있었다.

VXLAN(Virtual eXtensible LAN)은 L3 네트워크 위에서 L2 네트워크를 확장할 수 있도록 하는 오버레이 네트워크 기술이다.
이는 OpenStack, Kubernetes 같은 클라우드 환경에서 가상 네트워크를 격리하고 확장하는 데 많이 활용된다.
특히, 데이터센터에서는 각 노드가 별도의 관리 IP(예: 10.0.0.x)를 사용하여 VXLAN 터널을 구성한다.
VXLAN을 사용하면 물리적 L3 네트워크(관리 네트워크) 위에서 가상 L2 네트워크를 생성할 수 있다.
이를 위해 각 노드는 관리 네트워크의 IP(10.0.0.x)를 사용하여 VXLAN 터널을 설정하게 된다.
예를 들어, OpenStack 환경에서 다음과 같은 물리적 L3 네트워크(관리 네트워크)를 가질 수 있다.
Controller Node: 10.0.0.1
Compute Node 1: 10.0.0.2
Compute Node 2: 10.0.0.3
Network Node: 10.0.0.4
이 관리 네트워크를 통해 VXLAN 터널을 설정하고, 각 노드는 서로 캡슐화된 패킷을 주고받게 된다.
VXLAN을 통한 네트워크 통신은 VTEP(VXLAN Tunnel Endpoint)라는 개념을 이용해 이루어진다.
1) VTEP 설정`
Compute Node, Network Node 등은 VTEP 역할을 수행VTEP은 관리 네트워크(10.0.0.x)에서 IP를 할당받고, 이를 통해 VXLAN 패킷을 송수신2) VNI(VXLAN Network Identifier) 할당
VXLAN에서는 네트워크를 구분하기 위해 VNI(24bit)를 사용테넌트(사용자 그룹)마다 다른 VNI를 할당 가능Tenant A: VNI 1001
Tenant B: VNI 1002VNI를 가진 가상머신(VM)끼리만 VXLAN 터널을 통해 통신할 수 있음3) VXLAN 캡슐화 및 터널링
Compute Node에서 VM이 다른 Compute Node의 VM과 통신하려고 하면, 패킷은 VXLAN을 통해 캡슐화됨L3 네트워크(10.0.0.x 관리 네트워크)를 통해 다른 Compute Node의 VTEP으로 전달VTEP은 VXLAN 헤더를 해제하고 원래 L2 프레임을 VM에게 전달
Overlay Network란 기존 네트워크를 바탕으로 그 위에 구성된 또 다른 네트워크, 기존의 네트워크 위에 별도의 노드들(nodes)과 논리적 링크들(logical links)을 구성하여 이루어진 가상 네트워크
오버레이 네트워크를 구성하기 위해 오버레이 네트워크의 기반 네트워크인 언더레이 네트워크(Underlay Network)가 필요하다
VXLAN, GRE, Geneve 등의 터널링을 통해 오버레이 네트워크를 구성1) L2 스위칭 기능
2) SDN 지원 (OpenFlow)
3) 오버레이 네트워크 지원 (VXLAN, GRE, Geneve)
4) 고급 QoS 및 트래픽 관리
5)고급 패킷 필터링 및 보안
OVS는 크게 커널 모듈, 유저 스페이스 데몬, OpenFlow 프로토콜로 구성
1) 커널 모듈 (openvswitch.ko)
openvswitch.ko)을 통해 L2 스위칭을 처리2) 유저 스페이스 데몬 (ovs-vswitchd, ovsdb-server)
ovs-vswitchd: OVS의 핵심 프로세스로, OpenFlow 규칙을 기반으로 패킷을 처리ovsdb-server: OVS 데이터베이스(OVSDB)를 관리하며, 설정 정보를 저장3) OpenFlow 프로토콜 (SDN 컨트롤러 연동)

1) 가상 머신 간 통신
Host 1 내의 VM1과 VM2가 통신하려면, OVS 브릿지(br0)가 패킷을 포워딩2) 다른 호스트 간 통신
VM1(Host 1)에서 VM3(Host 2)로 패킷을 보낼 경우VM1의 패킷이 OVS 브릿지 br0를 통해 캡슐화됨물리 네트워크 인터페이스(eth0)를 통해 데이터 네트워크로 전송Host 2의 OVS 브릿지가 패킷을 수신하고, 캡슐화를 해제한 뒤 VM3로 전달3) 관리 네트워크
각 호스트는 관리 네트워크(Management Network)를 통해 통신하며, SDN 컨트롤러 또는 관리 서버와 연동
Self Service Network(tenant 네트워크) 환경에서는 인스턴스는 오버레이 네트워크(ex. VXLAN을 통해 192.168.200.0/24 같은 별도 대역)를 사용하지만, 외부와 통신하려면 Provider 네트워크(앞서 nmcli로 br-ex에 192.168.100.x를 할당한 flat 네트워크)를 연결해 주어야 한다.
즉, 두 네트워크를 Neutron Router로 연결하여 인스턴스는 내부 self-service 네트워크의 이점을 누리면서도, Floating IP를 통해 외부 통신이 가능하도록 구성하고자 한다.
$ virsh net-edit opstnat
<network>
<name>opstnat</name>
<uuid>bf2cbc9d-43a4-4249-b430-a02b5ba3acc1</uuid>
<forward mode='nat'/>
<bridge name='virbr10' stp='off' delay='0'/>
<mac address='52:54:00:8c:9b:3c'/>
<ip address='192.168.100.1' netmask='255.255.255.0'>
<dhcp>
<range start='192.168.100.100' end='192.168.100.200'/>
<host mac='c6:c0:9b:4d:33:42' name='controller' ip='192.168.100.101'/>
<host mac='6a:66:48:b9:1f:43' name='compute' ip='192.168.100.102'/>
</dhcp>
</ip>
</network>
우선 위와 같이 virsh의 opstnat의 MAC 주소를 현재 br-ex의 주소로 변경한다.
dnf install -y epel-release
dnf install -y openvswitch NetworkManager-ovs
이후 NetworkManager에서 OVS를 활용하기 위해 위 패키지들을 설치한다.
network-manager-openvswitch 라는 패키지 이름이 RHEL/CentOS 계열 기본 저장소에는 없거나 이름이 다를 수 있기 때문에 epel-release 레파지토리를 우선적으로 설치해주어야 한다.
sudo systemctl restart NetworkManager
sudo systemctl restart openvswitch
이후 해당 데몬들을 재시작해주고
$ vim /etc/NetworkManager/NetworkManager.conf
[main]
plugins=keyfile,ifcfg-rh,ovs
NetworkManager.conf에서 ovs 플러그인을 활성화한다.
sudo dnf install -y dhcp-client
이후 추후 br-ex의 IP 주소를 opstnat의 DHCP 서버로부터 받아오기 위한 dhcp-client를 설치한다.
nmcli connection modify "enp1s0" ipv4.method disabled
nmcli connection down "enp1s0"
이후 enp1s0에 할당된 IP를 비활성화합니다.
(“Wired connection 1”와 같이 인터페이스가 아니라 Connection Name을 활용해야 한다)
sudo ovs-vsctl add-br br-ex
이후 OVS 브릿지를 생성해주고
sudo ovs-vsctl add-port br-ex enp1s0
enp1s0를 br-ex의 포트로 추가한다.
즉, OVS가 enp1s0의 트래픽을 직접 제어할 수 있도록 enp1s0를 br-ex의 port로 연결하는 것이다.
nmcli connection add type ovs-bridge ifname br-ex con-name br-ex-ip
OVS 브릿지에 외부 IP를 할당하려면, nmcli로 ovs-bridge 타입의 Connection을 생성해야 한다.
(이때 Connection Name은 br-ex-ip를 사용한다)
nmcli connection modify br-ex-ip \
ipv4.method manual \
ipv4.addresses "192.168.100.102/24" \
ipv4.gateway "192.168.100.1" \
ipv4.dns "8.8.8.8 8.8.4.4"
br-ex-ip 연결에 IP, 게이트웨이, DNS 등을 수동으로 설정한다.
Compute Node이기 때문에 192.168.100.102를 할당한다.
nmcli connection up br-ex-ip
이제 br-ex 인터페이스에 192.168.100.102/24가 할당되고, 기본 게이트웨이가 192.168.100.1로 설정된다.
$ vim /etc/neutron/plugins/ml2/openvswitch_agent.ini
...
[ovs]
bridge_mappings = provider:br-ex
[vxlan]
local_ip = 192.168.100.102
l2_population = true
이후 neutron 서비스에 연결하기 위해 /etc/neutron/plugins/ml2/openvswitch_agent.ini의 local_ip 설정을 변경하고
<interface type='network'>
<mac address='6a:66:48:b9:1f:43'/>
<source network='opstnat'/>
<model type='virtio'/>
<address type='pci' domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
</interface>
libvirt 도메인의 network interface 설정을 위와 같이 변경한다.
6a:66:48:b9:1f:43은 br-ex에 할당된 MAC 주소이다
sudo dhclient -v br-ex
이후 인터페이스에서 DHCP 요청을 강제로 재시도한다.
dhclient (독립 실행형 DHCP 클라이언트)dhclient는 단순히 해당 인터페이스에 대해 직접 DHCP 요청을 보내고 응답을 받아 IP를 할당한다NetworkManager의 복잡한 상태 관리나 연결 프로파일 검증 없이 동작하므로, 인터페이스가 DHCP 요청을 받을 수 있으면 바로 IP를 얻는다따라서, Compute 노드에서 nmcli로 연결을 올리면 NetworkManager가 내부 구성이나 다른 활성 연결 때문에 정상적으로 진행되지 않아 타임아웃이 발생할 수 있지만 수동으로 dhclient를 실행하면 그런 제약 없이 직접 DHCP 요청을 보내 IP를 받기 때문에 성공할 수 있다.
[root@compute ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master ovs-system state UP group default qlen 1000
link/ether 6a:66:48:b9:1f:43 brd ff:ff:ff:ff:ff:ff
inet6 fe80::f38:74f3:29bf:d0c5/64 scope link noprefixroute
valid_lft forever preferred_lft forever
3: ovs-system: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether b6:12:c1:fd:d5:c7 brd ff:ff:ff:ff:ff:ff
4: br-ex: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
link/ether 6a:66:48:b9:1f:43 brd ff:ff:ff:ff:ff:ff
inet 192.168.100.102/24 brd 192.168.100.255 scope global dynamic br-ex
valid_lft 3658sec preferred_lft 3658sec
5: br-int: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
link/ether f6:5a:ee:fe:e6:47 brd ff:ff:ff:ff:ff:ff
[root@compute ~]# ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=115 time=31.6 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=115 time=32.2 ms
이후 위와 같이 Compute Node에서 br-ex를 통해 Internet으로 정상적으로 라우팅 되는 모습을 볼 수 있다.
enp1s0과 br-ex의 MAC 주소가 동일한 이유는 enp1s0이 Open vSwitch(OVS)의 브릿지 br-ex에 추가된 포트로 설정되었기 때문이며 Open vSwitch에서 브릿지와 포트는 기본적으로 동일한 MAC 주소를 공유한다.
따라서 현 구성에서는 중복된 MAC 주소는 OVS의 정상 동작 방식으로 판단하여도 된다.
하지만 이러한 방식으로는 KVM을 재부팅 할 때마다 수동으로 DHCP 요청을 매번 보내주어야 하는 번거로움이 있었다.
$ vim /etc/systemd/system/dhclient-br-ex.service
[Unit]
Description=DHCP Client for br-ex interface
After=openvswitch.service NetworkManager.service
Requires=openvswitch.service NetworkManager.service
Before=network.target
[Service]
Type=oneshot
ExecStart=/sbin/dhclient -v br-ex
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable dhclient-br-ex.service
따라서 KVM이 재부팅 될때마다 dhclient 실행해줄 수 있는 데몬을 생성하였다.
또한 dhclient-br-ex.service unit 파일의 [Unit] 섹션에 Open vSwitch와 NetworkManager 서비스에 대한 의존성을 추가하여, 이들 서비스가 시작된 후에 dhclient를 실행하도록 설정하였다.
[ OK ] Started Network Manager.
Starting Network Manager Wait Online...
Starting DHCP Client for br-ex interface...
[ OK ] Started Network Manager Script Dispatcher Service.
[FAILED] Failed to start Network Manager Wait Online.
See 'systemctl status NetworkManager-wait-online.service' for details.
[ OK ] Finished DHCP Client for br-ex interface.
하지만 위와 같이 NetworkManager-wait-online.service를 대기하여 부팅 시간이 비정상적으로 길어지고, 최종적으로 해당 데몬의 실행은 불가한 문제를 발하였다.
NetworkManager-wait-online.service는 NetworkManager가 관리하는 네트워크가 "온라인" 상태가 될 때까지 시스템 부팅이나 특정 서비스의 시작을 지연시키는 역할을 한다.
설정된 타임아웃 내에 네트워크 연결이 완전히 수립되지 않으면, 서비스는 실패로 처리되고 이후의 네트워크 의존 서비스들은 별도로 처리된다.
sudo systemctl disable NetworkManager-wait-online.service
sudo systemctl mask NetworkManager-wait-online.service
따라서 클라우드 환경이나, Open vSwitch, NetworkManager 등 다른 네트워크 관리 도구로 네트워크 인터페이스를 별도로 제어하는 경우에는 이 서비스가 불필요하거나 오히려 부팅 지연 및 오류를 유발할 수 있기 때문에 굳이 이 서비스를 활성화할 필요가 없으므로 비활성화하여 부팅 속도를 개선할 수 있었다.
또한 마스킹을 통해 다른 서비스가 의존성을 통해 실행하려 할 때도 해당 서비스가 실행되지 않도록 하였다.
$ ip route
192.168.100.0/24 dev br-ex proto kernel scope link src 192.168.100.102
이후 라우팅 테이블에서 192.168.100.1로의 Default 라우트가 누락되어 있는 것을 발견하여
$ vim /etc/systemd/system/dhclient-br-ex.service
[Unit]
Description=DHCP Client for br-ex interface
After=openvswitch.service NetworkManager.service
Requires=openvswitch.service NetworkManager.service
Before=network.target
[Service]
Type=oneshot
ExecStart=/sbin/dhclient -v br-ex
ExecStartPost=/bin/sh -c 'if ! ip route | grep -q "^default"; then ip route add default via 192.168.100.1 dev br-ex; fi'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
데몬의 ExecStartPost: dhclient을 통해 실행이 끝난 후, ip route 명령으로 기본 경로가 없는 경우에만 ip route add 명령으로 default gateway를 추가하도록 변경하여 이를 수정하였다.
Controller Node의 경우 IP 주소만 192.168.100.101로 변경하여 Compute Node와 동일하게 구성하였다.
[ OK ] Started Network Manager Script Dispatcher Service.
[FAILED] Failed to start DHCP Client for br-ex interface.
See 'systemctl status dhclient-br-ex.service' for details.
하지만 위와 같이 정상적으로 네트워크를 설정한 이후 Host PC를 리부트하고 난 뒤 위와 같이 부팅 중 dhclient-br-ex.service가 실패하는 모습을 볼 수 있었다
$ dhclient -v br-ex
...
DHCPDISCOVER on br-ex to 255.255.255.255 port 67 interval 9 (xid=0x51ba5b6f)
No DHCPOFFERS received.
No working leases in persistent database - sleeping.
따라서 위와 같이 수동으로 다시 dhclient를 작동시켰을 때 No working leases in persistent database와 같은 오류 메세지를 볼 수 있었다.
### COMPUTE
$ sudo ovs-vsctl get Bridge br-ex fail_mode
secure
우선 OVS의 br-ex의 fail_mode를 점검하였다.
OVS에서 br-ex → enp1s0로 트래픽이 이동하려면, br-ex의 fail_mode가 STP를 활성화하지 않고 정상적인 포워딩 상태여야 한다
위와 같이 결과가 "secure"이면, OVS가 자동으로 트래픽을 차단하는 상태이다
sudo ovs-vsctl set Bridge br-ex fail_mode=standalone
따라서 fail_mode를 standalone로 변경해주었다
fail_mode=secure
OVS OpenFlow Controller가 반드시 있어야 네트워크 트래픽을 처리할 수 있음OVS는 모든 패킷을 block 함OpenFlow 컨트롤러(ex. OpenDaylight, Ryu, ONOS)를 이용해 정밀한 트래픽 제어를 할 때나 컨트롤러를 통한 네트워크 정책 제어가 필수적일 때 해당 모드를 사용한다fail_mode=standalone
OpenFlow Controller 없이도 OVS 브리지가 일반적인 L2 스위치처럼 동작OVS OpenFlow Controller가 있더라도 컨트롤러 연결이 끊어져도 계속 작동STP(Spanning Tree Protocol) 같은 네트워크 제어 기능을 사용할 수 있음### HOST
ps aux | grep dnsmasq
sudo systemctl restart libvirtd
또한 dnsmasq가 실행 중인지 확인하고
### HOST
ps aux | grep dnsmasq
sudo systemctl restart libvirtd
DHCP 포트(67) Listen 여부를 확인해주었다
### HOST
$ virsh domiflist compute
Interface Type Source Model MAC
-------------------------------------------------------------
vnet0 network opstnat virtio 6a:66:48:b9:1f:43
이후 compute의 인터페이스로 vnet0가 붙어있음을 확인하여주고
sudo tcpdump -i vnet0 port 67 or port 68 -n
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on vnet0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
03:13:54.962220 IP 0.0.0.0.bootpc > 255.255.255.255.bootps: BOOTP/DHCP, Request from 6a:66:48:b9:1f:43, length 300
...
$ sudo tcpdump -i virbr10 port 67 or port 68 -n
tcpdump를 통해 패킷을 검사해보았다.
이때, vnet0으로는 패킷이 정상적으로 도달하지만 KVM의 NAT 네트워크인 opstnat의 virbr10로는 패킷이 도달하지 않음을 확인하고
$ sudo brctl show
bridge name bridge id STP enabled interfaces
virbr0 8000.525400330d65 yes
virbr10 8000.5254008c9b3c no
brctl을 통해 virbr10 브리지가 존재하지만, 인터페이스 목록이 없음, 즉 vnet0이 연결되지 않음을 확인하였다.
vnet0이 없으면 VM의 네트워크 패킷이 브리지를 통해 전달되지 않는다
$ sudo brctl addif virbr10 vnet0
$ sudo brctl addif virbr10 vnet1
$ sudo brctl show
bridge name bridge id STP enabled interfaces
virbr0 8000.525400330d65 yes
virbr10 8000.5254008c9b3c no vnet0
vnet1
따라서 vnet0,vnet1을 virbr10에 수동으로 추가하고
cat /var/lib/libvirt/dnsmasq/opstnat.hostsfile
36:4c:6a:07:2e:4d,192.168.100.101,controller
6a:66:48:b9:1f:43,192.168.100.102,compute
호스트에서 현재 DHCP Lease 확인해준 후
rm /var/lib/libvirt/dnsmasq/opstnat.hostsfile
sudo systemctl restart libvirtd
sudo systemctl restart dnsmasq
DHCP Lease 파일을 제거하여 DHCP 서버를 초기화하고, 새로운 요청을 받을 준비를 마쳤다
### COMPUTE
### CONTROLLER
sudo dhclient -r br-ex
sudo dhclient -v br-ex
이후 위와 같이 VM 내에서 -r 옵션을 통해 dhclient를 reload해주고 다시 DHCP 요청을 보내
$ sudo tcpdump -i virbr10 port 67 or port 68 -n
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on virbr10, link-type EN10MB (Ethernet), snapshot length 262144 bytes
03:23:22.715149 IP 0.0.0.0.bootpc > 255.255.255.255.bootps: BOOTP/DHCP, Request from 6a:66:48:b9:1f:43, length 300
03:23:22.715719 IP 192.168.100.1.bootps > 192.168.100.102.bootpc: BOOTP/DHCP, Reply, length 301
DHCP 요청 패킷이 virbr10까지 정상적으로 도달하는 것을 확인하였고, br-ex의 IP 또한 이전과 동일하게 부여받은 이후 Openstack 서비스들의 동작을 확인할 수 있었다.
br-ex -> enp1s0 -> vnet0(VM이 연결된 인터페이스) -> virbr10 -> opstnat
Compute VM에서 DHCP 요청을 보내는 경로는 위와 같다
일반적인 Multi Compute Node Self Service Network 시나리오는 위와 같이 구성된다.
해당 시나리오의 Open vSwitch(OVS) 네트워크는 OpenStack Neutron에서 흔히 사용되는 구조이며, 세 개의 브릿지(br-ex, br-int, br-tun)와 여러 개의 포트가 연결되어 있다.
| 포트명 | 풀네임 | 소속 브릿지 | 역할 |
|---|---|---|---|
| enp1s0 | Physical Interface 1 | br-ex | 물리 인터페이스, 외부 네트워크 연결 |
| br-ex | External Bridge | br-ex | 외부 네트워크 브릿지 |
| phy-br-ex | Physical Bridge - External | br-ex | br-int와 br-ex를 연결하는 패치 포트 |
| int-br-ex | Internal Bridge - External | br-int | br-int와 br-ex를 연결하는 패치 포트 |
| br-int | Integration Bridge | br-int | 내부 가상 네트워크 스위칭 |
| patch-tun | Patch Tunnel Port | br-int | br-int와 br-tun을 연결하는 패치 포트 |
| patch-int | Patch Integration Port | br-tun | br-int와 br-tun을 연결하는 패치 포트 |
| br-tun | Tunnel Bridge | br-tun | VXLAN/GRE 터널을 통해 Compute Node 간 가상 네트워크 연결 |
br-ex (External Bridge)주요 포트
enp1s0 (물리 네트워크 인터페이스) phy-br-ex (br-ex ↔ br-int 연결을 위한 패치 포트)int-br-ex와 연결되는 패치 포트br-ex와 br-int를 연결하여 내부 네트워크가 외부 네트워크로 나갈 수 있도록 함연결 관계
br-ex <-> enp1s0 (물리 네트워크 연결)
br-ex <-> phy-br-ex <-> int-br-ex <-> br-int
enp1s0는 물리 네트워크와 연결되어 외부와 통신phy-br-ex를 통해 br-int와 연결되어 내부 네트워크의 VM이 외부 네트워크와 통신 가능br-int (Integration Bridge)주요 포트
int-br-ex (br-int ↔ br-ex 연결을 위한 패치 포트)phy-br-ex와 연결되어 내부 네트워크의 트래픽이 외부 네트워크로 나가도록 함patch-tun (br-int ↔ br-tun 연결을 위한 패치 포트)patch-int와 연결되어 오버레이 네트워크(VXLAN/GRE)와 통신연결 관계
br-int <-> int-br-ex <-> phy-br-ex <-> br-ex
br-int <-> patch-tun <-> patch-int <-> br-tun
br-int는 VM들이 직접 연결되는 주요 브릿지int-br-ex를 통해 br-ex와 연결되어 외부 네트워크와 통신patch-tun을 통해 br-tun과 연결되어 다른 Compute Node의 VM과 통신br-tun (Tunnel Bridge)주요 포트
patch-int (br-tun ↔ br-int 연결을 위한 패치 포트)patch-tun과 연결되어 VXLAN/GRE 터널을 통해 Compute Node 간 가상 네트워크 트래픽을 전달연결 관계
br-tun <-> patch-int <-> patch-tun <-> br-int
br-tun은 오버레이 네트워크(VXLAN/GRE)를 관리하는 브릿지patch-int를 통해 br-int와 연결되어 VM 간 통신을 담당VM은 Tap 인터페이스(TAP Device) 를 통해 OVS 브릿지(br-int)와 연결된다br-int와 연결되는 과정인스턴스 생성 시 Tap 인터페이스 생성
libvirt가 Tap 인터페이스(TAP Device) 를 생성qemu-kvm에 의해 VM의 가상 네트워크 인터페이스(VNIC) 로 인식Tap 인터페이스를 br-int에 연결
ovs-vsctl을 사용하여 Tap 인터페이스를 br-int의 포트로 추가tap12345678-xx라는 Tap 인터페이스가 VM과 연결됨$ ovs-vsctl show
...
Bridge br-int
Port tap12345678-xx
Interface tap12345678-xxNeutron이 OVS 플로우 테이블을 설정
OpenStack Neutron은 SDN 컨트롤러 역할을 하며, br-int의 OpenFlow 룰을 설정함br-int)br-int에서 직접 처리됨Instance1 -> br-int -> Instance2
br-tun)br-int에서 patch-tun을 통해 br-tun으로 패킷 전송br-tun에서 VXLAN/GRE 캡슐화를 수행하여 다른 Compute Node로 패킷 전달br-tun이 패킷을 수신patch-int를 통해 br-int로 전달br-int에서 대상 VM에게 패킷 전달Instance1 -> br-int -> patch-tun -> patch-int -> br-tun (Compute Node 1)->
-> VXLAN/GRE 터널 ->
-> br-tun(Compute Node 2) -> patch-int -> patch-tun -> br-int -> Instance4
br-ex)br-int로 패킷 전송br-int에서 int-br-ex를 통해 br-ex로 전달br-ex에서 enp1s0(물리 인터페이스)로 패킷 전달enp1s0를 통해 외부 네트워크로 송출Instance1 -> br-int -> int-br-ex -> phy-br-ex -> br-ex -> enp1s0 -> 외부 네트워크
OpenStack 서비스와 OVS는 직접적인 관계가 없다. OVS는 네트워크 패킷을 전달하는 역할을 하고, OpenStack 서비스들은 API와 데이터 통신을 위해 br-ex 또는 다른 내부 네트워크를 사용한다controller 노드에서 실행되며, IP(192.168.100.101)를 기반으로 API 통신을 수행br-ex의 IP(192.168.100.101)로 직접 API 통신을 수행nova-api → keystone 인증 요청 (http://192.168.100.101:5000)neutron-server → rabbitmq 메시지 전달 (amqp://192.168.100.101:5672)glance-api → keystone 인증 요청 (http://192.168.100.101:5000)OpenStack Neutron는 OVS를 사용하여 가상 네트워크를 관리
OpenStack Neutron은 OVS 브릿지(br-int, br-tun, br-ex)를 사용하여 VM의 네트워크 트래픽을 처리
br-intbr-tunbr-ex가상 머신(VM)이 외부 네트워크(192.168.100.0/24)로 나갈 때
br-int에서 br-ex로 전달br-ex는 물리 인터페이스(enp1s0)를 통해 외부 네트워크로 송출Floating IP 설정 시
Floating IP(공인 IP)를 사용br-ex는 외부 네트워크를 담당하고 있으므로, Floating IP는 br-ex를 통해 외부 네트워크와 연결됨br-ex → 외부 네트워크와 연결 (물리 인터페이스)br-int → 가상 머신 간 통신 및 L2 네트워크 스위칭br-tun → Compute Node 간 VXLAN/GRE 터널링phy-br-ex, int-br-ex, patch-tun, patch-int) 를 통해 각 브릿지가 서로 연결| 항목 | OVS 사용 여부 | 설명 |
|---|---|---|
| OpenStack API 통신 | ❌ | OpenStack 서비스끼리 API 통신을 수행 (192.168.100.101) |
| VM 간 내부 통신 | ✅ | br-int를 통해 같은 Compute Node 내 VM 간 통신 |
| VM 간 다른 Compute Node 통신 | ✅ | VXLAN/GRE 터널(br-tun)을 통해 전달 |
| VM이 외부 네트워크 통신 | ✅ | br-ex를 통해 외부 네트워크로 송출 |

“Self-Service”라는 용어는 테넌트(사용자)가 별도의 물리적 네트워크 구성이나 관리자 개입 없이, 오버레이 기술을 통해 가상 네트워크를 생성하고 관리할 수 있음을 의미테넌트 자율성: 사용자가 네트워크, 서브넷, 라우터 등 네트워크 리소스를 직접 생성·관리할 수 있다추상화: 복잡한 터널링과 오버레이 구성은 Controller가 담당하므로, Compute Node에서는 단순 연결(포트 연결)만 처리하게 되어 관리 부담이 줄어든다br-ex → 외부 네트워크 연결 담당192.168.100.101 할당됨 (외부 네트워크에서 접근 가능한 IP)enp1s0(물리 인터페이스)를 통해 외부 네트워크와 연결됨controller)현 시나리오에서는 동일한 IP/인터페이스로 Management와 Overlay 트래픽을 같이 처리
하지만 Production 환경에서는 트래픽 분산 및 보안 등을 위해 별도 NIC를 사용하는 것이 일반적인 권장사항
따라서 추후 Management와 Overlay 트래픽을 분리할 예정
컨트롤러 노드는 Neutron 서버와 ML2 플러그인을 통해 오버레이 네트워크(예: VXLAN 또는 GRE 터널)를 생성하고 관리
### CONTROLLER
$ ovs-vsctl show
e066f1d7-b888-4e29-8cb6-484dc0a0c83d
Manager "ptcp:6640:127.0.0.1"
is_connected: true
Bridge br-ex
Controller "tcp:127.0.0.1:6633"
is_connected: true
fail_mode: secure
datapath_type: system
Port br-ex
Interface br-ex
type: internal
Port enp1s0
Interface enp1s0
Port phy-br-ex
Interface phy-br-ex
type: patch
options: {peer=int-br-ex}
Bridge br-int
Controller "tcp:127.0.0.1:6633"
is_connected: true
fail_mode: secure
datapath_type: system
Port br-int
Interface br-int
type: internal
Port int-br-ex
Interface int-br-ex
type: patch
options: {peer=phy-br-ex}
Port patch-tun
Interface patch-tun
type: patch
options: {peer=patch-int}
Bridge br-tun
Controller "tcp:127.0.0.1:6633"
is_connected: true
fail_mode: secure
datapath_type: system
Port br-tun
Interface br-tun
type: internal
Port patch-int
Interface patch-int
type: patch
options: {peer=patch-tun}
ovs_version: "3.1.3"
Self-service 네트워크 시나리오에서는 Compute Node에 ML2 플러그인을 별도로 설정할 필요가 없으며, Neutron 에이전트(neutron-openvswitch-agent)를 통해 인스턴스의 네트워크 인터페이스를 br-ex와 br-int에만 연결
### COMPUTE
$ ovs-vsctl show
85ccefa0-dd2d-4696-8ffe-bed0ece90fa1
Manager "ptcp:6640:127.0.0.1"
is_connected: true
Bridge br-ex
Controller "tcp:127.0.0.1:6633"
is_connected: true
fail_mode: secure
datapath_type: system
Port br-ex
Interface br-ex
type: internal
Port enp1s0
Interface enp1s0
Port phy-br-ex
Interface phy-br-ex
type: patch
options: {peer=int-br-ex}
Bridge br-int
Controller "tcp:127.0.0.1:6633"
is_connected: true
fail_mode: secure
datapath_type: system
Port int-br-ex
Interface int-br-ex
type: patch
options: {peer=phy-br-ex}
Port br-int
Interface br-int
type: internal
ovs_version: "3.1.3"
결론적으로, 현재 시나리오인 Compute Node에 ML2 설정을 하지 않고 br-ex, br-int만 사용하는 구성은 Self-Service Network의 기본 취지, 즉 테넌트가 오버레이 기반의 가상 네트워크를 직접 사용할 수 있도록 하면서도 인프라의 복잡한 설정은 중앙(Controller)에서 처리하도록 하는 설계와 부합한다.
또한 단일 Compute Node에서는 인스턴스 간 트래픽이 모두 로컬 br-int에서 처리되므로 터널 브리지(br-tun)가 필요 없다

openstack network create ext-net \
--external \
--provider-physical-network provider \
--provider-network-type flat
--external 옵션을 사용하여 외부 네트워크임을 지정합니다.
--provider-physical-network provider와 --provider-network-type flat 옵션은 Neutron의 물리 네트워크(br-ex와 enp1s0 매핑) 설정에 맞춰 조정합니다.
openstack network create private-net
인스턴스가 속하게 될 내부 네트워크를 생성합니다.
openstack subnet create private-subnet \
--network private-net \
--subnet-range 10.0.0.0/24 \
--allocation-pool start=10.0.0.10,end=10.0.0.200 \
--gateway 10.0.0.1
--subnet-range는 tenant 네트워크의 IP 범위를 정의합니다.
--allocation-pool은 인스턴스에 할당 가능한 IP 범위입니다.
--gateway는 서브넷의 기본 게이트웨이로, 나중에 라우터와 연결할 때 사용됩니다.
openstack router create router1
tenant 네트워크와 외부 네트워크 간 라우팅을 위해 라우터를 생성합니다.
openstack router set router1 --external-gateway ext-net
라우터에 외부 네트워크(ext-net)를 연결하여, 내부 인스턴스가 외부로 나갈 수 있는 경로를 제공합니다.
openstack router add subnet router1 private-subnet
tenant 네트워크의 서브넷(private-subnet)을 라우터에 연결하여, 네트워크 간 트래픽이 라우팅되도록 합니다.
$ ovs-vsctl show
...
Bridge br-int
Controller "tcp:127.0.0.1:6633"
is_connected: true
fail_mode: secure
datapath_type: system
Port br-int
Interface br-int
type: internal
Port int-br-ex
Interface int-br-ex
type: patch
options: {peer=phy-br-ex}
Port qg-1bbdb6e1-37
tag: 2
Interface qg-1bbdb6e1-37
type: internal
Port tapa182dc24-7a
tag: 1
Interface tapa182dc24-7a
type: internal
Port patch-tun
Interface patch-tun
type: patch
options: {peer=patch-int}
Port qr-b69bdbee-ee
tag: 1
Interface qr-b69bdbee-ee
type: internal
Port qg-1bbdb6e1-37
tag: 2 → VLAN 태그 2번을 사용 type: internal → 내부 가상 인터페이스 qg-로 시작하는 이름 → "qg"는 Quantum Gateway (현재 Neutron Gateway) 포트를 의미 Port tapa182dc24-7a
tag: 1 → VLAN 태그 1번을 사용 type: internal → 내부 가상 인터페이스 tap-로 시작하는 포트는 VM(가상 머신)과 연결된 인터페이스 tag: 1이므로 네트워크 세그먼트 1에 속한 네트워크 Port qr-b69bdbee-ee
tag: 1 → VLAN 태그 1번을 사용 type: internal → 내부 가상 인터페이스 qr-로 시작하는 포트는 Neutron Router의 내부 인터페이스 (Quantum Router)tag: 1이므로 VLAN 세그먼트 1과 연결됨 # t2.nano (1 vCPU, 0.5GB RAM)
openstack flavor create t2.nano \
--vcpus 1 \
--ram 512 \
--disk 8 \
--swap 0 \
--ephemeral 0 \
--public
# t2.micro (1 vCPU, 1GB RAM)
openstack flavor create t2.micro \
--vcpus 1 \
--ram 1024 \
--disk 8 \
--swap 0 \
--ephemeral 0 \
--public
# t2.small (1 vCPU, 2GB RAM)
openstack flavor create t2.small \
--vcpus 1 \
--ram 2048 \
--disk 20 \
--swap 0 \
--ephemeral 0 \
--public
# t2.medium (2 vCPUs, 4GB RAM)
openstack flavor create t2.medium \
--vcpus 2 \
--ram 4096 \
--disk 40 \
--swap 0 \
--ephemeral 0 \
--public
# t2.large (2 vCPUs, 8GB RAM)
openstack flavor create t2.large \
--vcpus 2 \
--ram 8192 \
--disk 40 \
--swap 0 \
--ephemeral 0 \
--public
# t2.xlarge (4 vCPUs, 16GB RAM)
openstack flavor create t2.xlarge \
--vcpus 4 \
--ram 16384 \
--disk 80 \
--swap 0 \
--ephemeral 0 \
--public
# t2.2xlarge (8 vCPUs, 32GB RAM)
openstack flavor create t2.2xlarge \
--vcpus 8 \
--ram 32768 \
--disk 160 \
--swap 0 \
--ephemeral 0 \
--public
$ openstack flavor list
+--------------------------------------+------------+-------+------+-----------+-------+-----------+
| ID | Name | RAM | Disk | Ephemeral | VCPUs | Is Public |
+--------------------------------------+------------+-------+------+-----------+-------+-----------+
| 050dd2b1-4d42-4a77-9e23-b56e59b83f94 | t2.medium | 4096 | 40 | 0 | 2 | True |
| 1f3d5bab-46f7-4b37-b63e-96c8709988d8 | t2.nano | 512 | 8 | 0 | 1 | True |
| 6d40e04f-2897-4966-9b27-b5866d6a9d2c | t2.small | 2048 | 20 | 0 | 1 | True |
| b4f7f1cc-c987-4168-ada7-a40392c5209e | t2.xlarge | 16384 | 80 | 0 | 4 | True |
| de3a8ace-937c-4c26-bcd0-4dbc4a9f7550 | t2.2xlarge | 32768 | 160 | 0 | 8 | True |
| fbf44a9d-0e44-4098-8ea1-c2abacad93bc | t2.large | 8192 | 40 | 0 | 2 | True |
| ff81773d-27ad-490d-976f-fb9f58caf697 | t2.micro | 1024 | 8 | 0 | 1 | True |
+--------------------------------------+------------+-------+------+-----------+-------+-----------+
이후 인스턴스를 생성해주기 위해 위와 같이 flavor를 생성한다.
| Flavor Name | vCPUs | RAM | Root Disk | Swap | Ephemeral Disk |
|---|---|---|---|---|---|
t2.nano | 1 | 512MB | 8GB | 0GB | 0GB |
t2.micro | 1 | 1GB | 8GB | 0GB | 0GB |
t2.small | 1 | 2GB | 20GB | 0GB | 0GB |
t2.medium | 2 | 4GB | 40GB | 0GB | 0GB |
t2.large | 2 | 8GB | 40GB | 0GB | 0GB |
t2.xlarge | 4 | 16GB | 80GB | 0GB | 0GB |
t2.2xlarge | 8 | 32GB | 160GB | 0GB | 0GB |
이때, AWS의 flavor의 스펙에 맞게 생성해주었다.
또한 현재 AWS에서 배포되고 있는 서버는 t2.micro임을 확인하였다.
| OS | 크기 | 장점 | 비고 |
|---|---|---|---|
| Alpine Linux | ~5MB | 초경량, 빠른 부팅, 최소 패키지 | 기본적으로 glibc 미포함 (musl libc 사용) |
| Ubuntu Minimal (20.04/22.04 LTS) | ~29MB | 안정적, LTS 지원, 보안 업데이트 우수 | 표준 Ubuntu보다 가벼움 |
| Debian Slim (11/12) | ~22MB | 안정적, 패키지 호환성 우수 | 보안 패치가 빠름 |
| Rocky Linux Minimal (9.x) | ~50MB | RHEL 계열, CentOS 대체, 안정적 | CentOS 계열 선호 시 선택 가능 |
이후 인스턴스 생성을 위한 이미지를 선택하기 위해 위와 같이 사용이 가능한 이미지를 비교하였다.
wget https://download.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud.latest.x86_64.qcow2 -O rocky-linux-9.qcow2
현재 Rocky Linux로 Host와 KVM을 구동하고 있는 만큼 물리 서버 설치용 ISO 이미지와 다르게, 클라우드 플랫폼에서 VM을 쉽게 배포할 수 있도록 사전 설정된 Rocky Linux Generic Cloud 이미지를 사용하도록 하였따.
Generic Cloud 이미지Pre-installed 클라우드 에이전트cloud-init이 기본 포함$ ls -alh
-rw-r--r-- 1 root root 582M Nov 18 22:53 rocky-linux-9.qcow2
우선 위와 같이 wget을 통해 이미지를 다운로드 받고
openstack image create "Rocky Linux 9 Generic Cloud" \
--file /root/images/rocky-linux-9.qcow2 \
--disk-format qcow2 \
--container-format bare \
--public \
--property os_type=linux \
--property architecture=x86_64 \
--property hypervisor_type=kvm
이미지를 생성하였따.
HttpException: 500: Server Error for url: http://controller:9292/v2/images, The server has either erred or is incapable of performing the requested operation.: 500 Internal Server Error
하지만 이때 위와 같은 오류가 발생하였다.
$ journalctl -u openstack-glance-api.service --no-pager | tail -n 20
...
Feb 11 08:06:52 controller glance-api[27776]: 2025-02-11 08:06:52.818 27776 ERROR oslo.limit.limit [None req-1cda880a-e48e-4748-b340-c32d8e20fde5 12529d0a6bfa4cb1894172f0fe354de9 0636a6ee7b2242e7ba9faf5e7d1daed4 - - default default] Unable to initialize OpenStackSDK session: An auth plugin is required to determine endpoint URL: keystoneauth1.exceptions.auth_plugins.MissingAuthPlugin: An auth plugin is required to determine endpoint URL
Feb 11 08:06:52 controller glance-api[27776]: 2025-02-11 08:06:52.819 27776 ERROR glance.common.wsgi [None req-1cda880a-e48e-4748-b340-c32d8e20fde5 12529d0a6bfa4cb1894172f0fe354de9 0636a6ee7b2242e7ba9faf5e7d1daed4 - - default default] Caught error: Can't initialise OpenStackSDK session: An auth plugin is required to determine endpoint URL.: oslo_limit.exception.SessionInitError: Can't initialise OpenStackSDK session: An auth plugin is required to determine endpoint URL.
따라서 로그를 확인해보니 An auth plugin is required to determine endpoint URL 에러 로그를 확인할 수 있었고
$ openstack endpoint list --service glance
+----------------------------------+-----------+--------------+--------------+---------+-----------+------------------------+
| ID | Region | Service Name | Service Type | Enabled | Interface | URL
|
+----------------------------------+-----------+--------------+--------------+---------+-----------+------------------------+
| 5882de4449b24b73a2b11835df9db71d | RegionOne | glance | image | True | public | http://controller:9292 |
| bc020a0587104bff898dc0dcd41ca0ed | RegionOne | glance | image | True | admin | http://controller:9292 |
| e18153d6e3924d268112ea67d1544f42 | RegionOne | glance | image | True | internal | http://controller:9292 |
+----------------------------------+-----------+--------------+--------------+---------+-----------+------------------------+
우선적으로 glance의 엔드포인트 리스트와
$ openstack user list
+----------------------------------+-----------+
| ID | Name |
+----------------------------------+-----------+
| 12529d0a6bfa4cb1894172f0fe354de9 | admin |
| 83110e40de9742ce87f8d83f02133151 | glance |
| 597d8fb3ab914e0783bf6eaecef35963 | placement |
| bfdfdbc65be14efb9bddbafdd6c489a6 | nova |
| 0114d4f75c8d44bd8a38ec2b77377d9d | neutron |
| 1fec3efa30104587b9dbf1e4bd6c41bf | judemin |
| 8673de9e4098497eb131fa319ba733a4 | cinder |
+----------------------------------+-----------+
openstack의 user를 확인해주었다.
openstack role add --project admin --user glance admin
Authorization의 문제일 수도 있어 우선 glance user의 권한을 admin으로 변경하고
$ curl -i -X POST http://controller:5000/v3/auth/tokens -H "Content-Type: application/json" -d '{
"auth": {
"identity": {
"methods": ["password"],
"password": {
"user": {
"name": "glance",
"domain": { "id": "default" },
"password": "glance"
}
}
},
"scope": {
"project": {
"name": "admin",
"domain": { "id": "default" }
}
}
}
}'
{"token": {"methods": ["password"], "user": {"domain": {"id": "default", "name": "Default"}, ...
직접적으로 Keystone에 요청이 정상적으로 이루어지는 것을 확인한 후
$ vim /etc/glance/glance-api.conf
[oslo_limit]
auth_type = password
...
endpoint_id = 5882de4449b24b73a2b11835df9db71d
[glance_store]
filesystem_store_datadir = /var/lib/glance/images/
[keystone_authtoken]
service_token_roles_required = True
glance-api.conf에서 아래 두 가지 설정을 수정해주었다.
/images -> /images/auth_type = password이는 oslo_limit 섹션이 Keystone 인증 정보를 제대로 찾지 못해 발생하는 전형적인 문제인 것을 확인할 수 있었고
systemctl restart openstack-glance-api.service
Glance 데몬을 재시작하여 해당 문제를 해결할 수 있었다.
HttpException: 413: Client Error for url: http://controller:9292/v2/images, The request returned a 413 Request Entity Too Large. This generally means that rate limiting or a quota threshold was breached.: The response body:: 413 Request Entity Too Large: Project 0636a6ee7b2242e7ba9faf5e7d1daed4 is over a limit for [Resource image_count_total is over limit of 0 due to current usage 0 and delta 1]
하지만 이후 image_count_total 관련 Quota 오류가 발생하여
$ openstack quota show admin
+-----------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Field | Value
|
+-----------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| backup-gigabytes | 1000
|
| backups | 10
|
| cores | 20
|
| fixed-ips | -1
|
| floating-ips | 50
|
| gigabytes | 1000
|
| gigabytes___DEFAULT__ | -1
|
| groups | 10
|
| injected-file-size | 10240
|
| injected-files | 5
|
| injected-path-size | 255
|
| instances | 10
|
| key-pairs | 100
|
| location | Munch({'cloud': '', 'region_name': '', 'zone': None, 'project': Munch({'id': '0636a6ee7b2242e7ba9faf5e7d1daed4', 'name': 'admin', 'domain_id': None, 'domain_name': 'Default'})}) |
| networks | 100
|
| per-volume-gigabytes | -1
|
| ports | 500
|
| project | 0636a6ee7b2242e7ba9faf5e7d1daed4
|
| project_name | admin
|
| properties | 128
|
| ram | 51200
|
| rbac_policies | 10
|
| routers | 10
|
| secgroup-rules | 100
|
| secgroups | 10
|
| server-group-members | 10
|
| server-groups | 10
|
| snapshots | 10
|
| snapshots___DEFAULT__ | -1
|
| subnet_pools | -1
|
| subnets | 100
|
| volumes | 10
|
| volumes___DEFAULT__ | -1
|
+-----------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
quota를 확인해주었지만 현재 image_count_total 관련 설정이 존재하지 않음을 확인하였다.
openstack registered limit create \
--service glance --default-limit 10 --region RegionOne image_count_total
이후 image_count_total 에 대한 제한 등록 아래 레퍼런스에 근거하여 시도하였는데
$ openstack registered limit create \
--service glance --default-limit 10 --region RegionOne image_count_total
You are not authorized to perform the requested action: identity:create_registered_limits. (HTTP 403) (Request-ID: req-93452b92-8c23-4cf2-b803-24d185f44c88)
현재 ./admin_rc를 통해 admin user로 해당 명령을 실행했음에도 You are not authorized와 같은 오류가 발생하여
$ cat /etc/keystone/policy.json
{}
$ vim /etc/keystone/policy.yaml
identity:create_registered_limits: "role:admin"
$ systemctl restart httpd
/etc/keystone/policy.json 설정 파일과 policy.yaml 파일이 비어 있는 것을 확인하고 수동으로 create_registered_limits을 추가할 수 있는 권한 정책을 추가하였다.
$ openstack registered limit create \
--service glance --default-limit 10 --region RegionOne image_count_total
+---------------+----------------------------------+
| Field | Value |
+---------------+----------------------------------+
| default_limit | 10 |
| description | None |
| id | 243bbd072193480cb092c80e930857a7 |
| region_id | RegionOne |
| resource_name | image_count_total |
| service_id | 6048f1fd11cb4dcda5de41d1251a0296 |
+---------------+----------------------------------+
이후 registered limit이 정상적으로 수행되는 것을 확인하고
$ vim /etc/keystone/policy.yaml
identity:create_registered_limits: "role:admin"
identity:get_registered_limits: "role:admin"
identity:update_registered_limits: "role:admin"
identity:delete_registered_limits: "role:admin"
/etc/keystone/policy.yaml에 이미지를 생성하기 위해 필요한 다른 정책들을 추가한 후
openstack registered limit create \
--service glance --default-limit 10000 --region RegionOne image_size_total
업로드할 수 있는 최대 이미지 크기 설정 (10GB, 1000MB)
openstack registered limit create \
--service glance --default-limit 10 --region RegionOne image_count_uploading
동시에 업로드 가능한 이미지 개수 증가
openstack registered limit create \
--service glance --default-limit 10000 --region RegionOne image_stage_total
Staging 영역(임시 저장 공간) 크기 설정 (10GB, 1000MB)
위와 같이 registered limit를 생성해준 후
systemctl restart openstack-glance-api
이미지 데몬을 재시작한 후
openstack image list
+--------------------------------------+-----------------------------+--------+
| ID | Name | Status |
+--------------------------------------+-----------------------------+--------+
| 73c6e7ee-3b6c-40dd-bdc5-96058e183faa | Rocky Linux 9 Generic Cloud | active |
+--------------------------------------+-----------------------------+--------+
이미지 생성을 정상적으로 완료할 수 있었다.
openstack keypair create mykey > mykey.pem
chmod 600 mykey.pem
우선 인스턴스를 생성하기 위해 openstack keypair create mykey 명령어를 통해 SSH key pair를 생성하였다.
생성된 프라이빗 키는 mykey.pem 파일에 저장되었으며, chmod 600 명령어로 적절한 권한 설정을 진행하였다.
ls -l
...
-rw------- 1 root root 1676 Feb 13 05:38 mykey.pem
이후 해당 키에 대한 권한을 확인해준 후
openstack security group create test_sg --description "Test security group allowing port 8888"
테스트용 Security Group을 생성하였다.
openstack security group rule create --proto tcp --dst-port 22 --remote-ip 0.0.0.0/0 test_sg
openstack security group rule create --proto tcp --dst-port 80 --remote-ip 0.0.0.0/0 test_sg
openstack security group rule create --proto tcp --dst-port 8888 --remote-ip 0.0.0.0/0 test_sg
해당 Security Group은
TCP 프로토콜에 대해 포트 22, 80, 8888로의 외부 접근을 허용하고 --remote-ip 0.0.0.0/0 옵션을 통해 모든 IP에서의 접근을 허용하도록 하였다.
openstack server create \
--flavor t2.micro \
--image "Rocky Linux 9 Generic Cloud" \
--nic net-id=$(openstack network list --name private-net -f value -c ID) \
--security-group test_sg \
--key-name mykey \
test-instance
또한 --nic net-id=... 옵션으로 생성한 내부 네트워크(private-net)가 인스턴스의 NIC에 연결될 수 있도록 이를 생성하였다,
Unexpected API Error. Please report this at http://bugs.launchpad.net/nova/ and attach the Nova API log if possible.
<class 'keystoneauth1.exceptions.discovery.DiscoveryFailure'> (HTTP 500) (Request-ID: req-dc5133ac-7750-4263-b426-d8df9cc0577e)
하지만 위와 같은 오류가 발생하여 추가적으로 트러블 슈팅을 진행하였다.
$ journalctl -u openstack-nova-api --no-pager | tail -n 50
...
Feb 13 05:42:11 controller nova-api[1761]: 2025-02-13 05:42:11.751 1761 ERROR nova.api.openstack.wsgi keystoneauth1.exceptions.discovery.DiscoveryFailure: Could not find versioned identity endpoints when attempting to authenticate. Please check that your auth_url is correct. Unable to establish connection to https://controller/identity: HTTPSConnectionPool(host='controller', port=443): Max retries exceeded with url: /identity (Caused by NewConnectionError('<urllib3.connection.HTTPSConnection object at 0x7ff1e73edaf0>: Failed to establish a new connection: [Errno 111] ECONNREFUSED'))
Feb 13 05:42:11 controller nova-api[1761]: 2025-02-13 05:42:11.751 1761 ERROR nova.api.openstack.wsgi
Feb 13 05:42:11 controller nova-api[1761]: 2025-02-13 05:42:11.757 1761 INFO nova.api.openstack.wsgi [None req-ffd94076-91ed-463b-b65f-ec39b5d1ab80 12529d0a6bfa4cb1894172f0fe354de9 0636a6ee7b2242e7ba9faf5e7d1daed4 - - default default] HTTP exception thrown: Unexpected API Error. Please report this at http://bugs.launchpad.net/nova/ and attach the Nova API log if possible.
Feb 13 05:42:11 controller nova-api[1761]: <class 'keystoneauth1.exceptions.discovery.DiscoveryFailure'>
로그에서 Nova API가 Keystone에 접근할 수 없는 즉, auth_url이 https://controller/identity로 설정되어 있는데, 해당 엔드포인트에 접근할 수 없다는 오류가 발생하였다.
### CONTROLLER
### COMPUTE
$ vim /etc/nova/nova.conf
...
[service_user]
# auth_url = https://controller/identity
auth_url = http://controller:5000/identity
따라서 Controller와 Compute Node의 /etc/nova/nova.conf의 auth_url를 위와 같이 변경해주었고
openstack role add --user nova --project admin admin
openstack role add --user neutron --project admin admin
권한과 관련된 오류를 방지하기 위해 nova, neutron 사용자의 권한을 admin으로 변경하고
systemctl restart openstack-nova-api.service
데몬을 재시작해주었다.
Feb 13 06:29:09 controller nova-api[3353]: 2025-02-13 06:29:09.027 3353 ERROR nova.network.neutron [None req-3dc37ce7-d4cf-4fa2-8d06-c4d476675eca 12529d0a6bfa4cb1894172f0fe354de9 0636a6ee7b2242e7ba9faf5e7d1daed4 - - default default] Neutron client was not able to generate a valid admin token, please verify Neutron admin credential located in nova.conf: neutronclient.common.exceptions.Unauthorized: 401-{'error': {'code': 401, 'title': 'Unauthorized', 'message': 'The request you have made requires authentication.'}}
Feb 13 06:29:09 controller nova-api[3353]: 2025-02-13 06:29:09.028 3353 ERROR nova.api.openstack.wsgi [None req-3dc37ce7-d4cf-4fa2-8d06-c4d476675eca 12529d0a6bfa4cb1894172f0fe354de9 0636a6ee7b2242e7ba9faf5e7d1daed4 - - default default] Unexpected exception in API method: nova.exception.NeutronAdminCredentialConfigurationInvalid: Networking client is experiencing an unauthorized exception.
하지만 여전히 nova-api에서 Neutron API에 접근할 때 Neutron client was not able to generate a valid admin token 오류가 발생하였다.
이는 nova.conf의 [neutron] 섹션에서 잘못된 인증 정보가 설정되어있을 확률이 높아 발생한 문제라 예상하였다.
$ openstack role assignment list --user neutron --project admin --names
+-------+-----------------+-------+---------------+--------+--------+-----------+
| Role | User | Group | Project | Domain | System | Inherited |
+-------+-----------------+-------+---------------+--------+--------+-----------+
| admin | neutron@Default | | admin@Default | | | False |
+-------+-----------------+-------+---------------+--------+--------+-----------+
따라서 우선 neutron user의 권한과 존재 유무를 확인하고
openstack user set --password neutron neutron
neutron user의 비밀번호를 재설정해준 이후
$ TOKEN=$(openstack token issue -c id -f value)
$ curl -i -H "X-Auth-Token: $TOKEN" http://controller:8774/v2.1/os-networks
HTTP/1.1 500 Internal Server Error
Openstack-Api-Version: compute 2.1
X-Openstack-Nova-Api-Version: 2.1
Vary: OpenStack-API-Version
Vary: X-OpenStack-Nova-API-Version
Content-Type: application/json; charset=UTF-8
Content-Length: 231
X-Openstack-Request-Id: req-92268784-e75e-4251-b138-fa96e43ec0af
X-Compute-Request-Id: req-92268784-e75e-4251-b138-fa96e43ec0af
Date: Thu, 13 Feb 2025 11:37:42 GMT
{"computeFault": {"code": 500, "message": "Unexpected API Error. Please report this at http://bugs.launchpad.net/nova/ and attach the Nova API log if possible.\n<class 'nova.exception.NeutronAdminCredentialConfigurationInvalid'>"}}
직접적으로 Curl을 통해 요청해보았다.
하지만 동일한 오류가 발생하여 nova.conf의 [neutron] 섹션을 다시 점검하였다.
$ vim /etc/nova/nova.conf
[neutron]
# auth_url = http://controller:5000
auth_url = http://controller:5000/v3
endpoint_override = http://controller:9696
이후 auth_url에 /v3를 추가해주었으며 publicURL을 사용하고 있다면 Nova가 잘못된 URL로 호출하는 것일 수 있기 때문에 internalURL을 강제 지정하였다.
systemctl restart openstack-nova-api
systemctl restart neutron-server
이후 데몬을 재시작하였음에도
Feb 13 08:47:43 controller nova-api[13471]: 2025-02-13 08:47:43.814 13471 ERROR nova.network.neutron [None req-2158797e-50db-4d47-9b3a-78757468df2d 12529d0a6bfa4cb1894172f0fe354de9 0636a6ee7b2242e7ba9faf5e7d1daed4 - - default default] Neutron client was not able to generate a valid admin token, please verify Neutron admin credential located in nova.conf: neutronclient.common.exceptions.Unauthorized: 401-{'error': {'code': 401, 'title': 'Unauthorized', 'message': 'The request you have made requires authentication.'}}
동일한 오류가 발생하여
$ vim /etc/neutron/neutron.conf
[keystone_authtoken]
www_authenticate_uri = http://controller:5000
# auth_url = http://controller:5000
auth_url = http://controller:5000/v3
...
service_token_roles = admin
service_token_roles_required = True
/etc/neutron/neutron.conf의 auth_url를 위와 같이 수정하고 service_token_roles과 service_token_roles_required를 추가하여 문제를 해결할 수 있었다.
$ tail -n 50 /var/log/neutron/metadata-agent.log
...
2025-02-13 08:28:18.229 10728 ERROR neutron.agent.metadata.agent raise error_for_code(reply_code, reply_text,
2025-02-13 08:28:18.229 10728 ERROR neutron.agent.metadata.agent amqp.exceptions.AccessRefused: (0, 0): (403) ACCESS_REFUSED - Login was refused using authentication mechanism AMQPLAIN. For details see the broker logfile.
2025-02-13 08:28:18.229 10728 ERROR neutron.agent.metadata.agent
하지만 neutron-metadata-agent의 로그를 확인해본 결과 위와 같은 오류가 발생하였다.
rabbitmqctl list_users
Listing users ...
user tags
guest [administrator]
이에 rabbitmqctl를 통해 RabbitMQ User를 검사해보았는데, 이전에 생성한 openstack 사용자가 삭제되었음을 발견하고
rabbitmqctl add_user openstack openstack
rabbitmqctl set_permissions -p / openstack ".*" ".*" ".*"
새롭게 user를 생성하고 권한을 부여한 후
rabbitmqctl change_password openstack <설치 시 설정한 RabbitMQ 비밀번호>
해당 user의 비밀번호를 다시 설정해주었다.
systemctl restart rabbitmq-server
systemctl restart neutron-metadata-agent
이후 각 데몬들을 재시작하여
rabbitmqctl list_connections
Listing connections ...
user peer_host peer_port state
openstack 192.168.100.101 36842 running
openstack 192.168.100.102 57656 running
openstack 192.168.100.101 36848 running
openstack 192.168.100.102 57672 running
openstack 192.168.100.101 36858 running
openstack 192.168.100.102 57674 running
...
RabbitMQ로 각 Openstack 서비스들의 요청이 정상적으로 들어오는 것을 확인하고 문제를 해결할 수 있었다.
$ openstack hypervisor list
이후 인스턴스를 위한 하이퍼바이저를 확인하는 해당 명령어를 실행했을 때, Compute Node가 등록되어 있어야 정상 작동하는데 아무것도 출력되지 않아 Compute Node가 Nova에 정상적으로 등록되지 않았음을 확인하였다.
### COMPUTE
$ journalctl -u openstack-nova-compute --no-pager | grep ERROR
...
FileNotFoundError: [Errno 2] No such file or directory: '/usr/lib/python3.9/site-packages/instances'
nova.exception_Remote.ComputeHostNotFound_Remote: Compute host compute could not be found.
따라서 Compute Node의 openstack-nova-compute 로그를 살펴본 결과 Nova가 인스턴스를 저장해야 할 디렉터리를 찾지 못하고 있음을 확인하였다.
기본적으로 Nova는 /var/lib/nova/instances 경로를 사용해야 하지만, 잘못된 경로를 참조하고 있음을 확인하고
$ grep instances_path /etc/nova/nova.conf
#instances_path=$state_path/instances
#instances_path_share =
#snapshots_directory=$instances_path/snapshots
# :oslo.config:option:`DEFAULT.instances_path` is on different disk partition
/etc/nova/nova.conf에 instances_path가 주석 처리된 것을 확인하고
mkdir -p /var/lib/nova/instances
chown -R nova:nova /var/lib/nova/instances
chmod 755 /var/lib/nova/instances
인스턴스를 위한 디렉터리를 생성한 후 적절한 권한을 설정한 뒤
$ vim /etc/nova/nova.conf
instances_path=/var/lib/nova/instances
/etc/nova/nova.conf의 instances_path를 위와 같이 업데이트 해준 후
systemctl restart openstack-nova-compute
데몬을 재시작하였다.
### CONTROLLER
$ nova-manage cell_v2 discover_hosts --verbose
...
Found 2 cell mappings.
Skipping cell0 since it does not contain hosts.
Getting computes from cell 'cell1': 4c904549-f28f-4d89-b47d-b1de8135152a
Checking host mapping for compute host 'compute': bef94020-5be0-422b-9af6-a14464b0a209
Creating host mapping for compute host 'compute': bef94020-5be0-422b-9af6-a14464b0a209
Found 1 unmapped computes in cell: 4c904549-f28f-4d89-b47d-b1de8135152a
이후 Compute Node 등록을 강제로 실행한 뒤
$ openstack hypervisor list
+----+---------------------+-----------------+-----------------+-------+
| ID | Hypervisor Hostname | Hypervisor Type | Host IP | State |
+----+---------------------+-----------------+-----------------+-------+
| 1 | compute | QEMU | 192.168.100.102 | up |
+----+---------------------+-----------------+-----------------+-------+
성공적으로 Hypervisor 목록을 확인할 수 있었고
$ openstack server create \
--flavor t2.small \
--image "Rocky Linux 9 Generic Cloud" \
--nic net-id=$(openstack network list --name private-net -f value -c ID) \
--security-group test_sg \
--key-name mykey \
test-instance
+-------------------------------------+--------------------------------------------------------------------+
| Field | Value |
+-------------------------------------+--------------------------------------------------------------------+
| OS-DCF:diskConfig | MANUAL |
| OS-EXT-AZ:availability_zone | |
| OS-EXT-SRV-ATTR:host | None |
| OS-EXT-SRV-ATTR:hypervisor_hostname | None |
| OS-EXT-SRV-ATTR:instance_name | |
| OS-EXT-STS:power_state | NOSTATE |
| OS-EXT-STS:task_state | scheduling |
| OS-EXT-STS:vm_state | building |
| OS-SRV-USG:launched_at | None |
| OS-SRV-USG:terminated_at | None |
| accessIPv4 | |
| accessIPv6 | |
| addresses | |
| adminPass | 9iyMDjLi6XGD |
| config_drive | |
| created | 2025-02-13T14:11:08Z |
| flavor | t2.small (6d40e04f-2897-4966-9b27-b5866d6a9d2c) |
| hostId | |
| id | 3fa3d02e-28fa-4067-bd5c-e58e4276a361 |
| image | Rocky Linux 9 Generic Cloud (73c6e7ee-3b6c-40dd-bdc5-96058e183faa) |
| key_name | mykey |
| name | test-instance |
| progress | 0 |
| project_id | 0636a6ee7b2242e7ba9faf5e7d1daed4 |
| properties | |
| security_groups | name='2510e7cb-95b2-4a26-acdf-e72a2d793a69' |
| status | BUILD |
| updated | 2025-02-13T14:11:08Z |
| user_id | 12529d0a6bfa4cb1894172f0fe354de9 |
| volumes_attached | |
+-------------------------------------+--------------------------------------------------------------------+
인스턴스가 정상적으로 생성되는 것을 확인할 수 있었다.
$ openstack server list
+--------------------------------------+---------------+--------+----------+-----------------------------+----------+
| ID | Name | Status | Networks | Image | Flavor |
+--------------------------------------+---------------+--------+----------+-----------------------------+----------+
| 3fa3d02e-28fa-4067-bd5c-e58e4276a361 | test-instance | ERROR | | Rocky Linux 9 Generic Cloud | t2.small |
+--------------------------------------+---------------+--------+----------+-----------------------------+----------+
하지만 인스턴스 빌드 도중 오류가 발생하여
{'code': 500, 'created': '2025-02-13T14:11:10Z', 'message': 'Exceeded maximum number of retries. Exhausted all hosts available for retrying build failures for instance 3fa3d02e-28fa-4067-bd5c-e58e4276a361.', 'details': 'Traceback (most recent call last):\n File "/usr/lib/python3.9/site-packages/nova/conductor/manager.py", line 703, in build_instances\n raise exception.MaxRetriesExceeded(reason=msg)\nnova.exception.MaxRetriesExceeded: Exceeded maximum number of retries. Exhausted all hosts available for retrying build failures for instance 3fa3d02e-28fa-4067-bd5c-e58e4276a361.\n'}
위와 같이 nova-conductor의 Exhausted all hosts available for retrying build failures for instance 오류임을 확인할 수 있었다.
openstack hypervisor stats show
+----------------------+-------+
| Field | Value |
+----------------------+-------+
| count | 1 |
| current_workload | 0 |
| disk_available_least | 57 |
| free_disk_gb | 61 |
| free_ram_mb | 7171 |
| local_gb | 61 |
| local_gb_used | 0 |
| memory_mb | 7683 |
| memory_mb_used | 512 |
| running_vms | 0 |
| vcpus | 6 |
| vcpus_used | 0 |
+----------------------+-------+
우선 하이퍼바이저의 문제를 점검하기 위해 하이퍼바이저의 status를 확인해준 뒤
$ journalctl -u openstack-nova-scheduler.service --no-pager -n 50
...
Feb 13 23:37:27 controller nova-scheduler[1862]: 2025-02-13 23:37:27.287 1862 ERROR nova keystoneauth1.exceptions.discovery.DiscoveryFailure: Could not find versioned identity endpoints when attempting to authenticate. Please check that your auth_url is correct.
nova-scheduler에서 위와 같이 Keystone 인증 문제가 발생하는 것을 볼 수 있었다.
$ vim /etc/nova/nova.conf
[keystone_authtoken]
www_authenticate_uri = http://controller:5000
auth_url = http://controller:5000/v3
memcached_servers = controller:11211
auth_type = password
project_domain_name = Default
user_domain_name = Default
project_name = admin
username = nova
password = nova
service_token_roles = admin
service_token_roles_required = True
따라서 위와 같이 auth_url을 수정하고 service_token_roles과 service_token_roles_required를 추가한 뒤
sudo systemctl restart openstack-nova-api
sudo systemctl restart openstack-nova-scheduler
관련 서비스 데몬들을 재시작하여 문제를 해결할 수 있었다
https://www.facebook.com/groups/openstack.kr/posts/2814338211913645/
$ journalctl -u openstack-nova-conductor.service --no-pager -f
...
nova.exception.PortBindingFailed: Binding failed for port 21d585de-0654-4359-a1af-61d283e8b138, please check neutron logs for more information.
하지만 Neutron이 Nova에 네트워크 포트를 바인딩하지 못하는 오류가 발생하였다.
새롭게 생성한 해당 인스턴스는 네트워크 인터페이스를 설정할 수 없으며, nova-scheduler가 포트 바인딩을 다시 시도하지만 결국 실패(MaxRetriesExceeded)한 것을 확인할 수 있었다.
$ openstack port list --network private-net
+--------------------------------------+------+-------------------+--------------------------------------------------------------------------+--------+
| ID | Name | MAC Address | Fixed IP Addresses
| Status |
+--------------------------------------+------+-------------------+--------------------------------------------------------------------------+--------+
| a182dc24-7ae3-4e0e-a647-0ce9f804dfd7 | | fa:16:3e:3a:cf:69 | ip_address='10.0.0.10', subnet_id='fb81c0eb-dca9-4ef5-9092-f241945c0199' | ACTIVE |
| b69bdbee-ee61-40e7-87bb-33b3cfc5af90 | | fa:16:3e:a7:0a:af | ip_address='10.0.0.1', subnet_id='fb81c0eb-dca9-4ef5-9092-f241945c0199' | ACTIVE |
+--------------------------------------+------+-------------------+--------------------------------------------------------------------------+--------+
우선 Neutron의 포트 상태를 확인해준 뒤
$ openstack port show a8d61a9e-cad9-4c7c-a939-30207be461f1
No Port found for a8d61a9e-cad9-4c7c-a939-30207be461f1
Neutron에서 Compute Node에 포트를 바인딩할 수 없어서 자동으로 포트를 삭제하여 로그의 포트가 존재하지 않음을 확인하고
$ virsh net-dumpxml opstnat
<network>
<name>opstnat</name>
<uuid>bf2cbc9d-43a4-4249-b430-a02b5ba3acc1</uuid>
<forward mode="bridge"/>
<bridge name="br-opstnat"/>
</network>
### HOST
<network>
<name>opstnat</name>
<uuid>bf2cbc9d-43a4-4249-b430-a02b5ba3acc1</uuid>
<forward mode="nat">
<nat>
<port start="1024" end="65535"/>
</nat>
</forward>
<bridge name="virbr10" stp="off" delay="0"/>
<mac address="52:54:00:8c:9b:3c"/>
<ip address="192.168.100.1" netmask="255.255.255.0">
<dhcp>
<range start="192.168.100.100" end="192.168.100.200"/>
<host mac="36:4c:6a:07:2e:4d" name="controller" ip="192.168.100.101"/>
<host mac="6a:66:48:b9:1f:43" name="compute" ip="192.168.100.102"/>
</dhcp>
</ip>
<portgroup name="vxlan">
<forward mode="nat"/>
<protocol family="ipv4">
<port start="4789" end="4789"/>
</protocol>
</portgroup>
</network>
private-net이 vxlan 타입의 네트워크이기 때문에 기존 opstnat는 NAT 모드(forward mode='nat')에서 동작하며, NAT 방식에서는 일반적으로 VXLAN 트래픽(UDP 4789)이 차단기에 4789 포트를 KVM에서 포트포워딩할 수 있도록 설정하였다.
### CONTROLLER
### COMPUTE
$ vim /etc/neutron/plugins/ml2/ml2_conf.ini
[ml2]
type_drivers = flat,vlan,vxlan
# tenant_network_types = vxlan
tenant_network_types = flat,vxlan
# mechanism_drivers = openvswitch,l2population
mechanism_drivers = openvswitch
# extension_drivers = port_security
이후 /etc/neutron/plugins/ml2/ml2_conf.ini에서 위와 같이 설정을 변경해주었는데
l2population은 여러 Compute Node 간 VXLAN 트래픽을 최적화하는 기능이라, 단일 노드에서는 불필요할 수도 있다VXLAN 네트워크만 tenant_network로 설정되었던 것을 flat을 지원하는 ext-net을 사용하고 있으므로, flat도 추가하였다extension_drivers : 포트 보안 (Security Groups) 관련 설정으로 일반적으로 문제는 없지만, Neutron 포트 바인딩이 실패할 경우 보안 그룹 문제일 가능성 있어서 테스트를 위해 포트 보안을 임시로 비활성화하였다.systemctl restart neutron-server neutron-openvswitch-agent
이후 관련 데몬들을 재시작하였지만
$ cat /var/log/neutron/openvswitch-agent.log | grep ERROR
2025-02-14 05:21:35.511 2732 ERROR neutron.agent.common.async_process [-] Error received from [ovsdb-client monitor tcp:127.0.0.1:6640 Interface name,ofport,external_ids --format=json]: None
2025-02-14 05:22:43.336 6066 ERROR neutron_lib.rpc [None req-e9439895-d0c9-4f4d-9d6b-a816cf9caca8 - - - - - -] Timeout in RPC method has_alive_neutron_server. Waiting for 9 seconds before next attempt. If the server is not down, consider increasing the rpc_response_timeout option as Neutron server(s) may be overloaded and unable to respond quickly enough.: oslo_messaging.exceptions.MessagingTimeout: Timed out waiting for a reply to message ID 9e20aa54bd494bee9b3b904fdb6349ec
/var/log/neutron/openvswitch-agent.log 로그에서 OVSDB (Open vSwitch Database)와 통신하는 ovsdb-client가 데이터를 가져오지 못하는 문제를 파악하였다.
해당 오류의 원인일 수 있는 요소들을 아래와 같다
따라서 위 요소들을 모두 점검하였지만 여전히 문제가 발생하여
$ cat /var/log/neutron/server.log | grep ERROR
2025-02-14 05:23:08.345 6093 ERROR neutron.plugins.ml2.managers [None req-b30b9088-5637-4ed0-835d-455db08e36f4 0114d4f75c8d44bd8a38ec2b77377d9d 0636a6ee7b2242e7ba9faf5e7d1daed4 - - - -] Failed to bind port d5d48599-1e43-43fa-9a59-7bc2679503b4 on host compute for vnic_type normal using segments [{'id': 'f4990c6a-89db-4523-8820-764717960458', 'network_type': 'vxlan', 'physical_network': None, 'segmentation_id': 336, 'network_id': '3d2a325a-b057-45ec-ade0-97849ce0a1f0'}]
2025-02-14 05:23:08.371 6093 ERROR neutron.plugins.ml2.managers [None req-b30b9088-5637-4ed0-835d-455db08e36f4 0114d4f75c8d44bd8a38ec2b77377d9d 0636a6ee7b2242e7ba9faf5e7d1daed4 - - - -] Failed to bind port d5d48599-1e43-43fa-9a59-7bc2679503b4 on host compute for vnic_type normal using segments [{'id': 'f4990c6a-89db-4523-8820-764717960458', 'network_type': 'vxlan', 'physical_network': None, 'segmentation_id': 336, 'network_id': '3d2a325a-b057-45ec-ade0-97849ce0a1f0'}]
2025-02-14 05:23:08.402 6093 ERROR neutron.plugins.ml2.managers [None req-b30b9088-5637-4ed0-835d-455db08e36f4 0114d4f75c8d44bd8a38ec2b77377d9d 0636a6ee7b2242e7ba9faf5e7d1daed4 - - - -] Failed to bind port d5d48599-1e43-43fa-9a59-7bc2679503b4 on host compute for vnic_type normal using segments [{'id': 'f4990c6a-89db-4523-8820-764717960458', 'network_type': 'vxlan', 'physical_network': None, 'segmentation_id': 336, 'network_id': '3d2a325a-b057-45ec-ade0-97849ce0a1f0'}]
2025-02-14 05:23:08.428 6093 ERROR neutron.plugins.ml2.managers [None req-b30b9088-5637-4ed0-835d-455db08e36f4 0114d4f75c8d44bd8a38ec2b77377d9d 0636a6ee7b2242e7ba9faf5e7d1daed4 - - - -] Failed to bind port d5d48599-1e43-43fa-9a59-7bc2679503b4 on host compute for vnic_type normal using segments [{'id': 'f4990c6a-89db-4523-8820-764717960458', 'network_type': 'vxlan', 'physical_network': None, 'segmentation_id': 336, 'network_id': '3d2a325a-b057-45ec-ade0-97849ce0a1f0'}]
/var/log/neutron/server.log 에서 'network_type': 'vxlan', 'physical_network': None...설정을 확인하였다.
단일 Compute Node 환경에서는 모든 인스턴스들이 br-int를 통해 통신하기 때문에 VXLAN 터널링이 필요하지 않아 현재 VXLAN 때문에 바인딩 실패가 발생하는 것을 확인하고, 터널링을 아예 제거하고 flat + vlan로 구성하여 문제를 해결하고자 하였다.
### CONTROLLER
$ sudo vi /etc/neutron/plugins/ml2/ml2_conf.ini
[ml2]
type_drivers = flat,vlan,vxlan
tenant_network_types = flat,vlan,vxlan
mechanism_drivers = openvswitch
[ml2_type_vlan]
network_vlan_ranges = provider:100:200
따라서 /etc/neutron/plugins/ml2/ml2_conf.ini를 위와 같이 변경하고
systemctl restart neutron-openvswitch-agent.service
neutron-openvswitch-agent 데몬을 재시작한 뒤 기존의 네트워크를 삭제해주었다.
openstack network create \
--provider-network-type vlan \
--provider-physical-network provider \
--provider-segment 100 \
private-net
이후 위와 같이 새롭게 private-net를 생성해주었다.
provider-network-type vlan: VLAN 사용provider-segment 100: VLAN ID 100 사용 (다른 VLAN과 충돌하지 않도록 설정)이때 VLAN을 사용하면 같은 provider 네트워크를 공유하면서도 private-net을 별도로 생성 가능하다.
openstack subnet create \
--network private-net \
--subnet-range 10.0.0.0/24 \
--gateway 10.0.0.1 \
--dns-nameserver 8.8.8.8 \
private-subnet
이후 VLAN 서브넷(private-subnet)을 생성하고
openstack router create my-router
openstack router set my-router --external-gateway ext-net
openstack router add subnet my-router private-subnet
라우터를 생성한 뒤 ext-net와 private-subnet를 연결해주면
최종적인 네트워크 토폴로지를 위와 같이 구성할 수 있으며
openstack server create \
--flavor t2.micro \
--image "Rocky Linux 9 Generic Cloud" \
--nic net-id=$(openstack network list --name private-net -f value -c ID) \
--security-group test_sg \
--key-name mykey \
test-instance
+-------------------------------------+--------------------------------------------------------------------+
| Field | Value |
+-------------------------------------+--------------------------------------------------------------------+
| OS-DCF:diskConfig | MANUAL |
| OS-EXT-AZ:availability_zone | |
| OS-EXT-SRV-ATTR:host | None |
| OS-EXT-SRV-ATTR:hypervisor_hostname | None |
| OS-EXT-SRV-ATTR:instance_name | |
| OS-EXT-STS:power_state | NOSTATE |
| OS-EXT-STS:task_state | scheduling |
| OS-EXT-STS:vm_state | building |
| OS-SRV-USG:launched_at | None |
| OS-SRV-USG:terminated_at | None |
| accessIPv4 | |
| accessIPv6 | |
| addresses | |
| adminPass | x9qWSLG2Sg3E |
| config_drive | |
| created | 2025-02-14T10:52:11Z |
| flavor | t2.micro (f72a3898-3018-4c8c-8bb1-90b92f788115) |
| hostId | |
| id | a0b2abe3-8d4b-4c4c-aed3-1dbbb4c28181 |
| image | Rocky Linux 9 Generic Cloud (73c6e7ee-3b6c-40dd-bdc5-96058e183faa) |
| key_name | mykey |
| name | test-instance |
| progress | 0 |
| project_id | 0636a6ee7b2242e7ba9faf5e7d1daed4 |
| properties | |
| security_groups | name='2510e7cb-95b2-4a26-acdf-e72a2d793a69' |
| status | BUILD |
| updated | 2025-02-14T10:52:11Z |
| user_id | 12529d0a6bfa4cb1894172f0fe354de9 |
| volumes_attached | |
+-------------------------------------+--------------------------------------------------------------------+
인스턴스가 정상적으로 생성된 뒤
위와 같이 정상적으로 private-net에 의해 고정 IP가 할당된 것을 확인할 수 있으며
openstack server list
+--------------------------------------+---------------+--------+------------------------+-----------------------------+----------+
| ID | Name | Status | Networks | Image
| Flavor |
+--------------------------------------+---------------+--------+------------------------+-----------------------------+----------+
| a0b2abe3-8d4b-4c4c-aed3-1dbbb4c28181 | test-instance | ACTIVE | private-net=10.0.0.145 | Rocky Linux 9 Generic Cloud | t2.micro |
+--------------------------------------+---------------+--------+------------------------+-----------------------------+----------+
해당 인스턴스가 정상적으로 구동되고
[root@compute ~]# ovs-vsctl show
85ccefa0-dd2d-4696-8ffe-bed0ece90fa1
Manager "ptcp:6640:127.0.0.1"
is_connected: true
Bridge br-ex
Controller "tcp:127.0.0.1:6633"
is_connected: true
fail_mode: secure
datapath_type: system
Port br-ex
Interface br-ex
type: internal
Port phy-br-ex
Interface phy-br-ex
type: patch
options: {peer=int-br-ex}
Port enp1s0
Interface enp1s0
Bridge br-int
Controller "tcp:127.0.0.1:6633"
is_connected: true
fail_mode: secure
datapath_type: system
Port int-br-ex
Interface int-br-ex
type: patch
options: {peer=phy-br-ex}
Port tapbaa2f08c-ad
tag: 1
Interface tapbaa2f08c-ad
Port patch-tun
Interface patch-tun
type: patch
options: {peer=patch-int}
Port br-int
Interface br-int
type: internal
ovs_version: "3.1.3"
Compute Node에서 tapbaa2f08c-ad 인터페이스가 br-int의 포트로 추가된 모습을 확인할 수 있었다.
openstack subnet create \
--network ext-net \
--subnet-range 192.168.100.0/24 \
--allocation-pool start=192.168.100.10,end=192.168.100.90 \
--gateway 192.168.100.1 \
--no-dhcp \
ext-subnet
또한 이후 Floating IP 생성을 위해 ext-net의 서브넷을 생성해준 뒤
$ openstack floating ip create ext-net
+---------------------+--------------------------------------+
| Field | Value |
+---------------------+--------------------------------------+
| created_at | 2025-02-14T10:57:30Z |
| description | |
| dns_domain | None |
| dns_name | None |
| fixed_ip_address | None |
| floating_ip_address | 192.168.100.29 |
| floating_network_id | 4f9841b9-dd23-4050-8bdd-09d79f8df5f9 |
| id | 6bca66bd-0d4a-41d8-9199-7b5174dc7f76 |
| name | 192.168.100.29 |
| port_details | None |
| port_id | None |
| project_id | 0636a6ee7b2242e7ba9faf5e7d1daed4 |
| qos_policy_id | None |
| revision_number | 0 |
| router_id | None |
| status | DOWN |
| subnet_id | None |
| tags | [] |
| updated_at | 2025-02-14T10:57:30Z |
+---------------------+--------------------------------------+
openstack server add floating ip test-instance 192.168.100.29
Floatinf IP를 생성하고 이를 test-instance에 할당해주어
$ ssh -i mykey.pem rocky@192.168.100.29
The authenticity of host '192.168.100.29 (192.168.100.29)' can't be established.
ED25519 key fingerprint is SHA256:dkWUo1kqCa1RpKEkquJ+wFpS3w2dZHtskeEafaNe++c.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.100.29' (ED25519) to the list of known hosts.
[rocky@test-instance ~]$
ssh를 통해 해당 인스턴스에 접속할 수 있었으며
### test-instance
[rocky@test-instance ~]$ uname -r
5.14.0-503.14.1.el9_5.x86_64
[rocky@test-instance ~]$ ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=56 time=37.7 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=56 time=35.8 ms
[rocky@test-instance ~]$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether fa:16:3e:d8:4a:aa brd ff:ff:ff:ff:ff:ff
altname enp0s3
altname ens3
inet 10.0.0.145/24 brd 10.0.0.255 scope global dynamic noprefixroute eth0
valid_lft 85975sec preferred_lft 85975sec
inet6 fe80::f816:3eff:fed8:4aaa/64 scope link
valid_lft forever preferred_lft forever
네트워크 설정 또한 정상적으로 구성된 것을 확인할 수 있었다.
최종적인 네트워크 토폴로지는 위와 같으며 이로써 Compute Node까지 구성이 완료되었다.
이후에는 현재 물리적으로 스위치를 통해 연결된 다른 kubernetes-host에 K8s를 설정하고 테스트해본 뒤 최종적으로 현재 구축한 Openstack Private Cloud의 Orchestration 서비스로서 동작하게 할 계획이다.