Fullmoon System

The Operations Loop 구축기 — PXE에서 NetBox·AWX·Zabbix까지

AI_Manager

이 글은 VirtualBox의 네 내부망 위에 서버 설치·자산·자동화·관제를 연결한 실제 구축 절차다. 중앙 서비스는 fml-ops-01/02에 함께 배치하되 Compose와 K3s로 관리 단위를 나눴다. 각 단계에 온라인망과 폐쇄망 절차를 함께 적었다.

명령은 이 실습의 주소와 고정 버전을 기준으로 한다. 비밀번호·토큰·SSH 개인키는 예시나 Git에 포함하지 않는다. 구성 파일의 자리표시자는 자신의 비밀 저장소에서 주입한다. 실행한 범위와 실제 결과는 검증 글을 기준으로 확인한다.

이 글의 순서

1. 버전과 설치 순서

구성 요소 사용 버전·방식 선택 이유
가상화 / OS VirtualBox 7.2.18 / Rocky Linux 10.2 실제 PC에서 재현 가능한 Linux 실습
Docker / Compose 29.8.1 / 5.5.1 중앙·Proxy의 컨테이너 관리
Zabbix 7.0.30 LTS Server/Web/Proxy/Agent 2 Server와 Proxy의 버전 일치, 정식 LTS 패치 고정
NetBox 4.7.1, netbox-docker 5.1.1 두 App/Worker와 API 기반 자산 관리
PostgreSQL / Patroni PostgreSQL 15 / Patroni 4.1.5 중앙 DB 복제·역할 전환
Redis / etcd Redis 7.4 / etcd 3.7.1 Sentinel과 분산 장애 판정
AWX / Operator 24.6.1 / 2.19.1 공식 Operator 설치 방식
K3s v1.37.0+k3s1 단일 VM의 AWX 실행 기반
NetBox collection netbox.netbox 3.23.0 NetBox v2 Bearer 토큰을 사용하는 inventory plugin

설치 순서는 OS·네트워크 → DNS/NTP·CA → etcd/PostgreSQL/Redis → 접속 VIP → NetBox/Zabbix → 환경별 Proxy/Bastion → K3s/AWX·Git → PXE 대상 → 자동화 연동 → 장애/복원 검증이다. 뒤 단계의 UI가 뜨더라도 앞 단계의 DB 역할·이름 해석·시간 동기화가 정상인지 먼저 확인한다.

‘최신’은 2026-09-20 구축 기준의 고정 버전이다. 이후 재구축에서는 지원 정책과 이미지 digest를 다시 확인하고, 이 글의 버전과 새 버전을 섞어 설치하지 않는다. Zabbix의 최신 일반 릴리스와 최신 정식 LTS는 구분한다.

2. VM과 네트워크 준비

CORE는 10.77.10.0/24, PROD는 10.77.20.0/24, DEV는 10.77.30.0/24, STG는 10.77.40.0/24다. 각각 fml-core/prod/dev/stg Internal Network로 만든다. Gateway와 Provision에는 네 내부망 NIC를 붙이고, Edge와 대상은 자신의 환경망에 붙인다.

온라인망에서 구성 시

공식 ISO의 체크섬을 확인하고 Rocky 최소 설치 템플릿을 만든다. 관리 VM에는 설치 기간 NAT NIC와 내부망 NIC를 분리해 사용한다. 호스트의 SSH 포트 포워딩은 127.0.0.1에만 바인딩한다. 대상 PXE VM에는 NAT를 붙이지 않는다.

$VBox = 'C:\Program Files\Oracle\VirtualBox\VBoxManage.exe'
& $VBox list vms
& $VBox showvminfo fml-ops-01 --machinereadable
# 대상은 승인된 빈 디스크에만 새로 생성한다.
.\lab-v2\Create-PxeTarget.ps1 -Environment prod

템플릿 복제 시 hostname, machine-id, SSH host key, MAC을 각각 새로 만든다. 원본 템플릿의 고정 주소·키·machine-id가 여러 VM에 남지 않도록 확인한다. SELinux Enforcing과 firewalld를 유지하고 root 원격 로그인·암호 로그인을 제한한다.

폐쇄망에서 구성 시

