
현재 AWS를 통해 서비스하여 사용자를 확보할 계획이 있는 언제볼까? 프로젝트의 경우 현재 수익화 방안이 자세하게 고려되지 않은 프로젝트의 특성상 지속적으로 비용이 발생할 수밖에 없고
프로젝트의 규모가 작은만큼 Private Cloud를 구축하는 것이 적합하다고 판단하여
최종적으로 구현하고자 하는 아키텍처 디자인은 위와 같다.
퍼블릭 클라우드의 IaaS와 Kubernetes 기반 Orchestration 기능을 모방하는 형태로 구현하고자 하며, 해당 아키텍처 위에서 현재 개발하고 있는 프로젝트를 실 사용자를 대상으로 서비스하는 것을 목표로 한다.
추후 직접 구축한 Openstack Private Cloud를 구축하고 이를 Kubernetes를 통해 제어하는 형태로 Migration하기 위해서는 기존의 VM을 통해 구축하는 것에 한계가 있다고 판단하여
20년부터 약 3년간 함께해온 노트북을 초기화하고 이를 Openstack Cloud를 위한 엔드 디바이스로 전환할 계획이다.
CPU i7 12코어
16GB RAM
HDD 500GB
SSD 1TB
노트북의 개략적인 스펙은 위와 같으며, 이는 현재 개발하고 있는 서비스를 위한 인프라를 구축하기에 충분하다고 판단하였다.
다른 Linux 프로젝트가 아닌 Rocky Linux를 선택한 이유는 다음과 같다.
RHEL 호환성
CentOS의 정신을 계승하는 OS
안정성과 장기 지원
무료 사용 가능
국내 도입 사례 증가
| 배포판 | 비용/라이선스 | 장점 | 단점 | 특징/비고 |
|---|---|---|---|---|
| Rocky Linux | 무료 (커뮤니티 기반) | - RHEL과 바이너리 호환 - CentOS 창시자 주도, 안정적 - 장기 지원(LTS) 정책 | - 초창기이므로 AlmaLinux 대비 기업 스폰서 규모가 작을 수 있음 | CentOS 대체로 각광 국내외 대기업서 점차 도입 증가 |
| AlmaLinux | 무료 (커뮤니티 + 기업 지원) | - RHEL 호환 - 기업 후원(CloudLinux사), 빠른 업데이트 - CentOS 대체로 인기 | - Rocky와 기능·성능 큰 차이 없으나 선택 시 커뮤니티 분산 | CentOS EOL 이후 RHEL 계열 대표 후보 기업 후원으로 안정된 지원 |
| Red Hat Enterprise Linux (RHEL) | 유료 (구독 라이선스) | - 엔터프라이즈 지원 - 신뢰도 높은 보안 업데이트 - 광범위한 기업 고객 사례 | - 라이선스 비용 발생 - 특정 버전에서 독점 패치가 있을 수 있음 | 대기업·공공기관에서 전통적으로 가장 많이 사용 |
| CentOS Stream | 무료 (레드햇 주도 커뮤니티) | - RHEL보다 최신 기능 빠르게 반영 - RHEL “미리보기” 버전 역할 | - 엔터프라이즈 안정성 부족 - CentOS(Stable)와 달라진 릴리스 정책 | 테스트·개발 환경용으로 적합 장기 운영(Production)에는 다소 불안 |
| Ubuntu LTS (Canonical) | 무료/유료 지원 병행 가능 | - 빠른 패키지 업데이트, 풍부한 문서 - 컨테이너·클라우드 네이티브 환경에 강점 | - RHEL 계열과 패키지/커맨드 차이 - 일부 기업은 RHEL 계열 중심으로 운영 | 네이버·카카오 등 인터넷 기업에서 많이 사용 OpenStack/MicroStack 등 최신 오픈소스 호환성 높음 |
| SUSE Linux Enterprise (SLES) | 유료 (구독 라이선스) | - 안정적이고 유연한 패키지 - 독일계 기업에서 인기 - SAP, 메인프레임 등 엔터프라이즈 강점 | - 국내 레퍼런스 상대적으로 적음 - RHEL 계열에 비해 사용자층이 적어 자료가 한정적 | 제조·임베디드·SAP 환경에서 채택 유럽 지역에서 시장 점유율 높은 편 |

우선 위 Rocky Linux 페이지에서 DVD ISO를 다운로드 받는다.

또한 VM이 아니라 물리적으로 USB를 통해 설치를 진행할 것이기 때문에 rufus를 통해 현재 가지고 있는 USB를 Bootable Drive로 만들어준다.
이후 아래 블로그를 참조하여 설치를 진행하였다.
이후 Rocky Linux에서 Wifi Interface를 통해 연결이 되지 않아
위 레퍼런스를 참고하여 NetworkManager-wifi를 통해 네트워크 세팅을 완료해주었다.
추후 고정 IP 할당 등의 네트워크 세팅을 진행하거나, Openstack Cloud의 가용성을 위하여 Ethernet을 통해 네트워크를 설정하기 이전 현재 컴퓨터가 책상 밑에 있기 때문에 우선 현재 사용하는 노트북에서 SSH를 통해 제어하기로 하였다.
또한 이후 활성화한 Web Console을 통해 이를 확인할 수 있었다.
우선 Openstack Private Cloud를 위와 같이 구축하고자 한다.
https://docs.openstack.org/ko_KR/install-guide/overview.html
https://terianp.tistory.com/192
물리적 Linux 시스템에 설치하여 가상 머신을 생성할 수 있는 소프트웨어 기능
하드웨어 가상화 기술(Intel VT-x 또는 AMD-V)을 사용하여 가상화를 구현

하드웨어 계층
Linux 커널 계층
KVM 계층
게스트 운영 체제(KVM Guest)
QEMU 하드웨어 에뮬레이션
스레드 관리
$ lscpu | grep Virtualization
Virtualization: VT-x
우선 위와 같이 시스템 가상화를 지원하는지 확인한다
dnf -y install qemu-kvm libvirt libvirt-daemon libvirt-client virt-install virt-viewer virt-manager
이후 Rocky Linux에서 KVM을 활용하기 위해 필요한 패키지들을 설치한다
QEMU-KVM
Libvirt
Libvirt-daemon
Libvirt-client
Virt-install
Virt-viewer
Virt-manager
$ lsmod | grep kvm
kvm_intel 409600 0
kvm 1134592 1 kvm_intel
irqbypass 16384 1 kvm
현재 호스트 시스템에서 KVM 모듈이 정상적으로 로드되었는지 확인한 이후
systemctl start libvirtd
systemctl enable libvirtd
libvirtd를 시작하고 enable 하여준다.
wget https://dl.rockylinux.org/vault/rocky/9.2/isos/x86_64/Rocky-9.2-x86_64-minimal.iso
이후 위와 같이 Rocky Linux 9.2 minimal 이미지를 다운로드 받은 이후
cp ./Rocky-9.2-x86_64-minimal.iso /var/lib/libvirt/images/
ll /var/lib/libvirt/images/
libvirt의 images 디렉터리에 해당 이미지를 복사한다.
Wi-Fi 인터페이스를 직접 bridge로 활용하는 것은 현재 불가능하기 때문에 Host 머신에서 NAT 기반 가상 네트워크를 생성하여 각 Node들이 외부와도 통신하고, Node끼리의 통신 또한 가능하도록 구성하고자 한다.
$ virsh net-list --all
Name State Autostart Persistent
--------------------------------------------
default active yes yes
default라는 네트워크가 있고, Active: yes이기 때문에 이미 NAT 네트워크가 동작 중임을 확인할 수 있다.
vim openstack_node_network.xml
<network>
<name>opstnat</name>
<forward mode="nat"/>
<bridge name="virbr10" stp="off" delay="0"/>
<ip address="192.168.100.1" netmask="255.255.255.0">
<dhcp>
<range start="192.168.100.100" end="192.168.100.200"/>
</dhcp>
</ip>
</network>
하지만 새로운 네트워크를 만들기 위해 위와 같이 xml 파일을 생성하고
# 네트워크 정의
virsh net-define ./openstack_node_network.xml
# 호스트 부팅시 자동 시작 설정
virsh net-autostart opstnat
# 네트워크 시작
virsh net-start opstnat
위 명령어들을 차례대로 실행한다
$ virsh net-list --all
Name State Autostart Persistent
--------------------------------------------
default active yes yes
opstnat active yes yes
$ ip addr
...
5: virbr10: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
link/ether 52:54:00:44:a7:65 brd ff:ff:ff:ff:ff:ff
inet 192.168.100.1/24 brd 192.168.100.255 scope global virbr10
valid_lft forever preferred_lft forever
이후 새롭게 생성한 가상 네트워크 인터페이스가 정상적으로 작동하는 것을 볼 수 있다.
wget https://download.cirros-cloud.net/0.6.2/cirros-0.6.2-x86_64-disk.img
mv cirros-0.6.2-x86_64-disk.img /var/lib/libvirt/images
네트워크가 정상적으로 작동하는지 확인하기 위해 cirros 이미지를 다운로드 받고 libvirt의 images 디렉터리에 해당 이미지를 복사한다.
https://docs.openstack.org/image-guide/obtain-images.html#cirros-test
virt-install \
--name controller-test \
--ram 128 \
--vcpus 1 \
--os-variant generic \
--import \
--disk path=/var/lib/libvirt/images/cirros-0.6.2-x86_64-disk.img,format=qcow2,bus=virtio \
--network network=opstnat,model=virtio \
--graphics none
이후 위와 같이 VM을 실행하였지만
checking http://169.254.169.254/2009-04-04/instance-id
failed 1/20: up 0.87. request failed
failed 2/20: up 2.88. request failed
Cirros의 경우 부팅 과정에서 클라우드 메타데이터 서버를 조회하려고 시도하기 때문에, 테스트 시간이 증가되어
wget https://dl-cdn.alpinelinux.org/alpine/v3.21/releases/x86_64/alpine-standard-3.21.2-x86_64.iso
mv alpine-standard-3.21.2-x86_64.iso /var/lib/libvirt/images
경량화된 Apline Linux를 활용하는 것으로 방향을 바꾸었다.
$ virsh list --all
Id Name State
---------------------------------
3 controller-test running
$ virsh destroy controller-test
$ virsh undefine controller-test --remove-all-storage
우선 기존의 VM을 중지시킨 후 관련 qcow storage까지 삭제하고
virt-install \
--name controller-test \
--ram 256 \
--vcpus 1 \
--os-variant generic \
--disk path=/var/lib/libvirt/images/alpine-standard-3.21.2-x86_64-disk.qcow2,format=qcow2,bus=virtio,size=2 \
--network network=opstnat,model=virtio \
--graphics none \
--cdrom /var/lib/libvirt/images/alpine-standard-3.21.2-x86_64.iso
위 명령어를 통해 alpine-linux로 VM 인스턴스를 다시 생성한다.
Starting install...
Allocating 'alpine-standard-3.21.2-x86_64-disk.qcow2' | 2.0 GB 00:00:00
Creating domain... | 00:00:00
Running text console command: virsh --connect qemu:///system console controller-test
Connected to domain 'controller-test'
Escape character is ^] (Ctrl + ])
만약 위와 같이 설치 도중 멈춰있다면 RAM 메모리의 문제일 가능성이 존재하므로, 메모리를 높여 생성하면 된다.
tail -f /var/log/libvirt/qemu/controller-test.log
또한 위 명령어를 통해 현재 생성한 VM의 실시간 로그를 볼 수 있다.
virsh console controller-test
설치가 완료되면 위 명령어를 통해 해당 VM의 console로 접근할 수 있다.
localhost:~# ifconfig eth0 up
localhost:~# udhcpc -i eth0
udhcpc: started, v1.37.0
udhcpc: broadcasting discover
udhcpc: broadcasting discover
udhcpc: broadcasting select for 192.168.100.167, server 192.168.100.1
udhcpc: lease of 192.168.100.167 obtained from 192.168.100.1, lease time 3600
이후 Alpine Linux에서 수동으로 eth0 인터페이스를 활성화하고
localhost:~# ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether 52:54:00:81:68:f4 brd ff:ff:ff:ff:ff:ff
inet 192.168.100.167/24 brd 192.168.100.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe81:68f4/64 scope link
valid_lft forever preferred_lft forever
alpine linux 내부에서 네트워크 연결을 확인할 수 있으며
localhost:~# ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: seq=0 ttl=56 time=34.553 ms
64 bytes from 8.8.8.8: seq=1 ttl=56 time=36.382 ms
NAT를 통해 외부 네트워크에 정상적으로 접근할 수 있는 것을 볼 수 있다.
virt-install \
--name compute-test \
--ram 256 \
--vcpus 1 \
--os-variant generic \
--disk path=/var/lib/libvirt/images/alpine-standard-3.21.2-x86_64-disk-compute.qcow2,format=qcow2,bus=virtio,size=2 \
--network network=opstnat,model=virtio \
--graphics none \
--cdrom /var/lib/libvirt/images/alpine-standard-3.21.2-x86_64.iso
동일한 방식으로 위와 같이 compute-test 노드를 생성한다.
localhost:~# ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether 52:54:00:81:68:f4 brd ff:ff:ff:ff:ff:ff
inet 192.168.100.167/24 brd 192.168.100.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe81:68f4/64 scope link
valid_lft forever preferred_lft forever
localhost:~# ping 192.168.100.116
PING 192.168.100.116 (192.168.100.116): 56 data bytes
64 bytes from 192.168.100.116: seq=0 ttl=64 time=0.772 ms
64 bytes from 192.168.100.116: seq=1 ttl=64 time=0.813 ms
localhost:~# ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
link/ether 52:54:00:f0:25:e7 brd ff:ff:ff:ff:ff:ff
inet 192.168.100.116/24 brd 192.168.100.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fef0:25e7/64 scope link
valid_lft forever preferred_lft forever
localhost:~# ping 192.168.100.167
PING 192.168.100.167 (192.168.100.167): 56 data bytes
64 bytes from 192.168.100.167: seq=0 ttl=64 time=0.562 ms
64 bytes from 192.168.100.167: seq=1 ttl=64 time=0.847 ms
최종적으로 각 Node에서 서로 통신이 가능한 것을 확인할 수 있었다