ISO, 검증된 설치 패키지, 내부 CA, 공개키와 구성 템플릿을 반입한 뒤 같은 내부망 구조를 만든다. NAT NIC를 연결하지 않은 상태에서도 OS 설치와 내부 DNS/NTP에 접근할 수 있어야 한다. ‘NAT를 나중에 끈 기존 설치’와 ‘필요한 의존성을 반입해 처음부터 설치’를 다른 시험으로 기록한다.

이 PC에서는 새 BIOS PXE 대상의 2 vCPU 부팅이 정지해 1 vCPU를 사용했다. 중앙 1호기는 Intel Core i7-6700K CPU profile에서 4 vCPU로 실행했다. 이 프로파일명은 실제 PC CPU가 아니다. Hyper-V/VBS/메모리 무결성은 변경하지 않았다. 자세한 VM 문제는 별도 블로그에서 다룬다.

3. 라우팅·DNS·NTP·접근 경로

환경 간 통신은 Gateway의 firewalld policy로 허용한다. Provision은 각 환경에 직접 연결되어 DHCP·PXE·저장소를 제공하며 라우터로 사용하지 않는다.

출발 목적지 포트 목적
중앙 .11/.12 환경 Edge .10 TCP 22 Bastion 진입
환경 Edge .10 승인된 대상 .101 TCP 22 SSH forwarding
환경 대상 같은 환경 Edge .10 TCP 10051 Agent Active 데이터
환경 Edge .10 중앙 .11/.12 TCP 10051 Active Proxy와 HA Server
승인된 환경망 VIP 10.77.10.10 TCP 443 NetBox/Zabbix/AWX API·웹
환경 대상 같은 환경 Provision .20 DNS 53, NTP 123, HTTP 80, PXE 관련 포트 설치·이름 해석·시간·내부 저장소
중앙·quorum 해당 내부 서비스 노드 etcd 2379/2380, Patroni 8008, PG 5432/6432, Redis 6379/26379, NFS 2049 데이터 복제·판정·공유 미디어

위 표는 통신 목적 요약이다. 실제 허용 규칙은 호스트 방화벽과 라우터 정책을 함께 확인한다. DB·etcd·Redis를 인터넷이나 모든 환경망에 공개하지 않는다.

온라인망에서 구성 시

관리 VM에 필요한 패키지를 설치하고 내부 인터페이스와 정적 경로를 만든다. 인터넷용 default route와 실습망의 정적 경로를 섞지 않는다.

nmcli connection modify fml-internal \
  +ipv4.routes '10.77.20.0/24 10.77.10.1'
nmcli connection modify fml-internal \
  +ipv4.routes '10.77.30.0/24 10.77.10.1'
nmcli connection modify fml-internal \
  +ipv4.routes '10.77.40.0/24 10.77.10.1'
nmcli device reapply enp0s8
ip route
chronyc tracking

폐쇄망에서 구성 시

내부 DNS에서 netbox.fullmoon.test, zabbix.fullmoon.test, awx.fullmoon.test를 VIP 10.77.10.10으로, git.fullmoon.test를 10.77.10.12로 해석한다. 외부 DNS fallback에 의존하지 않는다. Provision의 chrony를 내부 기준으로 쓰되 실제 운영에서는 별도 검증한 시간원과 동기화 정책을 준비한다.

getent hosts netbox.fullmoon.test git.fullmoon.test
chronyc sources -v
ip route get 10.77.20.10

브라우저 접속은 실습 CA를 신뢰하는 관리 단말에서 HTTPS 이름으로 한다. Windows에서 내부망을 직접 라우팅하지 않을 때는 loopback SSH tunnel을 사용할 수 있다. 이미 사용하는 로컬 443 포트가 없는지 먼저 확인한다.

ssh -i <관리용_개인키> -p 22031 -N -L 127.0.0.1:443:10.77.10.10:443 labadmin@127.0.0.1

관리 단말의 hosts에는 세 서비스 이름을 127.0.0.1로 연결하고, 검증한 공개 CA 인증서를 신뢰 저장소에 등록해야 한다. 서버 CA 개인키는 가져오지 않는다. 중앙 1호기 장애 시험 중에도 접근하려면 중앙 2호기의 관리 SSH 포트 22032를 사용한다. 인증서 경고를 무시하는 방식은 정상 접속 절차로 삼지 않는다.

4. 컨테이너 런타임과 반입 묶음

온라인망에서 구성 시

Docker 공식 RHEL 저장소에서 Docker/Compose를 설치한다. Compose 프로젝트 경로는 /opt/fullmoon-lab/core, /opt/fullmoon-lab/apps, /opt/fullmoon-lab/edge로 구분한다. 민감한 환경 파일은 root 소유 0600, 상위 디렉터리는 0700으로 둔다.

docker version
docker compose version
docker compose --project-directory /opt/fullmoon-lab/core \
  -f /opt/fullmoon-lab/core/compose.yml config --quiet
docker image inspect --format '{{json .RepoDigests}}' \
  zabbix/zabbix-server-pgsql:alpine-7.0.30

렌더링된 compose 전체 출력에는 비밀번호가 포함될 수 있으므로 공개 로그에 config 내용을 저장하지 않는다. 호스트 SELinux Enforcing 여부와 Docker daemon의 SELinux integration 활성 여부도 구분한다. 이 실습은 호스트 Enforcing을 유지하지만 Docker SELinux integration까지 적용한 환경으로 주장하지 않는다.

폐쇄망에서 구성 시

같은 CPU 아키텍처·Rocky major release의 연결된 준비 VM에서 RPM과 의존성을 받는다. Docker image archive와 K3s/containerd archive는 별개다. Docker에 이미지를 load했다고 K3s에서 사용할 수 있는 것은 아니다.

# 준비 VM: 필요한 버전의 RPM과 모든 의존성 저장
dnf download --resolve --alldeps --destdir ./rpms \
  docker-ce docker-ce-cli containerd.io docker-compose-plugin
createrepo_c ./rpms
docker image save -o images.tar <반입할_고정_이미지_목록>
sha256sum images.tar > images.tar.sha256

# 폐쇄망 반입 후
sha256sum -c images.tar.sha256
docker image load -i images.tar
k3s ctr images import fullmoon-ee-r2.tar

반입 목록은 OS ISO·RPM 저장소, Docker/Compose, Patroni 빌드 결과, NetBox/Zabbix/Redis/etcd 이미지, K3s 바이너리와 airgap 이미지, Operator/RBAC proxy/AWX/EE 이미지, Git bundle, 설정 템플릿·공개 CA, 서명 키와 체크섬이다. 준비 VM의 캐시 덕분에 우연히 통과하지 않도록 외부 NIC가 없는 새 대상에서 검증한다.

5. PostgreSQL·etcd·Redis 구성

etcd와 Sentinel은 중앙 두 노드와 quorum 노드에 배치한다. PostgreSQL/Patroni와 Redis는 중앙 두 노드에 배치한다. DB는 NetBox·Zabbix·AWX용 계정과 데이터베이스를 분리한다.

온라인망에서 구성 시

고정 이미지를 pull/build하고 노드별 NODE_NAME/NODE_IP, Patroni 설정, Redis/Sentinel 설정, 제한된 비밀 파일을 배치한다. etcd 세 노드가 서로 통신하는지 확인한 뒤 중앙 DB를 시작한다.

docker compose --project-directory /opt/fullmoon-lab/core \
  -f /opt/fullmoon-lab/core/compose.yml --profile central up -d
docker compose --project-directory /opt/fullmoon-lab/core \
  -f /opt/fullmoon-lab/core/compose.yml exec -T postgres \
  patronictl -c /etc/patroni/patroni.yml list
curl --fail http://10.77.10.11:8008/patroni
curl --fail http://10.77.10.12:8008/patroni

quorum 노드는 central profile 없이 etcd·Sentinel만 실행한다. Patroni의 synchronous_mode는 활성화하고 synchronous_mode_strict는 false로 두었다. 복제 상태와 장애 시 커밋 손실 가능성을 운영 조건에 맞춰 판단해야 한다. 두 DB를 각각 수동 primary로 만드는 방식은 사용하지 않는다.

폐쇄망에서 구성 시

Patroni Dockerfile이 pip·apt 등 외부 저장소를 필요로 하므로 온라인 준비 구간에서 이미지를 완성해 반입한다. 현장에서 즉석 빌드하다 인터넷 의존성을 만나는 방식은 피한다. 같은 archive를 두 중앙 노드에 load하고 이미지 ID를 비교한 뒤 동일한 절차로 시작한다.