virt-install \
--name controller \
--ram 6144 \
--vcpus 4 \
--disk path=/var/lib/libvirt/images/controller.qcow2,size=40 \
--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
GUI 없이 텍스트로만 설치를 진행하기 위해 --graphics none 파라미터를 추가한다.
inst.text : 텍스트 모드 설치를 활성화console=ttyS0,115200n8 : 시리얼 콘솔(ttyS0) 사용 설정또한 추가적으로 위 파라미터를 추가하여 설치되는 상황을 볼 수 있다.
이후 위와 같은 선택 화면이 나오게 된다.
이때 원격 데스크톱 프로토콜로, 설치 화면을 GUI 형태로 볼 수 있는 VNC(Virtual Network Computing)가 아닌 텍스트 모드로 진행하는 것을 선택한다.
이후 위 옵션에서 우선적으로 Installation Destination을 선택하고
차례대로 Replace Existing Linux System -> Standard Partition을 선택한다.
LVM(Logical Volume Manager)
- 이름처럼 파티션대신 볼륨이라는 단위로 저장 장치를 다룰 수 있다
- 물리 디스크를 볼륨 그룹으로 묶고 이것을 논리 볼륨으로 분할하여 관리하는 커널의 한 부분이다
- 스토리지의 확장,변경에 유연하며, 크기를 변경할 때 기존 데이터의 이전이 필요 없다
이후 우선 환경 구축에 활용하기 위해 root 비밀번호를 rootroot로 설정하고 설치를 진행한다.
이후 위와 같이 설치가 완료되어 Enter를 누르면
정상적으로 부팅되고, 기존에 설정한 opstnat에 따라 IP가 정상적으로 부여된 것을 확인할 수 있다.
또한 내부에서 정상적으로 통신이 가능한 것을 확인할 수 있다.
Static IP를 할당하기 위하여 Libvirt 측에서 특정 MAC 주소 → 특정 IP로 할당하도록 예약하는 방법을 사용하는 것이 이상적이라고 생각하여
$ virsh domiflist controller
Interface Type Source Model MAC
-------------------------------------------------------------
vnet3 network opstnat virtio 52:54:00:ef:ea:07
우선 controller의 MAC 주소를 위 명령어를 통해 알아낸 후
virsh net-edit opstnat
<network>
<name>opstnat</name>
<uuid>5760c7a9-7071-4db2-b7d6-72faad9cb7bf</uuid>
<forward mode='nat'/>
<bridge name='virbr10' stp='off' delay='0'/>
<mac address='52:54:00:44:a7:65'/>
<ip address='192.168.100.1' netmask='255.255.255.0'>
<dhcp>
<range start='192.168.100.100' end='192.168.100.200'/>
<!-- Static IP할당 -->
<host mac="52:54:00:ef:ea:07" name="controller" ip="192.168.100.101"/>
</dhcp>
</ip>
</network>
opstnat 네트워크 설정 파일에 <host>를 추가한다.
virsh net-destroy opstnat
virsh net-start opstnat
이후 위와 같이 네트워크를 재시작하고 VM을 재시작한다.
해당 설정이 잘 적용되었는지 확인하기 위해서는 /etc/libvirt/qemu/networks/opstnat.xml 파일을 확인하면 된다.
# CONTROLLER
$ 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:ef:ea:07 brd ff:ff:ff:ff:ff:ff
inet6 fe80::5054:ff:feef:ea07/64 scope link noprefixroute
valid_lft forever preferred_lft forever
이후 controller 내부에서 네트워크 정보를 확인해보니, IPv4 주소가 할당되지 못한 것을 확인할 수 있었다.
# HOST
sudo systemctl stop firewalld
sudo systemctl disable firewalld
대체로 libvirt가 자동으로 iptables/firewalld 규칙을 추가해주지만, 혹시나 호스트 방화벽에서 DHCP 트래픽인 UDP 67/68가 차단될 가능성이 존재하여 방화벽을 해제해주었다.
# HOST
$ virsh net-dhcp-leases opstnat
Expiry Time MAC address Protocol IP address Hostname Client ID or DUID
--------------------------------------------------------------------------------------------------------------
2025-01-24 07:08:03 52:54:00:5b:03:87 ipv4 192.168.100.158/24 - 01:52:54:00:5b:03:87
2025-01-24 07:11:56 52:54:00:69:0d:46 ipv4 192.168.100.200/24 - 01:52:54:00:69:0d:46
2025-01-24 07:31:05 52:54:00:ef:ea:07 ipv4 192.168.100.139/24 controller 01:52:54:00:ef:ea:0
또한 위와 같이 호스트 측 DHCP 할당 내역을 확인한 결과 controller의 MAC 주소 52:54:00:ef:ea:07에 대해 이전의 IP 주소가 할당되어 있는 것을 확인할 수 있었고
# HOST
$ cat /var/lib/libvirt/dnsmasq/opstnat.hostsfile
52:54:00:ef:ea:07,192.168.100.101,controller
opstnat.hostsfile에서 정상적으로 주소가 할당되어 있는 것을 확인하여
# HOST
virsh shutdown controller
virsh net-destroy opstnat
이는 controller를 shutdown하지 않고 reboot하여 발생한 문제라 생각해 우선 controller와 opstnat을 종료한 이후
# HOST
virsh net-start opstnat
virsh start controller
두 개 모두 재시작한 이후에
# CONTROLLER
[root@controller ~]# 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:ef:ea:07 brd ff:ff:ff:ff:ff:ff
inet 192.168.100.101/24 brd 192.168.100.255 scope global dynamic noprefixroute enp1s0
valid_lft 3593sec preferred_lft 3593sec
inet6 fe80::5054:ff:feef:ea07/64 scope link noprefixroute
valid_lft forever preferred_lft forever
[root@controller ~]# 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=33.5 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=56 time=35.8 ms
192.168.100.101가 정상적으로 할당되어 있는 것을 확인할 수 있었다.
virsh snapshot-create-as controller initial-snapshot \
--description "Initial snapshot for controller VM" \
--disk-only --atomic
또한 Openstack을 설치하기 이전에 문제 발생시 불필요한 소요를 방지하기 위해 VM의 Snapshot을 생성하였다.
이때 활용한 파라미터는 아래와 같다
--disk-only: 디스크 스냅샷만 생성--atomic: 스냅샷 생성이 모든 디스크에 대해 원자적으로 수행되도록 설정$ virsh snapshot-list controller
Name Creation Time State
---------------------------------------------------------------
initial-snapshot 2025-01-24 07:12:38 -0500 disk-snapshot
생성된 Snapshot을 위와 같이 확인할 수 있다.
vi나 vim 에디터에 접근하고 난 뒤 위와 같이 화면이 강제로 작아지는 문제가 있었다
echo "export TERM=xterm-256color" >> ~/.bashrc
source ~/.bashrc
우선 위와 같이 xterm-256color 설정을 ~/.bashrc에 추가하여 에디터에서 나와도 현재 호스트의 터미널 사이즈와 동잃하게 설정할 수 있도록 하고
### HOST
$ tput lines
51
$ tput cols
113
호스트 머신에서의 터미널 사이즈를 확인한 후
### VM
$ tput lines
24
$ tput cols
80
VM에서의 터미널 사이즈를 확인한다.
### VM
stty rows 50
해당 두 설정의 충돌로 인해 발생한 문제라고 판단하여 임의로 rows를 재설정해주어
stty(Set Terminal Options)는터미널(tty, teletypewriter)장치의 설정을 조회하거나 수정할 수 있는 명령어
터미널 장치의 속도, 제어 문자, 입력/출력 옵션 등을 관리하며, 사용자와 시스템 간의 입력 및 출력 동작 방식 설정 가능
조금 더 원활한 환경에서 VIM 에디터를 사용할 수 있었다.
./.vimrc 파일이나 res 옵션 등 다양한 바업을 시도해보았지만, 위 방법으로 일단 개발을 진행하고 추후 더 자세하게 다룰 예정이다.
sudo hostnamectl set-hostname controller
sudo systemctl restart systemd-hostnamed
우선 설치를 시작하기 이전, Openstack을 위한 많은 서비스들이 hostname을 기준으로 요청을 보내고 받기 때문에 위와 같이 현재 기기의 호스트 명을 controller으로 변경해준다.
$ hostnamectl
Static hostname: controller
Icon name: computer-vm
Chassis: vm 🖴
Machine ID: 58f2e266b6674e9fbdde9b925f80252c
Boot ID: 8d10ec1dac1448e4858a143ad3aeb0ae
Virtualization: kvm
Operating System: Rocky Linux 9.5 (Blue Onyx)
CPE OS Name: cpe:/o:rocky:rocky:9::baseos
Kernel: Linux 5.14.0-284.11.1.el9_2.x86_64
Architecture: x86-64
Hardware Vendor: Red Hat
Hardware Model: KVM
Firmware Version: 1.16.3-2.el9_5.1
위와 같이 Static hostname이 controller로 잘 설정된 것을 확인할 수 있다.
yum install chrony
$ chronyc sources
MS Name/IP address Stratum Poll Reach LastRx Last sample
===============================================================================
^- any.time.nl 2 6 377 52 +41ms[ +41ms] +/- 115ms
^- 121.174.142.82 3 6 377 16 +266us[ +266us] +/- 43ms
^- 121.174.142.81 3 6 377 13 +78us[ +78us] +/- 43ms
^* 193.123.243.2 2 6 377 51 +299us[ +545us] +/- 5506us
우선 Controller Node만 세팅할 것이기 때문에 경량의 시간 동기화 도구인 Chrony를 설치하고 현재 NTP 상태를 확인한다.
추후 Compute Node는 Controller Node를 Time Server로 두어 동기화를 진행할 예정이다.
dnf install -y dnf-plugins-core
dnf config-manager --set-enabled crb
dnf install -y centos-release-openstack-zed
dnf upgrade
dnf install -y python3-openstackclient
dnf install -y openstack-selinux
이후 Opestack 릴리즈와openstackclient, 그리고 openstack-selinux을 설치하여준다.
Zed: 안정성과 확장성이 필요한 중소 규모의 클라우드 환경에 적합Antelope: 장기 지원이 필요한 대규모 기업 환경에 최적화Bobcat: 최신 기술과 보안이 중요한 클라우드 환경에 적합현재 공식 홈페이지에 존재하는 릴리즈 버전에 대한 간략한 설명은 위와 같으며, Zed 버전이 적합하다고 판단하여 이를 설치해주었다.
dnf install -y mariadb mariadb-server
dnf install -y python3-PyMySQL
우선 위와 같이 DB 관련 패키지들을 설치해준 이후
$ vi /etc/my.cnf.d/openstack.cnf
[mysqld]
bind-address = 192.168.100.101
default-storage-engine = innodb
innodb_file_per_table = on
max_connections = 4096
collation-server = utf8_general_ci
character-set-server = utf8
/etc/my.cnf.d/openstack.cnf을 생성하고 위와 같이 bind-address를 controller의 IP 주소로 설정한다.
systemctl enable mariadb.service
systemctl start mariadb.service
mysql_secure_installation
이후 MariaDB를 enable해주고 mysql_secure_installation을 통해 설정을 끝마친다.
[root@controller ~]# mysql -u root -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 7
Server version: 10.5.22-MariaDB MariaDB Server
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MariaDB [(none)]> SHOW DATABASES;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
+--------------------+
3 rows in set (0.001 sec)
최종적으로 DB에 접속이 정상적으로 잘 이루어지는지 확인한다.
dnf -y install rabbitmq-server
systemctl enable rabbitmq-server.service
systemctl start rabbitmq-server.service
rabbitmqctl add_user openstack <RABBIT_PASS>
Openstack은 내부적으로 Message Queueing 서비스를 활용하여 통신하기 때문에 대부분의 프로젝트가 지원하는 RabbitMQ를 설치하고 openstack 유저를 비밀번호와 함께 추가한다.
rabbitmqctl set_permissions openstack ".*" ".*" ".*"
수정, 읽기, 쓰기 권한을 openstack 유저에게 추가하면 Message Queue 세팅이 완료된다.
Identity 서비스의 인증 메커니즘은 Memcached를 사용하여 토큰을 캐시한다.
yum install -y memcached python3-memcached
따라서 우선 memcached 패키지를 설치한 이후
$ vi /etc/sysconfig/memcached
OPTIONS="-l 127.0.0.1,::1,controller"
memcached 서비스가 Controller 노드의 management IP 주소를 사용할 수 있도록 설정하고
systemctl enable memcached.service
systemctl start memcached.service
systemctl을 통해 서비스를 시작해준다.
OpenStack 서비스는 Etcd를 사용할 수 있기에 미리 설치하여둔다.
Etcd는 분산된 Key-Value 저장소로서 분산 키 잠금, 설정 저장, 서비스 liveness 상태 추적 및 기타 시나리오에서 활용된다.
dnf install -y etcd
위와 같이 우선 패키지를 설치하고
$ vi /etc/etcd/etcd.conf
# [member]
ETCD_NAME="controller"
ETCD_DATA_DIR="/var/lib/etcd/default.etcd"
#ETCD_WAL_DIR=""
#ETCD_SNAPSHOT_COUNT="10000"
#ETCD_HEARTBEAT_INTERVAL="100"
#ETCD_ELECTION_TIMEOUT="1000"
ETCD_LISTEN_PEER_URLS="http://192.168.100.101:2380"
ETCD_LISTEN_CLIENT_URLS="http://192.168.100.101:2379"
#ETCD_MAX_SNAPSHOTS="5"
#ETCD_MAX_WALS="5"
#ETCD_CORS=""
#
#[cluster]
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://192.168.100.101:2380"
ETCD_INITIAL_CLUSTER="controller=http://192.168.100.101:2380"
ETCD_INITIAL_CLUSTER_STATE="new"
ETCD_INITIAL_CLUSTER_TOKEN="etcd-cluster-01"
ETCD_ADVERTISE_CLIENT_URLS="http://192.168.100.101:2379"
이후 MariaDB때의 설정과 마찬가지로 현재 controller의 주소로 설정하여준다.
systemctl enable etcd
systemctl start etcd
이후 etcd 서비스를 시작하여 Openstack을 설치하기 위한 환경 구성을 마친다.
### HOST
$ virsh snapshot-list controller
Name Creation Time State
-------------------------------------------------------------------
environment-snapshot 2025-01-26 05:05:17 -0500 disk-snapshot
initial-snapshot 2025-01-24 07:12:38 -0500 disk-snapshot
또한 환경설정이 끝난 이후의 snapshot을 생성해주었다.
mysql -u root -p
우선 위와 같이 MariaDB에 접속하여
CREATE DATABASE keystone;
GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'localhost' \
IDENTIFIED BY 'KEYSTONE_DBPASS';
GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'%' \
IDENTIFIED BY 'KEYSTONE_DBPASS';
keystone DB를 생성하고 KEYSTONE_DBPASS를 비밀번호로 접근 권한을 설정하여준다.
dnf -y install openstack-keystone httpd mod_wsgi
이후 keystone과 관련된 패키지를 설치하고
$ vi /etc/keystone/keystone.conf
[database]
...
# The SQLAlchemy connection string to use to connect to the database. (string
# value)
# Deprecated group/name - [DEFAULT]/sql_connection
# Deprecated group/name - [DATABASE]/sql_connection
# Deprecated group/name - [sql]/connection
connection = mysql+pymysql://keystone:KEYSTONE_DBPASS@controller/keystone
...
[token]
...
provider = fernet
keystone.conf 파일에서 connection과 provider를 설정한다.
Fernet은 대칭 키 암호화 방식을 사용하는 암호화 표준
Python의 cryptography 라이브러리에서 제공되며, OpenStack Keystone의 토큰 암호화 메커니즘 중 하나로 사용
$ su -s /bin/sh -c "keystone-manage db_sync" keystone
2025-01-26 05:17:05.077 54259 INFO alembic.runtime.migration [-] Context impl MySQLImpl.
2025-01-26 05:17:05.077 54259 INFO alembic.runtime.migration [-] Will assume non-transactional DDL.
2025-01-26 05:17:05.095 54259 INFO alembic.runtime.migration [-] Context impl MySQLImpl.
2025-01-26 05:17:05.095 54259 INFO alembic.runtime.migration [-] Will assume non-transactional DDL.
2025-01-26 05:17:05.140 54259 INFO alembic.runtime.migration [-] Running upgrade -> 27e647c0fad4, Initial version
2025-01-26 05:17:07.129 54259 INFO alembic.runtime.migration [-] Running upgrade 27e647c0fad4 -> e25ffa003242, Initial no-op Yoga contract migration.
2025-01-26 05:17:07.135 54259 INFO alembic.runtime.migration [-] Running upgrade 27e647c0fad4 -> 29e87d24a316, Initial no-op Yoga expand migration.
이후 keystone 사용자로 스위치한 뒤, Keystone 데이터베이스를 초기화한다.
keystone-manage fernet_setup --keystone-user keystone --keystone-group keystone
keystone-manage credential_setup --keystone-user keystone --keystone-group keystone
이후 Keystone에서 사용하는 Fernet 토큰을 위한 키 저장소를 생성하고 초기화하고 Keystone에서 사용하는 자격 증명 암호화 키를 위한 키 저장소를 생성하고 초기화한다.
# keystone-manage bootstrap --bootstrap-password <ADMIN_PASS> \
keystone-manage bootstrap --bootstrap-password admin \
--bootstrap-admin-url http://controller:5000/v3/ \
--bootstrap-internal-url http://controller:5000/v3/ \
--bootstrap-public-url http://controller:5000/v3/ \
--bootstrap-region-id RegionOne
위 명령어를 통해 Keystone을 설정하고 OpenStack 환경에서 인증 서비스를 사용할 수 있도록 기본 구성을 생성한다
vim /etc/httpd/conf/httpd.conf
#
# ServerName gives the name and port that the server uses to identify itself.
# This can often be determined automatically, but we recommend you specify
# it explicitly to prevent problems during startup.
#
# If your host doesn't have a registered DNS name, enter its IP address here.
#
ServerName controller
Apache HTTP 서버 설정에 위와 같이 ServerName을 controller로 변경하고
ln -s /usr/share/keystone/wsgi-keystone.conf /etc/httpd/conf.d/
/usr/share/keystone/wsgi-keystone.conf` 파일에 대해 링크를 생성한다.
systemctl enable httpd.service
systemctl start httpd.service
이후 httpd.service를 시작하고
export OS_USERNAME=admin
# export OS_PASSWORD=ADMIN_PASS
export OS_PASSWORD=admin
export OS_PROJECT_NAME=admin
export OS_USER_DOMAIN_NAME=Default
export OS_PROJECT_DOMAIN_NAME=Default
export OS_AUTH_URL=http://controller:5000/v3
export OS_IDENTITY_API_VERSION=3
환경변수를 위와 같이 설절하고
export | grep OS
declare -x HOSTNAME="controller"
declare -x OS_AUTH_URL="http://controller:5000/v3"
declare -x OS_IDENTITY_API_VERSION="3"
declare -x OS_PASSWORD="admin"
declare -x OS_PROJECT_DOMAIN_NAME="Default"
declare -x OS_PROJECT_NAME="admin"
declare -x OS_USERNAME="admin"
declare -x OS_USER_DOMAIN_NAME="Default"
설정된 환경변수를 확인한다.
● httpd.service - The Apache HTTP Server
Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; preset: di>
Active: active (running) since Sun 2025-01-26 05:25:50 EST; 2min 58s ago
Docs: man:httpd.service(8)
Main PID: 54661 (httpd)
Status: "Total requests: 0; Idle/Busy workers 100/0;Requests/sec: 0; Bytes>
Tasks: 197 (limit: 36051)
Memory: 61.5M
CPU: 534ms
CGroup: /system.slice/httpd.service
├─54661 /usr/sbin/httpd -DFOREGROUND
├─54662 /usr/sbin/httpd -DFOREGROUND
├─54663 "(wsgi:keystone-" -DFOREGROUND
├─54664 "(wsgi:keystone-" -DFOREGROUND
├─54665 "(wsgi:keystone-" -DFOREGROUND
├─54666 "(wsgi:keystone-" -DFOREGROUND
├─54667 "(wsgi:keystone-" -DFOREGROUND
├─54668 /usr/sbin/httpd -DFOREGROUND
├─54669 /usr/sbin/httpd -DFOREGROUND
└─54670 /usr/sbin/httpd -DFOREGROUND
이후 위와 같이 keystone 서버가 작동하고 있는 것을 볼 수 있다.
https://docs.openstack.org/keystone/2023.1/install/index-rdo.html
CREATE DATABASE glance;
GRANT ALL PRIVILEGES ON glance.* TO 'glance'@'localhost' \
IDENTIFIED BY 'GLANCE_DBPASS';
GRANT ALL PRIVILEGES ON glance.* TO 'glance'@'%' \
IDENTIFIED BY 'GLANCE_DBPASS';
우선 glance DB를 생성하고 PRIVILEGES를 GRANT하여준다.
$ openstack user create --domain default --password-prompt glance
User Password: # glance
Repeat User Password: # glance
+---------------------+----------------------------------+
| Field | Value |
+---------------------+----------------------------------+
| domain_id | default |
| enabled | True |
| id | 83110e40de9742ce87f8d83f02133151 |
| name | glance |
| options | {} |
| password_expires_at | None |
+---------------------+----------------------------------+
우선 위와 같이 glance user를 admin 프로젝트 하에 만들어주고
openstack role add --project admin --user glance admin
openstack service create --name glance \
--description "OpenStack Image" image
glance user에게 admin 권한을 추가해준 후
openstack endpoint create --region RegionOne \
image public http://controller:9292
openstack endpoint create --region RegionOne \
image internal http://controller:9292
openstack endpoint create --region RegionOne \
image admin http://controller:9292
Image service의 API endpoint들을 위와 같이 생성해준다.
dnf install -y openstack-glance
위와 같이 openstack-glance 패키지를 설치한다
openstack role add --user glance --user-domain Default --system all reader
이후 glance user에 system-scope 자원에 대한 reader 권한을 추가한다
vim /etc/glance/glance-api.conf
[DEFAULT]
bind_host = 0.0.0.0
bind_port = 9292
use_keystone_limits = True
[database]
# ...
connection = mysql+pymysql://glance:GLANCE_DBPASS@controller/glance
[keystone_authtoken]
# ...
www_authenticate_uri = http://controller:5000
auth_url = http://controller:5000
memcached_servers = controller:11211
auth_type = password
project_domain_name = Default
user_domain_name = Default
project_name = admin
username = glance
password = GLANCE_PASS # glance Openstack User의 비밀번호
[paste_deploy]
# ...
flavor = keystone
[glance_store]
# ...
stores = file,http
default_store = file
filesystem_store_datadir = /var/lib/glance/images/
[oslo_limit]
endpoint_id = 5882de4449b24b73a2b11835df9db71d
auth_url = http://controller:5000
auth_type = password
user_domain_id = default
username = glance
system_scope = all
password = GLANCE_PASS
region_name = RegionOne
이후 /etc/glance/glance-api.conf를 열고 위 단계들을 완료한다.
openstack endpoint list
+----------------------------------+-----------+--------------+--------------+---------+-----------+----------------------------+
| ID | Region | Service Name | Service Type | Enabled | Interface | URL |
+----------------------------------+-----------+--------------+--------------+---------+-----------+----------------------------+
| 5882de4449b24b73a2b11835df9db71d | RegionOne | glance | image | True | public | http://controller:9292 |
+----------------------------------+-----------+--------------+--------------+---------+-----------+----------------------------+
이때 oslo_limit의 endpoint_id의 경우 glance의 public endpoint의 id를 적어주면 된다
su -s /bin/sh -c "glance-manage db_sync" glance
이후 DB와 동기화를 진행하고
systemctl enable openstack-glance-api.service
systemctl start openstack-glance-api.service
$ systemctl status openstack-glance-api
● openstack-glance-api.service - OpenStack Image Service (code-named Glance) AP>
Loaded: loaded (/usr/lib/systemd/system/openstack-glance-api.service; enab>
Active: active (running) since Sun 2025-01-26 06:40:10 EST; 1s ago
Main PID: 63413 (glance-api)
Tasks: 1 (limit: 36051)
Memory: 77.9M
CPU: 1.445s
CGroup: /system.slice/openstack-glance-api.service
└─63413 /usr/bin/python3 /usr/bin/glance-api
위와 같이 glance 서비스가 정상 동작하는 것을 확인할 수 있다.
https://docs.openstack.org/glance/2023.1/install/install-rdo.html#finalize-installation
$ 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
우선 hostname에 문제가 있을 수도 있어 etc/hosts 에 위와 같이 192.168.100.101 controller를 추가해주었다.
$ curl http://controller:5000
{"versions": {"values": [{"id": "v3.14", "status": "stable", "updated": "2020-04-07T00:00:00Z", "links": [{"rel": "self", "href": "http://controller:5000/v3/"}], "media-types": [{"base": "application/json", "type": "ap pplication/vnd.openstack.identity-v3+json"}]}]}}
이후 현재 Keystone의 엔트포인트로의 요청이 정상적임을 확인하였다.
$ curl http://controller:9292
curl: (7) Failed to connect to controller port 9292: Connection refused
하지만 Glance의 엔드포인트로의 요청이 불가함을 확인한 이후
$ journalctl -u openstack-glance-api.service -n 20
ERROR: Auth plugin requires parameters which were not given: auth_url
jorurnalctl을 통해 auth_url이 할당되지 읺아서 발생한 문제임을 확인하였다.
cat << EOF > admin-openrc
export OS_PROJECT_DOMAIN_NAME=Default
export OS_USER_DOMAIN_NAME=Default
export OS_PROJECT_NAME=admin
export OS_USERNAME=admin
export OS_PASSWORD=<ADMIN_PASSWORD>
export OS_AUTH_URL=http://controller:5000/v3
export OS_IDENTITY_API_VERSION=3
export OS_IMAGE_API_VERSION=2
EOF
Troubleshooting 과정에서 Shell을 재시작하였기 때문에 우선 임의로 admin-openrc 스크립트를 생성해주고
source admin-openrc
openstack token issue
셸 스크립트를 실행하고 Keystone 인증에 문제가 없는지 token issue를 통해 확인한다.
$ vim /etc/glance/glance-api.conf
[key_manager]
#
# From castellan.config
#
# Specify the key manager implementation. Options are "barbican" and "vault".
# Default is "barbican". Will support the values earlier set using
# [key_manager]/api_class for some time. (string value)
# Deprecated group/name - [key_manager]/api_class
#backend = barbican
이후 조금 더 긴밀하게 /etc/glance/glance-api.conf를 삺펴본 결과, 공식문서와는 다르게 [key_manager]에만 Auth 관련 변수가 존재하여 이를 우선 주석처리 해주었고
[keystone_authtoken]
#
# From keystonemiddleware.auth_token
#
# auth_uri = http://controller:5000
auth_url = http://controller:5000
project_domain_name = Default
user_domain_name = Default
project_name = admin
username = glance
password = glanc
[keystone_authtoken]에서는 URL이 아닌 URI로 변수가 존재하였으며, 공식문서에서 필수로 요구하는 변수들을 입력하고
● openstack-glance-api.service - OpenStack Image Service (code-named Glance) AP>
Loaded: loaded (/usr/lib/systemd/system/openstack-glance-api.service; enab>
Active: active (running) since Wed 2025-01-29 06:02:20 EST; 1s ago
Main PID: 11308 (glance-api)
Tasks: 5 (limit: 36032)
Memory: 114.7M
CPU: 1.221s
CGroup: /system.slice/openstack-glance-api.service
├─11308 /usr/bin/python3 /usr/bin/glance-api
├─11311 /usr/bin/python3 /usr/bin/glance-api
├─11312 /usr/bin/python3 /usr/bin/glance-api
├─11313 /usr/bin/python3 /usr/bin/glance-api
└─11314 /usr/bin/python3 /usr/bin/glance-api
Glance 데몬이 정상적으로 실행되는 것을 확인할 수 있었다.
OpenStack Placement 서비스는 클라우드 환경에서 가용 자원(CPU, 메모리, 디스크, GPU etc.)의 사용을 추적하고, 스케줄링을 최적화하는 역할을 한다.
또한 REST API 기반으로 동작하며, Hypervisor 및 Resource Provider 정보를 저장하고 관리한다.
주요 기능은 아래와 같다
CREATE DATABASE placement;
GRANT ALL PRIVILEGES ON placement.* TO 'placement'@'localhost' \
IDENTIFIED BY 'PLACEMENT_DBPASS';
GRANT ALL PRIVILEGES ON placement.* TO 'placement'@'%' \
IDENTIFIED BY 'PLACEMENT_DBPASS';
우선 위와 같이 DB와 Placement에 대한 정보를 생성한다.
openstack user create --domain default --password-prompt placement
User Password: # placement
Repeat User Password: # placement
+---------------------+----------------------------------+
| Field | Value |
+---------------------+----------------------------------+
| domain_id | default |
| enabled | True |
| id | 597d8fb3ab914e0783bf6eaecef35963 |
| name | placement |
| options | {} |
| password_expires_at | None |
+---------------------+----------------------------------+
이후 openstack에 placement user를 생성하고
openstack role add --project admin --user placement admin
openstack service create --name placement \
--description "Placement API" placement
role을 추가하고 service를 생성한 이후
openstack endpoint create --region RegionOne \
placement public http://controller:8778
openstack endpoint create --region RegionOne \
placement internal http://controller:8778
openstack endpoint create --region RegionOne \
placement admin http://controller:8778
API Endpoint를 생성해준다.
dnf install -y openstack-placement-api
패키지를 설치하고 /etc/placement/placement.conf를 수정하는 과정은 공식 문서와 동일하게 진행하였다.
systemctl restart httpd
httpd 서비스를 성공적으로 재시작하여 placement 서비스 설치를 완료할 수 있었다
https://docs.openstack.org/placement/2023.1/install/install-rdo.html
CREATE DATABASE nova_api;
CREATE DATABASE nova;
CREATE DATABASE nova_cell0;
GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'localhost' \
IDENTIFIED BY 'NOVA_DBPASS';
GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'%' \
IDENTIFIED BY 'NOVA_DBPASS';
GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'localhost' \
IDENTIFIED BY 'NOVA_DBPASS';
GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'%' \
IDENTIFIED BY 'NOVA_DBPASS';
GRANT ALL PRIVILEGES ON nova_cell0.* TO 'nova'@'localhost' \
IDENTIFIED BY 'NOVA_DBPASS';
GRANT ALL PRIVILEGES ON nova_cell0.* TO 'nova'@'%' \
IDENTIFIED BY 'NOVA_DBPASS';
우선 위와 같이 DB 설정을 진행하고
openstack user create --domain default --password-prompt nova
User Password: # nova
Repeat User Password: # nova
+---------------------+----------------------------------+
| Field | Value |
+---------------------+----------------------------------+
| domain_id | default |
| enabled | True |
| id | bfdfdbc65be14efb9bddbafdd6c489a6 |
| name | nova |
| options | {} |
| password_expires_at | None |
+---------------------+----------------------------------+
openstack에 nova user를 생성해준다
openstack role add --project admin --user nova admin
openstack service create --name nova \
--description "OpenStack Compute" compute
openstack endpoint create --region RegionOne \
compute public http://controller:8774/v2.1
openstack endpoint create --region RegionOne \
compute internal http://controller:8774/v2.1
openstack endpoint create --region RegionOne \
compute admin http://controller:8774/v2.1
이후 차례로 위 명령어들을 통해 세팅하고
dnf -y install openstack-nova-api openstack-nova-conductor \
openstack-nova-novncproxy openstack-nova-scheduler
패키지를 설치한 이후 공식 문서에 따라 conf 파일을 수정해준다.
https://docs.openstack.org/nova/2023.1/install/controller-install-rdo.html
su -s /bin/sh -c "nova-manage cell_v2 list_cells" nova
+-------+--------------------------------------+------------------------------------------+-------------------------------------------------+----------+
| Name | UUID | Transport URL | Database Connection | Disabled |
+-------+--------------------------------------+------------------------------------------+-------------------------------------------------+----------+
| cell0 | 00000000-0000-0000-0000-000000000000 | none:/ | mysql+pymysql://nova:****@controller/nova_cell0 | False |
| cell1 | 4c904549-f28f-4d89-b47d-b1de8135152a | rabbit://openstack:****@controller:5672/ | mysql+pymysql://nova:****@controller/nova | False |
+-------+--------------------------------------+------------------------------------------+-------------------------------------------------+----------+
설정 이후 nova cell0과 cell1이 정상적으로 등록되었는지 확인하고
systemctl enable \
openstack-nova-api.service \
openstack-nova-scheduler.service \
openstack-nova-conductor.service \
openstack-nova-novncproxy.service
systemctl start \
openstack-nova-api.service \
openstack-nova-scheduler.service \
openstack-nova-conductor.service \
openstack-nova-novncproxy.service
nova와 관련된 서비스를 모두 시작해준다.
Job for openstack-nova-conductor.service failed because the control process exited with error code.
See "systemctl status openstack-nova-conductor.service" and "journalctl -xeu openstack-nova-conductor.service" for details.
Job for openstack-nova-scheduler.service failed because the control process exited with error code.
See "systemctl status openstack-nova-scheduler.service" and "journalctl -xeu openstack-nova-scheduler.service" for details.
하지만 nova 관련 서비스를 시작하는 과정에서 위와 같은 오류가 발생하였다
ERROR nova openstack.exceptions.NotSupported: The placement service for controller:RegionOne exists but does not have any supported versions.
이를 해결하기 위해 journalctl로 로그를 살펴본 결과 위와 같은 ERROR 메세지를 확인하여
dnf install -y python3-osc-placement
우선 패키지 문제일 수도 있기에 python3-osc-placement 패키지를 설치하고
placement-status upgrade checkstart
+----------------------------------------------------------------------+
| Upgrade Check Results |
+----------------------------------------------------------------------+
| Check: Missing Root Provider IDs |
| Result: Success |
| Details: None |
+----------------------------------------------------------------------+
| Check: Incomplete Consumers |
| Result: Success |
| Details: None |
+----------------------------------------------------------------------+
공식 문서의 Verification 방법을 시도해보았고, openstack-placement 서비스가 작동하는 것을 확인하였지만 같은 오류가 지속적으로 발생하였다.
$ tail -f /var/log/placement/placement-api.log
AH01630: client denied by server configuration: /usr/bin/placement-api
AH01630: client denied by server configuration: /usr/bin/placement-api
AH01630: client denied by server configuration: /usr/bin/placement-api
...
이후 placement-api 자체의 로그를 확인해본 결과 클라이언트의 요청이 거부되는 것을 확인하고
setenforce 0
semanage port -a -t http_port_t -p tcp 8778
setsebool -P httpd_can_network_connect 1
setenforce 1
SELinux에서 placement-api 서비스의 8778번 포트를 허용하고
$ firewall-cmd --list-all
public (active)
target: default
icmp-block-inversion: no
interfaces: enp1s0
sources:
services: cockpit dhcpv6-client ssh
ports: 9292/tcp
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
firewall-cmd --add-port=8778/tcp --permanent
firewall-cmd --reload
동일 포트를 방화벽에서도 허용해주었으나, 문제가 해결되지 않아
$ curl http://controller:8778
<!doctype html>
<html>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<title>HTTP Server Test Page powered by: Rocky Linux</title>
<style type="text/css">
/*<![CDATA[*/
html {
height: 100%;
width: 100%;
}
body {
background: rgb(20,72,50);
background: -moz-linear-gradient(180deg, rgba(23,43,70,1) 30%, rgba(0,0,0,1) 90%) ;
background: -webkit-linear-gradient(180deg, rgba(23,43,70,1) 30%, rgba(0,0,0,1) 90%) ;
...
placement-api의 엔드포인트인 http://controller:8778으로 요청을 보내보았다.
이때 Rocky Linux 기본 HTTP 서버 테스트 페이지가 표시되고 placement-api의 JSON 응답이 출력되지 않았다.
vi /etc/httpd/conf.d/00-placement-api.conf
이는 placement-api가 Apache에서 제대로 매핑되지 않았을 가능성이 높다고 판단하여 httpd 내의 Apache 가상 호스트 설정을 확인하였다.
Listen 8778
<VirtualHost *:8778>
WSGIProcessGroup placement-api
WSGIApplicationGroup %{GLOBAL}
WSGIPassAuthorization On
WSGIDaemonProcess placement-api processes=3 threads=1 user=placement group=plaa
cement
WSGIScriptAlias / /usr/bin/placement-api
<IfVersion >= 2.4>
ErrorLogFormat "%M"
</IfVersion>
ErrorLog /var/log/placement/placement-api.log
#SSLEngine On
#SSLCertificateFile ...
#SSLCertificateKeyFile ...
</VirtualHost>
Alias /placement-api /usr/bin/placement-api
<Location /placement-api>
SetHandler wsgi-script
Options +ExecCGI
WSGIProcessGroup placement-api
WSGIApplicationGroup %{GLOBAL}
WSGIPassAuthorization On
</Location>
WSGIScriptAlias / /usr/bin/placement-api 설정과 <Directory /usr/bin> 설정 등 일부는 누락되고 일부는 중복되는 요소가 있었기에
Listen 8778
<VirtualHost *:8778>
WSGIDaemonProcess placement-api processes=3 threads=1 user=placement group=placement
WSGIProcessGroup placement-api
WSGIPassAuthorization On
WSGIScriptAlias / /usr/bin/placement-api
<Directory /usr/bin>
Require all granted
Options +ExecCGI
SetHandler wsgi-script
</Directory>
<Location />
Require all granted
</Location>
ErrorLog /var/log/httpd/placement-api_error.log
CustomLog /var/log/httpd/placement-api_access.log combined
</VirtualHost>
위와 같이 조치하였다.
<Directory /usr/bin> 추가 -> Apache가 실행 파일 접근 허용<Location /> 추가 -> API 요청을 올바르게 처리Alias /placement-api /usr/bin/placement-api 제거 -> 중복 설정 제거$ curl http://controller:8778
{"versions": [{"id": "v1.0", "max_version": "1.39", "min_version": "1.0", "status": "CURRENT", "links": [{"rel": "self", "href": ""}]}]}
이후 placement-api의 응답을 정상적으로 받아볼 수 있었고
systemctl restart \
openstack-nova-api.service \
openstack-nova-scheduler.service \
openstack-nova-conductor.service \
openstack-nova-novncproxy.service
nova 관련 서비스들도 모두 정상적으로 실행되는 것을 확인할 수 있었다.
CREATE DATABASE neutron;
GRANT ALL PRIVILEGES ON neutron.* TO 'neutron'@'localhost' \
IDENTIFIED BY 'NEUTRON_DBPASS';
GRANT ALL PRIVILEGES ON neutron.* TO 'neutron'@'%' \
IDENTIFIED BY 'NEUTRON_DBPASS';
우선 Neutron 서비스를 설치하기 이전 DB 설정을 위와 같이 완료하고
openstack user create --domain default --password-prompt neutron
User Password: # neutron
Repeat User Password: # neutron
+---------------------+----------------------------------+
| Field | Value |
+---------------------+----------------------------------+
| domain_id | default |
| enabled | True |
| id | 0114d4f75c8d44bd8a38ec2b77377d9d |
| name | neutron |
| options | {} |
| password_expires_at | None |
+---------------------+----------------------------------+
Openstack User를 생성하고
openstack service create --name neutron \
--description "OpenStack Networking" network
openstack endpoint create --region RegionOne \
network public http://controller:9696
openstack endpoint create --region RegionOne \
network internal http://controller:9696
openstack endpoint create --region RegionOne \
network admin http://controller:9696
이전의 서비스 설치 과정과 동일하게 Endpoint를 설정하여준다.

Option 2는 Option 1에 계층 3(Layer 3) 서비스 기능을 추가하여 인스턴스를 Self-Service Network에 연결할 수 있도록 확장합니다.
데모 사용자 또는 기타 비특권 사용자는 라우터를 포함한 셀프 서비스 네트워크를 관리할 수 있으며, 이를 통해 셀프 서비스 네트워크와 프로바이더 네트워크 간 연결이 가능해집니다.
또한, Floating IP 주소를 사용하면 인터넷과 같은 외부 네트워크에서 셀프 서비스 네트워크에 있는 인스턴스에 접근할 수 있도록 지원합니다.
셀프 서비스 네트워크는 일반적으로 오버레이 네트워크(Overlay Network)를 사용합니다.
VXLAN과 같은 오버레이 네트워크 프로토콜은 추가적인 헤더를 포함하므로 패킷의 오버헤드가 증가하고, 사용 가능한 페이로드(payload) 공간이 줄어들게 됩니다.
가상 네트워크 인프라에 대한 정보를 알지 못하는 인스턴스는 기본적으로 이더넷 최대 전송 단위(MTU, Maximum Transmission Unit)인 1500바이트를 사용하여 패킷을 전송하려고 시도합니다.
그러나 Networking 서비스는 DHCP를 통해 인스턴스에 올바른 MTU 값을 자동으로 제공합니다.
하지만 일부 클라우드 이미지에서는 DHCP를 사용하지 않거나 DHCP의 MTU 옵션을 무시할 수 있으므로, 메타데이터(metadata) 또는 스크립트(script)를 사용하여 직접 MTU를 설정해야 합니다.
https://docs.openstack.org/neutron/2023.1/install/controller-install-rdo.html
Public Cloud와 조금이라도 더 유사한 환경을 구축하기 위하여 Self Service Network를 설치해주었다.
yum install -y openstack-neutron openstack-neutron-ml2 \
openstack-neutron-openvswitch ebtables
우선 위와 같이 패키지를 설치한 이후
# [ml2] 섹션: Modular Layer 2 (ML2) 플러그인 설정
# ML2는 OpenStack Neutron에서 다양한 Layer-2 네트워크 기술을 지원하는 플러그인
# 네트워크 유형(type_drivers), 테넌트 네트워크 유형(tenant_network_types),
# 메커니즘 드라이버(mechanism_drivers), 확장 드라이버(extension_drivers) 등을 정의함
[ml2]
# type_drivers: Neutron에서 사용할 수 있는 Layer-2 네트워크 유형을 정의
# flat: VLAN 태그 없이 단일 네트워크로 동작하는 방식
# 모든 인스턴스가 동일한 브로드캐스트 도메인을 공유하며, 네트워크 분리가 불가능함
# vlan: VLAN 태그를 사용하여 여러 개의 가상 네트워크를 생성할 수 있음
# VLAN ID를 기반으로 트래픽을 분리하여 네트워크 격리를 제공함
# vxlan: VXLAN(오버레이 네트워크) 방식을 사용하여 Layer-2 네트워크를 확장
# 물리 네트워크의 VLAN 개수 제한을 넘어서 더 많은 가상 네트워크를 생성할 수 있음
type_drivers = flat,vlan,vxlan
# tenant_network_types: 테넌트(사용자) 네트워크가 사용할 네트워크 유형을 정의
# vxlan: 셀프 서비스 네트워크(사용자가 직접 생성하는 네트워크)에 VXLAN 사용
tenant_network_types = vxlan
# mechanism_drivers: 네트워크 패킷을 처리하는 드라이버 설정
# openvswitch: Open vSwitch 기반의 가상 스위치 사용
# l2population: VXLAN 사용 시 L2 학습을 최적화하여 성능 개선 (ARP 트래픽 감소)
mechanism_drivers = openvswitch,l2population
# extension_drivers: 네트워크 기능 확장을 위한 드라이버 설정
# port_security: 포트 보안(스푸핑 방지 등) 기능 활성화
extension_drivers = port_security
# Flat 네트워크는 별도의 VLAN 태그 없이 물리 네트워크와 동일한 네트워크를 제공
# 모든 인스턴스가 같은 브로드캐스트 도메인에 속하며, 트래픽이 분리되지 않음
# 주로 외부 네트워크(프로바이더 네트워크)와의 연결을 위해 사용됨
[ml2_type_flat]
flat_networks = provider
# VXLAN(Virtual eXtensible LAN)은 Layer-2 네트워크를 Layer-3 기반에서 확장하는 오버레이 네트워크 기술
# 물리 네트워크의 VLAN ID 개수 제한(4096개)을 넘어서 더 많은 가상 네트워크를 생성할 수 있음
# VNI (VXLAN Network Identifier) 값을 사용하여 네트워크를 구분함
# vni_ranges: VXLAN ID 범위를 지정하여 오버레이 네트워크 생성
# 1:1000 → VXLAN 네트워크 ID(VNI) 범위를 1~1000으로 설정
[ml2_type_vxlan]
vni_ranges = 1:1000
ml2 관련 설정을 진행한 이후
# Open vSwitch(OVS)는 가상 네트워크 브리지를 제공하는 가상 스위치
# bridge_mappings: 프로바이더 네트워크를 물리 네트워크 인터페이스와 매핑
# provider:PROVIDER_INTERFACE_NAME → provider 네트워크가 사용할 물리 인터페이스를 지정
# PROVIDER_INTERFACE_NAME을 실제 물리 인터페이스 이름으로 변경해야 함 (예: eth1)
[ovs]
bridge_mappings = provider:PROVIDER_INTERFACE_NAME
# [vxlan] 섹션: VXLAN 오버레이 네트워크 설정
# VXLAN을 활성화하고, 오버레이 트래픽을 처리할 물리 네트워크 인터페이스의 IP 주소를 지정
# local_ip: VXLAN 터널링을 처리할 물리 인터페이스의 IP 주소
# OVERLAY_INTERFACE_IP_ADDRESS를 실제 관리 네트워크의 IP로 변경해야 함 (예: 192.168.1.10)
# l2_population: VXLAN에서 Layer-2 학습을 최적화하여 성능을 개선
# ARP 및 브로드캐스트 트래픽을 줄이기 위해 l2population 메커니즘과 함께 사용됨
[vxlan]
local_ip = OVERLAY_INTERFACE_IP_ADDRESS
l2_population = true
# [securitygroup] 섹션: 보안 그룹 설정
# OpenStack 인스턴스의 네트워크 보안을 강화하기 위한 방화벽 및 보안 그룹 설정
# enable_security_group: 보안 그룹 기능 활성화 (기본값: true)
# firewall_driver: 사용할 방화벽 드라이버 지정
# - openvswitch: Open vSwitch 네이티브 방화벽 사용
# - iptables_hybrid: iptables 기반의 하이브리드 방화벽 사용
# 기본적으로 openvswitch를 사용하지만, 환경에 따라 iptables_hybrid를 활성화할 수도 있음
[securitygroup]
enable_security_group = true
firewall_driver = openvswitch
# firewall_driver = iptables_hybrid
ovs 관련 설정을 진행하였으며, 각 설정 옵션의 의미는 주석의 내용과 같다
https://docs.openstack.org/neutron/2023.1/install/controller-install-option2-rdo.html
$ vim /etc/neutron/metadata_agent.ini
[DEFAULT]
# ...
nova_metadata_host = controller
# 메타데이터 프록시 인증을 위한 보안 키
metadata_proxy_shared_secret = METADATA_SECRET
이후 위와 같이 metadata_agent를 설정하는데, 이때 METADATA_SECRET의 경우임의로 uuidgen을 통해 생성해주었다.
systemctl enable neutron-server.service \
neutron-openvswitch-agent.service neutron-dhcp-agent.service \
neutron-metadata-agent.service
systemctl start neutron-server.service \
neutron-openvswitch-agent.service neutron-dhcp-agent.service \
neutron-metadata-agent.service
systemctl enable neutron-l3-agent.service
systemctl start neutron-l3-agent.service
$ systemctl | grep neutron
neutron-l3-agent.service loaded active running OpenStack Neutron Layer 3 Agent
neutron-metadata-agent.service loaded active running OpenStack Neutron Metadata Agent
neutron-openvswitch-agent.service loaded active running OpenStack Neutron Open vSwitch Agent
neutron-server.service loaded active running OpenStack Neutron Server
Self Service Network를 구성하였으므로 neutron-l3-agent 서비스까지 구동이 정상적으로 진행되는 것을 확인하였다.
https://docs.openstack.org/neutron/2023.1/install/controller-install-rdo.html
$ openstack network agent list
HttpException: 503: Server Error for url: http://controller:9696/v2.0/agents, The server is currently unavailable. Please try again at a later time.<br /><br />
The Keystone service is temporarily unavailable.
우선 위와 같이 network agent의 리스트를 받아오지 못하여
openstack token issue
+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Field | Value
|
+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| expires | 2025-01-30T10:44:14+0000
|
| id | gAAAAABnm0nuW0PxiRcDmlgQSLHiNhDezRv9X8OfrTSxPJECeCu4GOV9Xyy-he_7eBEBqIHlvghRLdC_4Dxd8z1IZEUqVHrXKmIv9K9XewYzL8jHb1EHNCD1ffFIAKRobDy6FNxoBrifp5lo7vscFSz4lrYj_sktO6mS6zabgUBn4SkPtmLz1IU |
| project_id | 0636a6ee7b2242e7ba9faf5e7d1daed4
|
| user_id | 12529d0a6bfa4cb1894172f0fe354de9
|
+------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
kestone 자체의 문제인지 점검하기 위해 openstack token issue 명령어를 실행해주었고
openstack user set --password neutron neutron
혹시나 설치 과정에서 openstack user의 비밀번호가 잘못 설정되었을 수도 있어 비밀번호를 재설정 해주었다.
$ curl -i http://controller:5000/v3
HTTP/1.1 200 OK
Date: Thu, 30 Jan 2025 09:56:28 GMT
Server: Apache/2.4.62 (Rocky Linux) mod_wsgi/4.7.1 Python/3.9
Content-Length: 250
Vary: X-Auth-Token
x-openstack-request-id: req-f4c31868-509e-4336-8131-2b58df52f437
Content-Type: application/json
{"version": {"id": "v3.14", "status": "stable", "updated": "2020-04-07T00:00:00Z", "links": [{"rel": "self", "href": "http://controller:5000/v3/"}], "media-types": [{"base": "application/json", "type": "application n/vnd.openstack.identity-v3+json"}]}}
이후 keystone의 엔드포인트인 http://controller:5000/v3 요청은 정상적으로 수행되는 것을 확인하였고
$ TOKEN=$(openstack token issue -f value -c id)
$ curl -i -H "X-Auth-Token: $TOKEN" http://controller:9696/v2.0/agents
HTTP/1.1 503 Service Unavailable
Content-Length: 260
Content-Type: text/html; charset=UTF-8
X-Openstack-Request-Id: req-28376d98-3427-41ed-9e42-87d2e985e98a
Date: Thu, 30 Jan 2025 09:52:45 GMT
<html>
<head>
<title>503 Service Unavailable</title>
</head>
<body>
<h1>503 Service Unavailable</h1>
The server is currently unavailable. Please try again at a later time.<br /><br />
The Keystone service is temporarily unavailable.
</body>
</html>
추가적으로 토큰을 직접적으로 추가해 요청했음에도, 동일한 에러 메세지가 출력되는 것을 확인하였다.
$ journalctl -u neutron-server --no-pager | tail -n 50
Jan 30 04:49:32 controller systemd[1]: Stopping OpenStack Neutron Server...
Jan 30 04:49:32 controller systemd[1]: neutron-server.service: Deactivated successfully.
Jan 30 04:49:32 controller systemd[1]: Stopped OpenStack Neutron Server.
Jan 30 04:49:32 controller systemd[1]: neutron-server.service: Consumed 1min 45.490s CPU time.
Jan 30 04:49:32 controller systemd[1]: Starting OpenStack Neutron Server...
Jan 30 04:49:35 controller systemd[1]: Started OpenStack Neutron Server.
따라서 우선 neutron-server의 로그를 살펴보았으나, 문제가 존재하지 않아
SHOW DATABASES;
USE keystone;
SHOW TABLES;
SELECT * FROM user;
+----------------------------------+-------+---------+--------------------+---------------------+----------------+-----------+
| id | extra | enabled | default_project_id | created_at | last_active_at | domain_id |
+----------------------------------+-------+---------+--------------------+---------------------+----------------+-----------+
| 0114d4f75c8d44bd8a38ec2b77377d9d | {} | 1 | NULL | 2025-01-30 07:37:59 | NULL | default |
| 12529d0a6bfa4cb1894172f0fe354de9 | {} | 1 | NULL | 2025-01-26 10:22:02 | NULL | default |
| 597d8fb3ab914e0783bf6eaecef35963 | {} | 1 | NULL | 2025-01-29 11:34:42 | NULL | default |
| 83110e40de9742ce87f8d83f02133151 | {} | 1 | NULL | 2025-01-26 11:06:37 | NULL | default |
| bfdfdbc65be14efb9bddbafdd6c489a6 | {} | 1 | NULL | 2025-01-29 11:45:43 | NULL | default |
+----------------------------------+-------+---------+--------------------+---------------------+----------------+-----------+
openstack user list
+----------------------------------+-----------+
| ID | Name |
+----------------------------------+-----------+
| 12529d0a6bfa4cb1894172f0fe354de9 | admin |
| 83110e40de9742ce87f8d83f02133151 | glance |
| 597d8fb3ab914e0783bf6eaecef35963 | placement |
| bfdfdbc65be14efb9bddbafdd6c489a6 | nova |
| 0114d4f75c8d44bd8a38ec2b77377d9d | neutron |
+----------------------------------+-----------+
추가적으로 DB를 확인해주었고 neutron의 0114d4f75c8d44bd8a38ec2b77377d9d 아이디에 대한 데이터가 존재함을 확인하였다.
$ curl -i http://controller:9696/v2.0/agents
HTTP/1.1 401 Unauthorized
Content-Type: application/json
Content-Length: 114
Www-Authenticate: Keystone uri="http://controller:5000"
X-Openstack-Request-Id: req-75641407-fe06-46a7-b436-40f934b67253
Date: Thu, 30 Jan 2025 09:51:45 GMT
{"error": {"code": 401, "title": "Unauthorized", "message": "The request you have made requires authentication."}
하지만 여전히 문제가 발생하여, http://controller:9696/v2.0/agents로 직접 요청을 보내봤을 때 Unauthorized 에러 코드를 받을 수 있었으며
$ systemctl status memcached
× memcached.service - memcached daemon
Loaded: loaded (/usr/lib/systemd/system/memcached.service; enabled; preset>
Active: failed (Result: exit-code) since Thu 2025-01-30 00:01:20 EST; 4h 5>
Duration: 40ms
Main PID: 709 (code=exited, status=71)
CPU: 14ms
$ systemctl restart memcached
Keystone이 Memcached를 사용해 인증을 캐싱하기 때문에 Memcached가 중지되면 Keystone 인증이 실패할 수 있기에 중지된 Memcached 시작해주었다.
$ tail -n 10 /var/log/neutron/server.log
2025-01-30 05:03:21.810 120577 ERROR oslo_service.service amqp.exceptions.NotAllowed: Connection.open: (530) NOT_ALLOWED - access to vhost '/' refused for user 'openstack'
2025-01-30 05:03:21.810 120577 ERROR oslo_service.service
/var/log/neutron/server.log 로그를 통해 Neutron이 RabbitMQ에 접속하려다 거부된 상황임을 확인하였고
$ vim /etc/neutron/neutron.conf
[DEFAULT]
core_plugin = ml2
service_plugins = router
# transport_url = rabbit://openstack:o5vKi2rnhxB@controller
transport_url = rabbit://openstack:o5vKi2rnhxB@controller:5672/
...
설치 도중 /etc/neutron/neutron.conf가 잘못 설정되어 있는 것을 확인하고
$ rabbitmqctl list_users
Listing users ...
user tags
openstack []
guest [administrator]
$ rabbitmqctl set_permissions -p / openstack ".*" ".*" ".*"
rabbitmqctl을 통해 openstack 유저의 permission을 설정해준 뒤
systemctl stop neutron-server.service \
neutron-openvswitch-agent.service neutron-dhcp-agent.service \
neutron-metadata-agent.service \
neutron-l3-agent.service
systemctl start neutron-server.service \
neutron-openvswitch-agent.service neutron-dhcp-agent.service \
neutron-metadata-agent.service \
neutron-l3-agent.service
neutron 관련 서비스들을 모두 재시작 해주었다.
$ tail -n 30 /var/log/neutron/server.log
2025-01-30 05:19:29.104 132363 INFO oslo_service.service [None req-40929f16-5829-4ed1-80c1-b86f9478df5d - - - - - -] Starting 2 workers
2025-01-30 05:19:29.111 132363 INFO neutron.service [None req-40929f16-5829-4ed1-80c1-b86f9478df5d - - - - - -] Neutron service started, listening on 0.0.0.0:9696
2025-01-30 05:19:29.113 132374 INFO neutron.wsgi [-] (132374) wsgi starting up on http://0.0.0.0:9696
2025-01-30 05:19:29.114 132363 INFO oslo_service.service [None req-40929f16-5829-4ed1-80c1-b86f9478df5d - - - - - -] Starting 1 workers
2025-01-30 05:19:29.115 132375 INFO neutron.wsgi [-] (132375) wsgi starting up on http://0.0.0.0:9696
2025-01-30 05:19:29.117 132363 INFO oslo_service.service [None req-40929f16-5829-4ed1-80c1-b86f9478df5d - - - - - -] Starting 1 workers
2025-01-30 05:19:29.122 132363 INFO oslo_service.service [None req-40929f16-5829-4ed1-80c1-b86f9478df5d - - - - - -] Starting 1 workers
2025-01-30 05:20:13.274 132375 WARNING keystonemiddleware.auth_token [-] Identity response: {"error":{"code":401,"message":"The request you have made requires authentication.","title":"Unauthorized"}}
: keystoneauth1.exceptions.http.Unauthorized: The request you have made requires authentication. (HTTP 401) (Request-ID: req-7c8dc5d3-63d0-4339-abbc-221e8aebf573)
2025-01-30 05:20:13.663 132375 WARNING keystonemiddleware.auth_token [-] Identity response: {"error":{"code":401,"message":"The request you have made requires authentication.","title":"Unauthorized"}}
: keystoneauth1.exceptions.http.Unauthorized: The request you have made requires authentication. (HTTP 401) (Request-ID: req-eecf93b8-c067-48bc-b09f-f98d0419ad92)
2025-01-30 05:20:13.664 132375 CRITICAL keystonemiddleware.auth_token [-] Unable to validate token: Identity server rejected authorization necessary to fetch token data: keystonemiddleware.auth_token._exceptions.ServiceError: Identity server rejected authorization necessary to fetch token data
2025-01-30 05:20:13.666 132375 INFO neutron.wsgi [-] 192.168.100.101 "GET /v2.0/agents HTTP/1.1" status: 503 len: 434 time: 0.7935288
그럼에도 authorization 관련 오류가 발생하는 것을 로그를 통해 추가적으로 확인하여
# /etc/neutron/neutron.conf
[keystone_authtoken]
www_authenticate_uri = http://controller:5000
auth_url = http://controller:5000
memcached_servers = controller:11211
auth_type = password
project_domain_name = Default
user_domain_name = Default
project_name = admin
username = neutron
password = neutron
region_name = RegionOne
service_token_roles_required = True
service_token_roles_required를 /etc/neutron/neutron.conf에 추가해주고
openstack role add --project admin --user neutron admin
openstack의 neutron 유저에 대해 admin 권한을 추가해주고
systemctl restart neutron-server
neutron-server를 재시작해준 뒤
$ openstack network agent list
+--------------------------------------+----------------+------------+-------------------+-------+-------+------------------------+
| ID | Agent Type | Host | Availability Zone | Alive | State | Binary |
+--------------------------------------+----------------+------------+-------------------+-------+-------+------------------------+
| 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 |
+--------------------------------------+----------------+------------+-------------------+-------+-------+------------------------+
위와 같이 명령어는 정상적으로 동작하는 것을 확인할 수 있었다.
openstack network agent list
+--------------------------------------+--------------------+------------+-------------------+-------+-------+---------------------------+
| ID | Agent Type | Host | Availability Zone | Alive | State | Binary |
+--------------------------------------+--------------------+------------+-------------------+-------+-------+---------------------------+
| 5714217b-89ba-416f-8c16-ee556ed3e8e2 | Open vSwitch agent | controller | None | XXX | 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 |
+--------------------------------------+--------------------+------------+-------------------+-------+-------+---------------------------+
하지만 여전히 Open vSwitch agent가 Alive XXX 상태가 되는 문제가 존재하였다.
$ tail -f /var/log/neutron/openvswitch-agent.log
...
ERROR neutron oslo_privsep.daemon.FailedToDropPrivileges: privsep helper command exited non-zero (1)
이에 openvswitch-agent 로그를 확인한 뒤 FailedToDropPrivileges 오류가 발생하는 것을 확인하고
$ vim /etc/selinux/config
...
SELINUX=disabled
우선 SELinux의 setenforce를 Permissive로 변경해주었다.
$ ls -l /etc/neutron/rootwrap.conf
-rw-r--r--. 1 root root 1174 Apr 26 2024 /etc/neutron/rootwrap.conf
또한 /etc/neutron/rootwrap.conf에 대한 권한을 확인해주었다.
chown root:root /etc/neutron/rootwrap.conf
chmod 644 /etc/neutron/rootwrap.conf
만약 /etc/neutron/rootwrap.conf 파일의 권한이 정상적으로 설정되어 있지 않다면 위와 같이 변경하면 된다.
$ visudo
## Next comes the main part: which users can run what software on
## which machines (the sudoers file can be shared between multiple
## systems).
## Syntax:
##
## user MACHINE=COMMANDS
##
## The COMMANDS section may have other options added to it.
##
## Allow root to run any commands anywhere
root ALL=(ALL) ALL
neutron ALL=(ALL) NOPASSWD: ALL
이후 visudo를 통해 neutron Linux User에 대한 권한을 설정해주고
$ cat /etc/neutron/rootwrap.conf
# Configuration for neutron-rootwrap
# This file should be owned by (and only-writeable by) the root user
[DEFAULT]
# List of directories to load filter definitions from (separated by ',').
# These directories MUST all be only writeable by root !
filters_path=/etc/neutron/rootwrap.d,/usr/share/neutron/rootwrap
추가적으로 /etc/neutron/rootwrap.conf의 filters_path가 정상적으로 설정되어 있는 것을 확인해주었다.
$ vim /etc/nova/nova.conf
[DEFAULT]
enabled_apis = osapi_compute,metadata
transport_url = rabbit://openstack:o5vKi2rnhxB@controller:5672/
my_ip = 192.168.100.101
rootwrap_config=/etc/nova/rootwrap.conf
이후 /etc/nova/nova.conf에서 rootwrap_config를 설정하고
$ vim /etc/neutron/neutron.conf
[DEFAULT]
core_plugin = ml2
service_plugins = router
transport_url = rabbit://openstack:o5vKi2rnhxB@controller:5672/
auth_strategy = keystone
notify_nova_on_port_status_changes = true
notify_nova_on_port_data_changes = true
root_helper = sudo neutron-rootwrap /etc/neutron/rootwrap.conf
또한 /etc/neutron/neutron.conf에 root_helper 관련 변수를 설정한 이후
systemctl restart neutron-server.service
systemctl restart neutron-openvswitch-agent.service
neutron-server와 neutron-openvswitch-agent를 재시작하여 문제를 해결하였다.
tail -f /var/log/neutron/openvswitch-agent.log
...
2025-02-02 04:46:01.966 10140 INFO neutron.plugins.ml2.drivers.openvswitch.agent.ovs_neutron_agent [-] Mapping physical network provider to bridge enp1s0
2025-02-02 04:46:01.966 10140 ERROR neutron.plugins.ml2.drivers.openvswitch.agent.ovs_neutron_agent [-] Bridge enp1s0 for physical network provider does not exist. Agent terminated!
이후 Bridge enp1s0 for physical network provider does not exist 오류를 확인할 수 있었는데, 이전에 provider로 설정한 enp1s0의 경우 일반 물리 NIC 인터페이스이기에 OVS Bridge를 필요로 하는 openvswitch-agent에서 에러 로그를 발생시킨 것이었다.
ovs-vsctl add-br br-ex
따라서 OVS Bridge(br-ex)를 생성해주었다.
ovs-vsctl add-port br-ex enp1s0
만약 Provider 네트워크로 네트워크를 구성하고자 한다면 위와 같이 물리 NIC를 OVS Bridge에 포트로 연결한 이후 IP는 Bridge(br-ex) 혹은 내부 인터페이스(type=OVSIntPort)에 할당하면 된다.
### HOST
$ virsh net-edit opstnat
<name>opstnat</name>
...
<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='52:54:00:ef:ea:07' name='controller' ip='192.168.100.101'/>
</dhcp>
또한 물리 NIC를 OVS Bridge에 포트로 연결한 이후 현재 KVM NAT Network에서 enp1s0 언티페이스에 고정 IP를 할당하고 있기 때문에, 해당 MAC 주소를 br-ex로 변경해주어야 한다.
$ 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 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"
이후 ovs-vsctl show 명령어를 통해 ovs의 구성이 올바른 것을 확인하였다.
$ vim /etc/neutron/plugins/ml2/openvswitch_agent.ini
[ovs]
bridge_mappings = provider:br-ex
local_ip = 192.168.100.101
또한 /etc/neutron/plugins/ml2/openvswitch_agent.ini에서 bridge_mappings를 br-ex로 변경하고
systemctl restart neutron-openvswitch-agent.service
neutron-openvswitch-agent를 재시작하면
openstack network agent list
+--------------------------------------+--------------------+------------+-------------------+-------+-------+---------------------------+
| ID | Agent Type | Host | Availability Zone | Alive | State | Binary |
+--------------------------------------+--------------------+------------+-------------------+-------+-------+---------------------------+
| 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 |
+--------------------------------------+--------------------+------------+-------------------+-------+-------+---------------------------+
모든 network agent들이 정상 작동하는 것을 볼 수 있다.
Neutron에서 Self Service Network(=L3 NAT) 구조라면, 실제 외부 트래픽은 qrouter-xxxx Namspace의 qg-xxxx 인터페이스로 NAT되므로, 호스트 OS 레벨에서는 enp1s0에 IP를 할당한 상태로 외부 접근이 가능하고, Neutron이 필요할 경우 별도 OVS Bridge를 만들어(ex. br-int, br-ex), br-ex ↔ patch-port 방식으로 Neutron L3 Agent에서 Tenant 네트워크를 처리한다.
이는 Self Service 네트워크에서 “br-ex ↔ patch-port ↔ br-int ↔ qrouter-namespace ↔ VM” 흐름을 나타내며 호스트 레벨에서 별도 IP 없이 OVS만 연결(Neutron L3 Agent가 NAT)할 수 있다는 의미이다.
yum install -y openstack-dashboard
우선 위와 같이 openstack-dashboard 패키지를 설치해주고
vim /etc/openstack-dashboard/local_settings
나머지 설정은 공식 DOC에 따라 진행하고
https://docs.openstack.org/horizon/2024.2/install/install-rdo.html
$ vim /etc/httpd/conf.d/openstack-dashboard.conf
WSGIApplicationGroup %{GLOBAL}
만약 openstack-dashboard.conf에 위 코드가 존재하지 않으면 삽입해주고
systemctl restart httpd.service memcached.service
위와 같이 httpd와 memcached를 재시작해준다.
curl http://controller/dashboard
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>403 Forbidden</title>
</head><body>
<h1>Forbidden</h1>
<p>You don't have permission to access this resource.</p>
</body></html>
하지만 위와 같이 403 Forbidden 오류가 발생하여
$ vim /etc/httpd/conf.d/openstack-dashboard.conf
WSGIDaemonProcess dashboard
WSGIProcessGroup dashboard
WSGISocketPrefix run/wsgi
WSGIScriptAlias /dashboard /usr/share/openstack-dashboard/openstack_dashboard/wsgi.py
Alias /dashboard/static /usr/share/openstack-dashboard/static
<Directory /usr/share/openstack-dashboard/openstack_dashboard/wsgi.py>
Options FollowSymLinks
AllowOverride None
Require all granted
</Directory>
<Directory /usr/share/openstack-dashboard/static>
Options FollowSymLinks
AllowOverride None
Require all granted
</Directory>
WSGIApplicationGroup %{GLOBAL}
/etc/httpd/conf.d/openstack-dashboard.conf에서 잘못된 경로를 위와 같이 수정해주었다.
django.wsgi 파일이 실제 어디에 위치하는지 먼저 확인한 후 만약 없다면 Horizon 설치 경로에서 wsgi.py 파일 경로를 확인하여 수정한다.
curl -I controller/dashboard
HTTP/1.1 302 Found
Date: Mon, 03 Feb 2025 10:44:16 GMT
Server: Apache/2.4.62 (Rocky Linux) mod_wsgi/4.7.1 Python/3.9
Location: http://controller/auth/login/?next=/dashboard/
X-Frame-Options: DENY
Vary: Accept-Language,Cookie
Content-Language: en
Content-Type: text/html; charset=utf-8
이후 정상적으로 요청이 전달되는 것을 확인할 수 있다.
Horizon Dashboard에 접근하기 위하여 Host (Window) -SSH-> HOST(Rocky) -KVM-> VM(Rocky, Openstack)으로 Window Host에서 NAT 네트워크 내부의 KVM에 접근할 수 있도록 포트 포워딩을 진행하려고 한다.
### HOST
sudo yum install -y nginx
sudo systemctl enable nginx
sudo systemctl start nginx
우선 nginx 패키지를 설치해준 이후
### HOST
$ vim /etc/nginx/conf.d/horizon_proxy.conf
server {
listen 8080;
server_name _;
location / {
proxy_pass http://192.168.100.101/dashboard;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
위와 같이 horizon_proxy.conf를 작성한다.
### KVM
$ tail -f /var/log/httpd/error_log
[Wed Feb 05 07:38:04.919703 2025] [wsgi:error] [pid 1747:tid 1938] [remote 192.168.100.1:47914] ERROR django.security.DisallowedHost Invalid HTTP_HOST header: '192.168.100.101'. You may need to add '192.168.100.101' to ALLOWED_HOSTS.
[Wed Feb 05 07:38:04.920197 2025] [wsgi:error] [pid 1747:tid 1938] [remote 192.168.100.1:47914] WARNING django.request Bad Request: /dashboard/
Window Host의 브라우저를 통해 접근하려고 앴을 때 ALLOWED_HOSTS관련 오류가 발생한다면
### CONTROLLER
vim /etc/openstack-dashboard/local_settings
ALLOWED_HOSTS = ['horizon.example.com', 'localhost', "controller", "192.168.100.1", "192.168.100.101", "<HOST_IP>"]
위와 같이 /etc/openstack-dashboard/local_settings에 HOST_IP를 추가하여 해결할 수 있고
### HOST
setenforce 0
systemctl restart nginx
systemctl restart nginx
sudo setsebool -P httpd_can_network_connect 1
Rocky Linux Host에서 위 명령을 실행하여 nginx가 외부 네트워크 요청을 허용하도록 설정하고
### CONTROLLER
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
Controller 방화벽에 http와 https 관련 권한을 허용하였다.
정상적으로 Horizon Django Server에 접근은 되지만 Not Found 오류가 발생하였다.
### CONTROLLER
$ curl -I controller/dashboard
HTTP/1.1 302 Found
Date: Wed, 05 Feb 2025 08:14:43 GMT
Server: Apache/2.4.62 (Rocky Linux) mod_wsgi/4.7.1 Python/3.9
Location: http://controller/auth/login/?next=/dashboard/
X-Frame-Options: DENY
Vary: Accept-Language,Cookie
Content-Language: en
Content-Type: text/html; charset=utf-8
$ curl -I http://controller/auth/login/?next=/dashboard/
HTTP/1.1 404 Not Found
Date: Wed, 05 Feb 2025 08:14:56 GMT
Server: Apache/2.4.62 (Rocky Linux) mod_wsgi/4.7.1 Python/3.9
Content-Type: text/html; charset=iso-8859-1
따라서 nginx의 문제인지, Controller의 Horizon의 문제를 파악하기 위해 curl 명령을 통해 이를 검증하였고
### CONTROLLER
$ tail -n 30 /var/log/httpd/error_log
[Wed Feb 05 03:01:10.284869 2025] [autoindex:error] [pid 4208:tid 4401] [client 192.168.100.1:59440] AH01276: Cannot serve directory /var/www/html/: No matching DirectoryIndex (index.html) found, and server-generated directory index forbidden by Options directive
[Wed Feb 05 03:01:11.049002 2025] [autoindex:error] [pid 4208:tid 4405] [client 192.168.100.1:59454] AH01276: Cannot serve directory /var/www/html/: No matching DirectoryIndex (index.html) found, and server-generated directory index forbidden by Options directive
[Wed Feb 05 03:05:13.930351 2025] [autoindex:error] [pid 4455:tid 4492] [client 192.168.100.1:34560] AH01276: Cannot serve directory /var/www/html/: No matching DirectoryIndex (index.html) found, and server-generated directory index forbidden by Options directive
추가로 /var/log/httpd/error_log에서 /var/www/html/: No matching DirectoryIndex 관련 오류를 파악할 수 있었다.
### CONTROLLER
$ vim /etc/openstack-dashboard/local_settings
...
DEBUG = False
# This setting controls whether or not compression is enabled. Disabling
# compression makes Horizon considerably slower, but makes it much easier
# to debug JS and CSS changes
#COMPRESS_ENABLED = not DEBUG
# This setting controls whether compression happens on the fly, or offline
# with `python manage.py compress`
# See https://django-compressor.readthedocs.io/en/latest/usage/#offline-compresss
ion
# for more information
#COMPRESS_OFFLINE = not DEBUG
WEBROOT = '/dashboard'
이는 Horizon의 버전이 업데이트 되면서 Django 서버의 기본 Root 주소인 WEBROOT 설정이 누락되었다고 판단하여, 이를 /etc/openstack-dashboard/local_settings에 추가해어 이 문제를 해결해줄 수 있었다.
### CONTROLLER
$ tail -n 10 /var/log/httpd/error_log
...
Target WSGI script not found or unable to stat: /usr/share/openstack-dashboard/openstack_dashboard/wsgi.pydashboard
하지만 이후에도 여전히 WSGI에서 wsgi.pydashboard와 같이 wsgi.py에 대한 경로를 잘못 라우팅 하고 있음을 확인하였다.
### CONTROLLER
$ vim /etc/httpd/conf.d/openstack-dashboard.conf
WSGIDaemonProcess dashboard
WSGIProcessGroup dashboard
WSGISocketPrefix run/wsgi
WSGIApplicationGroup %{GLOBAL}
WSGIScriptAlias /dashboard /usr/share/openstack-dashboard/openstack_dashboard/wsgi.py
Alias /dashboard/static/ /usr/share/openstack-dashboard/static/
<Directory /usr/share/openstack-dashboard/openstack_dashboard>
Options All
AllowOverride All
Require all granted
</Directory>
<Directory /usr/share/openstack-dashboard/static>
Options All
AllowOverride All
Require all granted
</Directory>
따라서 /etc/httpd/conf.d/openstack-dashboard.conf를 위와 같이 수정하였다.
WSGIScriptAlias /dashboard로 설정하면 /dashboard로의 요청이 정확히 /usr/share/openstack-dashboard/openstack_dashboard/wsgi.py 파일로 매핑되고, 추가 PATH_INFO가 붙지 않아 정상적인 접근이 가능하다.
이외에도 기존에 잘못 설정되어 있었던 <Directory>와 관련 Options 들을 수정하였다.
### CONTROLLER
$ cd /usr/share/openstack-dashboard
python manage.py collectstatic
python manage.py compress
이때 STATIC_URL을 변경하였기 때문에, static 파일 경로를 갱신하기 위해 위 명령어를 실행하고
### CONTROLLER
$ chmod 777 /usr/share/openstack-dashboard/openstack_dashboard/wsgi.py
$ ll /usr/share/openstack-dashboard/openstack_dashboard/ | grep wsgi
-rwxrwxrwx. 1 root root 973 Apr 30 2024 wsgi.py
추가적인 wsgi.py 접근 권한 문제를 방지하기 위해 해당 파일에 대한 mode를 -rwxrwxrwx로 변경해주었다
### HOST
$ cat /etc/nginx/conf.d/horizon_proxy.conf
server {
listen 8080;
server_name _;
location / {
proxy_pass http://192.168.100.101;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
이후 /etc/nginx/conf.d/horizon_proxy.conf의 proxy_pass를 위와 같이 변경한다.
이전의 /dashboard는 WEBROOT를 통해 자동으로 지정되기 때문에 이를 제거하였다.
### HOST
systemctl restart httpd.service
### CONTROLLER
systemctl restart nginx.service
각각의 머신에서 httpd와 nginx 서비스를 재시작하면
정상적으로 Window Host에서 브라우저를 통해 KVM의 Horizon Dashboard로 접근이 가능한 것을 볼 수 있다.
CREATE DATABASE cinder;
GRANT ALL PRIVILEGES ON cinder.* TO 'cinder'@'localhost' \
IDENTIFIED BY 'CINDER_DBPASS';
GRANT ALL PRIVILEGES ON cinder.* TO 'cinder'@'%' \
IDENTIFIED BY 'CINDER_DBPASS';
우선 Block Storage 서비스를 설치하기 위해 위와 같이 DB를 정의하고
openstack user create --domain default --password-prompt cinder
User Password: # cinder
Repeat User Password: # cinder
openstack의 user를 생성한다.
openstack role add --project admin --user cinder admin
openstack service create --name cinderv3 \
--description "OpenStack Block Storage" volumev3
openstack endpoint create --region RegionOne \
volumev3 public http://controller:8776/v3/%\(project_id\)s
openstack endpoint create --region RegionOne \
volumev3 internal http://controller:8776/v3/%\(project_id\)s
openstack endpoint create --region RegionOne \
volumev3 admin http://controller:8776/v3/%\(project_id\)s
이후 위와 같이 role과 endpoint를 생성한 이후
yum install -y openstack-cinder
openstack-cinder 패키지를 설치해준다.
vim /etc/cinder/cinder.conf
이후의 /etc/cinder/cinder.conf를 설정하는 단계는 아래의 레퍼런스를 따라 진행하면 된다/
https://docs.openstack.org/cinder/2023.1/install/cinder-controller-install-rdo.html
su -s /bin/sh -c "cinder-manage db sync" cinder
이후 cinder 데이터베이스를 sync해주고
$ vim /etc/nova/nova.conf
...
[cinder]
os_region_name = RegionOne
Block Storage의 경우 Instance에서 접근하기에, /etc/nova/nova.conf 설정을 변경해주고
systemctl enable openstack-cinder-api.service openstack-cinder-scheduler.service
systemctl start openstack-cinder-api.service openstack-cinder-scheduler.service
위와 같이 서비스들을 활성화시켜주면
$ openstack volume service list
+------------------+------------+------+---------+-------+----------------------------+
| Binary | Host | Zone | Status | State | Updated At |
+------------------+------------+------+---------+-------+----------------------------+
| cinder-scheduler | controller | nova | enabled | up | 2025-02-06T11:16:49.000000 |
+------------------+------------+------+---------+-------+----------------------------+
Volume Service들이 정상적으로 구동되는 모습을 볼 수 있다.
yum install -y lvm2 device-mapper-persistent-data
레퍼런스의 경우 Controller Node와 Storage Node를 별도로 구성하도록 하지만, 현재는 Controller와 Compute Node만 활용할 계획이기 때문에 cinder-volume 서비스를 Controller Node에 설치한다.
dd if=/dev/zero of=/var/lib/cinder/cinder-disk.img bs=1M count=10240
공식 Doc와는 다르게 현재 KVM에서 Openstack을 구동하고 있으므로 우선 위와 같이 디스크 이미지 파일을 생성한다.
if=/dev/zero: 0으로 채워진 데이터를 입력of=/var/lib/cinder/cinder-disk.img: 이미지 파일 저장 경로bs=1M: 블록 사이즈 1MBcount=5120: 총 10240 블록 (10GB)losetup /dev/loop1 /var/lib/cinder/cinder-disk.img
이후 /dev/loop1을 새로 생성한 디스크 이미지에 매핑하여, 물리 디바이스처럼 사용할 수 있도록 하고
pvcreate /dev/loop1
/dev/loop1을 대상으로 LVM Physical Volume을 생성한다.
vgcreate cinder-volumes /dev/loop1
이후 vgcreate을 통해 /dev/loop1에 있는 스토리지 공간을 cinder-volumes라는 하나의 논리적 그룹으로 묶는다.
Cinder는 이 볼륨 그룹 내에서 Logical Volume을 생성하고 관리하여 인스턴스에 블록 스토리지를 제공하게 된다.
또한 현재 10GB밖에 할당하지 않았지만 추후 Host에서 추가적으로 KVM에 Storage를 할당하여 추가적으로 Volume을 할당할 계획이다.
$ vim /etc/lvm/lvm.conf
...
devices {
filter = [ "a/loop1/", "r/.*/" ]
...
인스턴스만 블록 스토리지 볼륨에 접근할 수 있으나 그러나 기본 운영 체제는 이 볼륨과 연관된 디바이스들을 관리한다.
기본적으로 LVM 볼륨 스캐닝 도구는 볼륨이 포함된 블록 스토리지 디바이스를 찾기 위해 /dev 디렉토리를 스캔하는데 만약 프로젝트가 자신의 볼륨에 LVM을 사용한다면, 스캐닝 도구는 이러한 볼륨들을 감지하여 캐시하려 시도할 수 있다.
이로 인해 기본 운영 체제와 프로젝트 볼륨 모두에서 다양한 문제가 발생할 수 있기 때문에 LVM이 cinder-volumes 볼륨 그룹을 포함하는 디바이스만 스캔하도록 재구성해야 한다.
a/loop1/: /dev/loop1 장치를 acceptr/.*/: 그 외의 모든 장치를 rejectyum install -y targetcli
이후 targetcli 패키지를 설치하고
$ vim /etc/cinder/cinder.conf
[DEFAULT]
...
enabled_backends = lvm
glance_api_servers = http://controller:9292
...
[lvm]
volume_driver = cinder.volume.drivers.lvm.LVMVolumeDriver
volume_group = cinder-volumes
target_protocol = iscsi
target_helper = lioadm
...
공식 레퍼런스에 따라 [DEFAULT], [lvm] 관련 설정을 추가하고
https://docs.openstack.org/cinder/2023.1/install/cinder-storage-install-rdo.html
systemctl enable openstack-cinder-volume.service target.service
systemctl start openstack-cinder-volume.service target.service
위와 같이 서비스들을 활성화하면
openstack volume service list
+------------------+----------------+------+---------+-------+----------------------------+
| Binary | Host | Zone | Status | State | Updated At |
+------------------+----------------+------+---------+-------+----------------------------+
| cinder-scheduler | controller | nova | enabled | up | 2025-02-06T11:56:50.000000 |
| cinder-volume | controller@lvm | nova | enabled | up | 2025-02-06T11:56:43.000000 |
+------------------+----------------+------+---------+-------+----------------------------+
최종적으로 cinder-scheduler, cinder-volume이 정상적으로 구동되고 있음을 확인할 수 있으며
Horizon Dashboard를 통해 이를 확인할 수도 있다.
이로써 Controller Node에 대한 구성이 완료되었다.