HAProxy의 DB 접속 포트는 6432다. Patroni /primary가 200을 반환하는 노드만 DB 쓰기 backend로 사용한다. 초기 60초 timeout에서는 유휴 DB 연결이 끊어질 수 있어 DB listener의 client/server timeout을 1시간으로 분리했다. 값은 실제 쿼리·연결 풀 정책에 맞춰 조정한다.

6. VIP·HTTPS와 NetBox 이중화

Keepalived의 VIP는 10.77.10.10, 정상 우선 노드는 ops01이다. 두 노드의 HAProxy는 NetBox 8082, Zabbix Web 8080, AWX 30080으로 연결한다. Node·DB·Redis·VIP의 active 역할은 항상 같은 노드일 필요가 없다.

온라인망에서 구성 시

실습 CA로 세 웹 이름을 SAN에 넣은 인증서를 발급하고 두 중앙 노드에 인증서/키를 제한된 권한으로 배치한다. NetBox App와 Worker에는 같은 SECRET_KEY, API token pepper, Redis Sentinel 설정을 사용한다.

# netbox-configuration.py의 주요 관계
DATABASES = {'default': {
    'ENGINE': 'django.db.backends.postgresql',
    'NAME': 'netbox', 'USER': 'netbox',
    'PASSWORD': '<비밀_파일에서_주입>',
    'HOST': '127.0.0.1', 'PORT': 6432,
}}
# SECRET_KEY / API_TOKEN_PEPPERS는 두 App에서 일치시킨다.
# REDIS tasks/caching은 Sentinel의 service와 각각의 DB 번호를 지정한다.

기본 설정 파일의 실제 버전별 형식은 제공한 NetBox 설정과 공식 문서를 기준으로 사용한다. 환경 변수의 이름만 맞추고 Python 설정에서 읽지 않으면 적용되지 않는다. DB 초기 migration은 먼저 한 노드에서 완료하고 나머지 노드와 Worker를 시작한다.

미디어는 quorum의 /srv/fullmoon/netbox-media를 NFSv4로 공유한다. 두 중앙 노드만 export에 허용하고 root_squash를 유지한다. NetBox 이미지의 실제 UID를 확인해 디렉터리 권한을 맞춘다. 이 Rocky 환경에서 확인한 NFS SELinux boolean은 virt_use_nfs다.

findmnt /srv/fullmoon/netbox-media
haproxy -c -f /etc/haproxy/haproxy.cfg
keepalived -t -f /etc/keepalived/keepalived.conf
systemctl is-active haproxy keepalived
curl --fail https://netbox.fullmoon.test/login/ -o /dev/null

폐쇄망에서 구성 시

NetBox 이미지에는 실행에 필요한 Python 의존성이 포함되어 있어 같은 digest의 이미지를 두 노드에 반입한다. 내부 CA를 호스트뿐 아니라 AWX EE에도 넣는다. 두 노드의 SECRET_KEY/pepper가 달라지지 않도록 비밀의 복구 절차도 준비한다. NFS 중단은 웹 API 성공과 별개로 미디어에 영향을 주므로 따로 시험한다.

실제로 만난 문제는 NetBox health check였다. HTTP/1.1 Host 없이 /login/을 검사하면 NetBox가 400을 반환하고 HAProxy가 모든 backend를 down으로 판정해 VIP가 503을 반환했다. 다음처럼 Host를 지정했다.

backend netbox_ui
    mode http
    option httpchk
    http-check send meth GET uri /login/ ver HTTP/1.1 hdr Host netbox.fullmoon.test
    http-check expect status 200
    server ops01 10.77.10.11:8082 check
    server ops02 10.77.10.12:8082 check

장애 시험에서는 Redis 연결의 기본 재시도로 NetBox 캐시 조회 한 번에 약 55.6초가 걸렸다. 설치된 NetBox 4.7.1과 django-redis 코드를 확인하고, 로컬 Sentinel을 먼저 조회하도록 바꿨다. 캐시는 지원되는 KWARGS에 연결·응답 timeout과 재시도 정책을 명시했다. loopback은 같은 Sentinel의 다른 접속 주소이며 과반수 구성원이 추가된 것은 아니다.

from redis.backoff import NoBackoff
from redis.retry import Retry

# 각 NetBox 노드의 로컬 Sentinel을 첫 번째로 조회한다.
SENTINELS = [('127.0.0.1', 26379), ('10.77.10.13', 26379),
             ('10.77.10.11', 26379), ('10.77.10.12', 26379)]
REDIS['caching']['KWARGS'] = {
    'socket_connect_timeout': 3,
    'socket_timeout': 3,
    'retry': Retry(NoBackoff(), 0),
}

빠르게 오류를 반환하도록 제한한 값이므로 개별 요청의 재시도와 서비스 복구 시간은 다르다. 실제 DB/Redis 주 노드 장애 후 API 회복까지의 시간은 검증 글에서 확인한다. 운영 부하나 다른 버전에서도 같은 결과가 나온다고 일반화하지 않는다.

7. Zabbix LTS HA와 환경별 Proxy/Bastion

온라인망에서 구성 시

Server/Web/Proxy는 alpine-7.0.30 태그를 사용하고 Rocky 대상 Agent 2는 el10용 7.0.30 RPM을 사용한다. 두 Server는 공통 Zabbix DB를 바라보되 HANodeName과 NodeAddress를 다르게 설정한다. Web은 특정 Server 주소를 고정하지 않고 HA 정보에서 활성 노드를 찾도록 설정했다.

# ops01
ZBX_HANODENAME=fml-ops-01
ZBX_NODEADDRESS=10.77.10.11:10051
# ops02
ZBX_HANODENAME=fml-ops-02
ZBX_NODEADDRESS=10.77.10.12:10051
# Active Proxy의 같은 HA cluster 주소는 세미콜론으로 연결
ZBX_SERVER_HOST=10.77.10.11:10051;10.77.10.12:10051

Proxy 이름은 Zabbix API에 등록한 이름과 정확히 일치해야 한다. 환경별 Proxy PSK와 Agent PSK를 구분한다. Proxy SQLite 데이터는 영속 볼륨에 두고 디스크 버퍼 정책을 적용한다. 단절 후 지연 데이터를 재전송했는지까지 확인해야 버퍼 동작을 검증했다고 할 수 있다.

폐쇄망에서 구성 시

Proxy 이미지는 docker load로 반입한다. Agent RPM은 내부 저장소로 제공하고 GPG 서명 검증을 유지한다. 이 el10 RPM은 B5333005 키로 검증했다. 기존 A14FE591 키만 사용하는 구성에서는 검증이 실패했으므로 공식 서명 키의 fingerprint를 확인해 추가했다.

rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-ZABBIX-B5333005
rpm -K zabbix-agent2-7.0.30-release1.el10.x86_64.rpm
# 검증한 키 fingerprint
# 4C3D6F2CC75F5146754FC374D913219AB5333005

Bastion은 환경 대상 .101:22로만 forwarding한다. 대화형 셸과 agent forwarding은 허용하지 않는다. 중앙의 키를 사용해 대상까지 연결하고 개인키를 Edge에 두지 않는다. 기존 관리 계정의 SSH 접근을 보존한 상태에서 sshd -t로 검사하고 reload한다.

Match User bastion
    AuthenticationMethods publickey
    AllowTcpForwarding local
    PermitOpen 10.77.20.101:22
    PermitTTY no
    AllowAgentForwarding no
    ForceCommand /bin/false
Match all

8. Git·K3s·AWX와 실행환경

온라인망에서 구성 시

Git bare repository는 ops02의 /srv/git/fullmoon-automation.git이다. AWX는 fmlgit의 읽기 전용 배포키로 main branch를 가져온다. forced command는 git-shell이 해석할 수 있게 경로까지 작은따옴표로 감쌌다.

restrict,command="git-upload-pack '/srv/git/fullmoon-automation.git'" ssh-ed25519 <공개키>

ops01에 K3s를 설치하고 SELinux와 Secret 암호화를 활성화했다. Traefik·ServiceLB·metrics-server는 이 실습에 불필요해 제외했다. AWX는 Operator 2.19.1과 AWX 24.6.1로 고정하고, PostgreSQL Secret은 기존 HA DB의 6432 경로를 가리킨다. 관리자 암호·DB 암호·CA bundle은 Kubernetes Secret으로 주입한다.

k3s kubectl -n awx get pods,jobs
k3s kubectl -n awx apply -f awx.yml
curl --fail https://awx.fullmoon.test/api/v2/ping/

Operator의 초기 RBAC proxy 이미지 gcr.io 경로가 404여서 같은 v0.15.0의 공식 quay.io/brancz 이미지로 수정했다. AWX CRD에서 cookie 설정은 문자열, host_aliases는 배열이어야 한다. SYSTEM_TASK_ABS_MEM을 숫자로 넣으면 해당 AWX 코드가 문자열 메서드를 호출하며 dispatcher가 실패해 불필요한 수동 설정을 제거했다.

폐쇄망에서 구성 시

K3s airgap 이미지와 Operator가 생성하는 모든 이미지까지 반입한다. 특히 init/migration/EE/RBAC proxy 이미지가 빠지면 Web 이미지만 있어도 설치되지 않는다. K3s containerd에 import 후 실제 이미지 이름이 CR의 이름과 일치하는지 확인한다.

실습 EE는 awx-ee:24.6.1에 netbox.netbox:3.23.0, 공개 CA, 검증한 Git/Edge SSH host key를 추가했다. 이미지 이름은 localhost/fullmoon-ee:24.6.1-netbox3.23.0-r2이며 AWX 실행환경의 pull 정책은 Never다. 이름에 localhost가 있다고 registry를 실행한 것은 아니며, K3s에 미리 import한 로컬 이미지를 사용한다.

k3s ctr images import fullmoon-ee-r2.tar
k3s ctr images list
git bundle verify fullmoon-automation.bundle

AWX에는 Project, 승인 bootstrap inventory, NetBox inventory source, SSH credential, NetBox/Zabbix API credential, Onboard/Verify Job Template, Workflow를 만든다. Workflow 성공 경로는 Onboard → NetBox inventory sync → Verify다. AWX 자체는 중앙 1호기 단일 구성이다. API ping의 ha 필드만으로 물리 분산 HA라고 판단하지 않는다.

9. PXE·Kickstart로 빈 서버 설치

온라인망에서 구성 시

공식 Rocky Minimal ISO를 검증하고 Provision의 /var/www/html/rocky에 설치 트리를 제공한다. 실제 ISO에는 Minimal/repodata가 있다. DVD의 BaseOS/AppStream 경로를 그대로 쓰면 404가 발생한다. DHCP는 환경별 승인 MAC만 .101에 매핑한다.

fml-prod-app-01 : 08:00:27:a0:20:65 → 10.77.20.101
fml-dev-app-01  : 08:00:27:a0:30:65 → 10.77.30.101
fml-stg-app-01  : 08:00:27:a0:40:65 → 10.77.40.101

이 PC의 VirtualBox에서는 BIOS + 공식 ipxe-legacy.iso가 DHCP→HTTP kernel/initrd→Kickstart까지 동작했다. iPXE 파일은 네트워크 설치를 시작하는 부트 매체이며 OS 패키지는 Provision에서 받는다. 새 VM은 빈 32GiB 디스크, 설치 RAM 4GiB, 1 vCPU로 만들고 설치 후 1GiB로 줄인다.

Kickstart의 %pre는 MAC, DMI의 VirtualBox 식별, /dev/sda 존재, 디스크 서명 부재를 확인한 뒤에만 파티션 작업을 허용한다. root 암호는 잠그고 labadmin 공개키·sshd·sudo·chrony·firewalld를 구성한다. %post가 남긴 SSH host key는 신뢰하는 VM 콘솔 기록과 비교해 등록한다. unknown host key를 무조건 수락하지 않는다.

폐쇄망에서 구성 시

같은 ISO·부트 파일·Kickstart를 내부 HTTP에 반입한다. 대상에는 외부 NIC가 없고 DNS/NTP/패키지를 같은 환경의 Provision에서 받는다. Agent 2용 내부 저장소까지 구성하면 외부 RPM 저장소가 없어도 기본 설정을 수행할 수 있다.

물리 서버에 적용할 때에는 VirtualBox DMI guard를 그대로 제거하고 실행하면 안 된다. 승인된 실제 serial/BMC/MAC, RAID 논리 디스크와 설치 대상 WWN, UEFI/Secure Boot·NIC 드라이버를 별도 매핑한 뒤 디스크 파괴 범위를 검토해야 한다. 이 글의 실제 설치 증거는 VM이며, 물리 서버 실장비 검증 결과로 바꾸어 표현하지 않는다.

10. 자산 API·인벤토리·관제 연결

온라인망에서 구성 시

NetBox의 자산 쓰기 계정에는 필요한 VM/Device/Interface/IP/카탈로그 권한을 부여하고, inventory 계정은 조회만 허용한다. 초기 Custom Field와 카탈로그 생성은 별도 bootstrap으로 수행한다. NetBox 4.7의 v2 토큰은 Bearer nbt_ 형식이다. Zabbix 자동화 계정은 관리 대상 그룹과 API method를 제한하고 토큰에 만료일을 둔다.

plugin: netbox.netbox.nb_inventory
api_endpoint: https://netbox.fullmoon.test
token:
  type: Bearer
  value: "{{ lookup('env', 'NETBOX_TOKEN') }}"
validate_certs: true
query_filters:
  - tag: auto
  - status: active

수집 단계는 임의 임시 디렉터리에 Python collector를 배포하고 실행 후 항상 제거한다. 대상의 실제 hostname·machine ID·관리 NIC/IP를 승인 inventory와 비교한다. 값이 다르거나 기존 IP가 다른 자산에 할당되어 있으면 NetBox 변경 전에 중단한다. API token이나 /proc 환경 전체를 수집 결과에 포함하지 않는다.

API 작업은 delegate_to: localhost로 AWX EE에서 실행한다. 대상용 ansible_become 변수가 로컬 작업에 전파되면 EE 안에서 sudo를 찾으며 실패할 수 있으므로 다음처럼 명시한다.

delegate_to: localhost
become: false
vars:
  ansible_become: false
  ansible_python_interpreter: "{{ ansible_playbook_python }}"

등록 순서는 실제 자산/Interface/IPAM → Zabbix host/Proxy/template → NetBox inventory source sync → 대상 baseline와 최신 Zabbix 데이터 검증이다. NetBox와 Zabbix API는 하나의 DB transaction이 아니므로 부분 성공 후 재실행을 지원하고, 성공한 자산을 무조건 삭제하는 보상 작업은 하지 않는다.

폐쇄망에서 구성 시

API 코드는 내부 HTTPS만 사용하므로 방식은 같다. EE의 CA, collection, Python 라이브러리, pinned SSH host key와 Git 커밋이 모두 반입되어 있어야 한다. Job 실행 중 Galaxy나 pip에서 패키지를 내려받지 않는다. target에는 fullmoon-minimal·fullmoon-zabbix 저장소만 명시해 Agent를 설치한다.

ServerActive=10.77.20.10:10051
Hostname=fml-prod-app-01
TLSConnect=psk
TLSAccept=psk
TLSPSKIdentity=fml-prod-app-01
TLSPSKFile=/etc/zabbix/agent.psk

PSK 파일은 zabbix 소유 0400으로 두고 AWX 로그에는 값을 남기지 않는다. 관리 호스트 중복 여부와 최신 item의 lastclock/lastvalue까지 확인한다. 호스트 생성 응답만으로 완료 처리하지 않는다.

설치 완료와 Workflow의 자동 연결

중앙 1호기의 fullmoon-postinstall.timer가 승인 대상의 설치 완료를 확인한다. SSH는 해당 Bastion을 거치고 고정한 host key를 검증한다. /var/lib/fullmoon-lab/pxe-installed, hostname, machine ID가 일치하면 Project sync → bootstrap inventory sync → 대상 한 대를 limit으로 지정한 Workflow를 실행한다. 작업 ID와 상태는 root만 읽는 state.json에 남긴다.

처음 생성한 SSH host key는 신뢰하는 VirtualBox serial console과 대조하여 승인 목록에 넣는다. 이 신원 승인 전에는 컨트롤러가 대기한다. 알 수 없는 키를 자동 수락하지 않는다. 이후 설치 완료 감지와 API 등록·관제 검증은 자동으로 이어진다. 물리 서버에서는 이 신원 등록을 BMC 콘솔이나 조직의 SSH CA 발급 절차와 연결해야 한다.

systemctl status fullmoon-postinstall.timer
journalctl -u fullmoon-postinstall.service --since '-30min'
# 성공: launched → AWX workflow ID → completed
# 실패: manual_review_required. 원인을 확인하기 전 자동 재등록하지 않는다.

이 컨트롤러와 AWX는 ops01에 있어 해당 노드 장애 동안 신규 온보딩이 멈춘다. Git이 있는 ops02가 중단되면 새 Project sync가 실패한다. 상태 파일을 잃거나 실행 응답 직후 프로세스가 종료되면 같은 Workflow가 다시 시도될 수 있으므로, 자산 등록 자체도 식별자와 IP 중복을 검사하는 재실행 가능한 방식으로 구성했다.

11. 접속 정보와 간단한 운영 가이드

기능 접속 경로 운영 확인
NetBox https://netbox.fullmoon.test VM/Device, Interface, primary IP, collection 상태
Zabbix https://zabbix.fullmoon.test HA 상태, Proxy last access, host 최신 데이터
AWX https://awx.fullmoon.test Project revision, inventory update, Workflow/Job 결과
Git ssh://fmlgit@git.fullmoon.test/srv/git/fullmoon-automation.git 읽기 전용 SCM 동기화
관리 SSH loopback 22031~22038 해당 VM의 labadmin 공개키 인증
대상 SSH 환경별 Bastion 경유 승인 host key와 .101:22 제한

일상 점검은 시간 동기화·디스크/메모리 → DB/Redis 역할 → App/Worker → HA/VIP → Proxy → 실제 Agent 데이터 → 최근 AWX 실패 순으로 수행한다. 컨테이너가 Up인지만 확인하지 않는다.

df -h
free -m
chronyc tracking
docker ps --format '{{.Names}} {{.Status}}'
k3s kubectl -n awx get pods
systemctl is-active haproxy keepalived

새 서버를 추가할 때에는 승인 inventory·MAC/IP·Bastion PermitOpen·SSH host key를 먼저 등록하고 Git에 커밋한다. Project sync 후 해당 호스트만 limit으로 지정한다. NetBox의 primary IP와 환경 그룹을 확인한 뒤 운영 대상으로 넘긴다. 실행 중인 서버 전체를 무제한 all 대상으로 바꾸지 않는다.

비밀번호 변경과 토큰 갱신은 비밀 저장소·AWX Credential·서비스 재시작 범위를 함께 관리한다. 만료된 토큰, 변경된 SSH host key, 다른 machine ID는 자동으로 우회하지 않는다. 노드 장애 후에는 살아남은 primary를 기준으로 복제본을 복귀시키고, 분리된 노드를 임의로 쓰기 활성화하지 않는다.

12. 백업·업데이트·검증으로 마무리

PostgreSQL 논리 백업, NetBox media, Git repository, AWX SECRET_KEY와 DB, CA/설정/비밀 자료를 각각 관리한다. VM snapshot만으로 애플리케이션 정합성 있는 백업을 대신하지 않는다. 백업 파일이 존재하는 것과 격리된 DB에 복원해 같은 자산을 읽는 것은 서로 다른 검증이다.

업데이트는 이미지 tag·digest와 EE/collection을 함께 기록하고 검증 후 진행한다. NetBox migration이나 DB major upgrade는 이전 이미지로 되돌리는 것만으로 복구되지 않을 수 있으므로 데이터 백업·복원 절차를 준비한다. 폐쇄망에서는 업데이트 반입 묶음과 checksum을 새로 만들고 이전 검증 묶음을 보존한다.

구축 완료 판정에는 자산 등록, inventory sync, 최신 관제 값, 재실행, 주소 충돌 거부, 서비스 장애 전환, Proxy 버퍼 복구, 백업 복원, 외부 통신이 없는 대상 설치를 포함한다. 실제로 수행한 시험과 미실행 항목은 검증 프로젝트에 구분해 기록한다.

함께 읽기와 구성 자료

포트폴리오 원문 · 온라인망·폐쇄망 구축 가이드 · 장애 시나리오·검증 기록 · VirtualBox 구성 문제 해결 기록

실습 설정·자동화 소스 ZIP · ZIP SHA-256

공개 ZIP에는 구성 템플릿과 자동화 소스를 담았습니다. OS·RPM·컨테이너 이미지와 자격 증명은 포함하지 않습니다. 자신의 주소·공개 CA·승인 SSH 키·비밀 저장소로 구성한 뒤 적용합니다.