Ansible 기반의 일관적이고 신뢰성 있는 폐쇄망 환경 배포 구축

Ansible 기반의 일관적이고 신뢰성 있는 폐쇄망 환경 배포 구축

폐쇄망 환경에서는 CMP(Cloud Management Platform) 구축에 필요한 오픈소스들을 일관성 있게 공급받는 것이 쉽지 않습니다. 인터넷 접속이 제한적이기 때문에 필요한 패키지, 이미지 등의 의존성을 외부에서 직접 받을 수 없는 상황이 빈번하게 발생합니다.

저희는 이러한 폐쇄망 환경에서의 배포 문제를 해결하고, CMP를 비롯한 오픈소스 기반 시스템의 안정적이며 일관된 구축을 지원하기 위한 자동화된 배포 전략 및 프로세스를 성공적으로 마련했습니다. 본 글에서는 이 전략의 핵심 개념과 구현 과정에 대해 상세히 설명하고자 합니다

문제 정의

폐쇄망 환경에서의 배포 문제는 크게 '공급자'와 '소비자' 두 부분으로 나눌 수 있습니다.

  • 공급자: 시스템에서 어떤 의존성을 지원하고, 각각의 버전은 무엇인지 한눈에 파악할 수 있는 BOM(Bill Of Materials)이 필요합니다. 개발자 및 운영자는 이 BOM을 바탕으로 무결한 배포 패키지를 준비하고 관리해야 합니다.
  • 소비자: 실제 배포 환경에서는 외부 인터넷에서 직접 의존성을 내려 받을 수 없습니다. 따라서 내부 저장소 또는 미리 준비된 저장소에서 모든 의존성을 정확히 받아야 합니다.

공급자가 제공해야 하는 주요 의존성 형태는 다음과 같습니다.

  1. Ubuntu 패키지(.deb 파일): OS 기반 패키지 설치가 필요한 경우, 필요한 deb 파일을 미리 수집해 내부 저장소에 저장해야 합니다.
  2. Kubernetes용 Docker 이미지: 마찬가지로 폐쇄망 내에서 사용할 컨테이너 이미지는 외부 레지스트리에서 직접 가져올 수 없으므로, 별도 이미지 레지스트리를 구성하고 필요한 이미지를 사전에 준비·관리해야 합니다.

솔루션 구축 시나리오

본 사례에서는 아래와 같은 총 11대의 VM (또는 물리 서버)으로 구성된 폐쇄망 환경을 가정하고, Ansible과 Nexus3를 활용한 일관된 배포 솔루션을 구축했습니다.

  • 배포 대상 서버 구성:
    • Nexus3 호스트 노드: 1대
    • Kubernetes 마스터 노드: 3대
    • Kubernetes 워커 노드: 3대
    • HAProxy 노드: 2대
    • PostgreSQL 노드: 2대

Ubuntu 패키지와 Docker 이미지를 모두 효율적으로 관리하기 위해 Nexus3를 사용했습니다. Nexus3는 볼륨 관리를 용이하게 하고 폐쇄망 내 배포 편의성을 높이기 위해 시스템 데몬 형태가 아닌 Docker 기반으로 구축했습니다.

단계 1: 폐쇄망 배포용 패키지 빌더 자동화

모든 의존성을 사전에 수집하고 통합 관리하기 위해 인터넷 접근이 가능한 '빌더 VM'을 구축했습니다. 이 빌더 VM에서 Ansible 플레이북을 통해 모든 준비 과정을 자동화합니다.

사전 조건: 빌더 VM에는 Ansible과 Docker가 설치되어 있어야 합니다.

1. 외부 APT 저장소 설정 및 GPG 키 다운로드

먼저 빌더 VM에서 Ubuntu 패키지를 다운로드 받아야 하므로, Kubernetes, PostgreSQL등 APT 저장소의 GPG키를 다운받고, sourcelists에 추가해줍니다.

- name: PostgreSQL GPG  다운로드
  get_url:
    url: https://www.postgresql.org/media/keys/ACCC4CF8.asc
    dest: /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc
    mode: '0644'

- name: PostgreSQL APT 저장소 추가
  apt_repository:
    repo: "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt {{ ansible_lsb.codename }}-pgdg main"
    filename: "pgdg"
    state: present

2. 필요한 Ubuntu 패키지 목록 정의

CMP 구축에 필요한 모든 필수 패키지 목록과 버전을 Ansible vars에 명확히 정의합니다. 이는 시스템의 BOM(Bill Of Materials) 역할을 수행하여 배포의 일관성을 유지합니다.

...
  vars:
    deb_packages:
      - curl
      - ansible
      - software-properties-common
      - apt-transport-https
      - ca-certificates
      - rsync
      - gpg
      - nfs-common
      - sshpass
      - fluent-bit=4.0.3
      - haproxy=2.4.24-0ubuntu0.22.04.2
      - keepalived=1:2.2.4-0.2build1
      - postgresql-17
      - containerd.io=1.7.27-1
      - kubeadm=1.33.0-1.1
      - kubelet=1.33.0-1.1
      - kubectl=1.33.0-1.1
...

3. 패키지 및 의존성 일괄 다운로드

apt-get download 명령과 apt-cache depends 의존성 탐색을 이용하여, 정의된 패키지뿐만 아니라 해당 패키지들이 요구하는 모든 의존성 패키지까지 /var/cache/apt/archives 디렉터리에 .deb 파일 형태로 다운로드합니다. 이 과정은 폐쇄망에서도 모든 패키지가 문제없이 설치될 수 있도록 완벽한 오프라인 환경을 조성하는 데 필수적입니다.

- name: 패키지  의존성 다운로드
  ansible.builtin.shell: |
    apt-get update
    cd /var/cache/apt/archives
    apt-get download $(apt-cache depends --recurse --no-recommends --no-suggests --no-conflicts --no-breaks --no-replaces --no-enhances {{ item }} | grep "^\w" | sort -u) || true
    apt-get download {{ item }} || true
  args:
    executable: /bin/bash
  loop: "{{ deb_packages }}"
  ignore_errors: yes

4. Nexus3 APT Hosted Repository로 .deb 파일 업로드

다운로드된 모든 .deb 파일은 Nexus3의 APT Hosted Repository로 업로드됩니다. Nexus3는 내부 apt 미러 서버 역할을 수행하여, 폐쇄망 내에서 다른 서버들이 패키지를 설치할 때 이 Nexus3를 apt 소스로 지정하여 사용할 수 있게 합니다.

- name: .deb 파일 목록 수집
  find:
    paths: "/var/cache/apt/archives"
    patterns: "*.deb"
  register: deb_files

- name: .deb 파일 Nexus로 업로드 (curl 사용)
  ansible.builtin.shell: |
    curl -u admin:admin -X POST "http://localhost:8081/service/rest/v1/components?repository=apt-hosted-repo" \
      -F "apt.asset=@{{ item.path }}" \
  loop: "{{ deb_files.files }}"
  loop_control:
    label: "{{ item.path | basename }}"

5. 필요한 Docker 이미지 정의

Kubernetes 배포에 필요한 모든 퍼블릭 및 사내 Docker 이미지 목록을 버전과 함께 상세히 정의합니다. 마찬가지로 시스템의 필요한 컨테이너 이미지에 대한 BOM 역할을 합니다.

vars:
    nexus_registry: "<builder repository url>"
    nexus_username: "..."
    nexus_password: "..."

    solution_registry: "<사내 nexus repository>"
    solution_username: "..."
    solution_password: "..."

    solution_docker_images:
     - "..."

    public_docker_images:
      - "grafana/grafana:12.0.1"
      - "fluent/fluent-bit:4.0.3-amd64"

      # k8s 설치를 위한 docker image (static pod)
      - "registry.k8s.io/pause:3.9"
...

6. Docker 이미지 Nexus Docker Repository로 푸시

정의된 모든 Docker 이미지를 빌더 VM의 로컬 환경으로 Pull 한 후, Nexus3의 Docker Hosted Repository로 Push합니다. 이때 regex_replace 필터를 사용하여 이미지 경로를 Nexus3의 형식에 맞게 변환하여 저장합니다. 이는 폐쇄망 내에서 이미지 이름을 변경하지 않고도 내부 레지스트리를 통해 이미지를 쉽게 참조할 수 있도록 합니다.

- name: Docker 이미지 Push
  ansible.builtin.shell: |
    docker push {{ nexus_registry }}/{{ item | regex_replace('^[^/]+/', '') }}
  loop: "{{ solution_docker_images }}"
  register: push_results

7. Nexus3 볼륨 데이터 아카이빙

모든 의존성 업로드가 완료되면, Nexus3 컨테이너를 중지하고 Nexus3가 사용하는 데이터 볼륨 전체를 압축하여 하나의 tar.gz 파일로 백업합니다. 이 tar.gz 파일이 바로 폐쇄망 환경으로 이전될 통합 배포 패키지가 됩니다. 이 파일 하나로 모든 패키지와 이미지를 한 번에 옮길 수 있어 효율적입니다.

- name: 볼륨 데이터 tar로 백업
  ansible.builtin.shell: |
    tar czvf {{ nexus_data_backup_path }} -C {{ nexus_data_mountpoint.stdout }} .

단계 2: 폐쇄망 환경에서의 배포

1. Nexus3 호스트 노드 구축

선행조건 1. Nexus3 호스트 노드는 Docker가 설치되어 있어야 합니다. 선행조건 2. Nexus3 Docker image를 폐쇄망 환경으로 가져가야 합니다.

도커를 사용하여 Nexus3 애플리케이션을 구동합니다. 이때 빌더 VM에서 생성된 tar.gz 압축 파일을 Nexus3 컨테이너의 Named Volume 위치에 풀어줍니다. Nexus3 컨테이너를 재시작하면, 빌더 VM에서 구축해 놓은 모든 APT 및 Docker 저장소 환경이 폐쇄망 내부에서 그대로 활성화됩니다.

services:
  nexus:
    image: sonatype/nexus3:3.81.1
    container_name: nexus3
    user: "root"
    volumes:
      - nexus-data:/nexus-data
    restart: unless-stopped

volumes:
  nexus-data:
    name: nexus-data
    driver: local

2. APT 저장소 변경

폐쇄망 환경의 내부 APT 저장소가 Nexus3를 통해 완성되었으므로, 나머지 모든 VM의 APT sources.list를 변경하여 Nexus3를 바라보도록 설정합니다. 이때 Nexus3 APT 저장소의 GPG 공개 키를 적용하여 보안성을 유지합니다.

- name: apt GPG key 다운로드
  become: true
  get_url:
    url: "http://{{ hostvars['nexus3_host'].ansible_host }}:8081/repository/raw-repository/gpg/nexus-apt-repo.public.gpg.key"
    dest: /usr/share/keyrings/nexus-apt-repo.public.gpg.key
    mode: '0644'

- name: apt sources.list를 내부 Nexus로 변경 (GPG key 적용)
  become: true
  copy:
    dest: /etc/apt/sources.list
    content: |
      deb [signed-by=/usr/share/keyrings/nexus-apt-repo.public.gpg.key] http://{{ hostvars['bastion'].ansible_host }}:8081/repository/apt-hosted-repo jammy main

- name: apt update
  become: true
  apt:
    update_cache: yes

3. CMP 및 기타 컴포넌트 설치

이제 폐쇄망 환경의 모든 VM은 Nexus3 저장소를 통해 필요한 패키지를 설치할 수 있게 됩니다. Ansible을 활용하여 Kubernetes, PostgreSQL, HAProxy 등 모든 CMP 연관 컴포넌트들을 자동으로 일관되게 설치합니다.

- name: 쿠버네티스 컴포넌트 설치
  block:
    - name: install kubeadm
      ansible.builtin.include_role:
        name: apt/kubeadm/{{ kubeadm_version }}
    - name: install kubelet
      ansible.builtin.include_role:
        name: apt/kubelet/{{ kubelet_version }}
    - name: install kubectl
      ansible.builtin.include_role:
        name: apt/kubectl/{{ kubectl_version }}

4. Kubernetes에 애플리케이션 설치

Kubernetes 클러스터 위에 배포될 CMP 및 오픈소스 애플리케이션들 역시 Nexus3 Docker Registry를 통해 이미지를 다운로드하여 설치 가능합니다.

Kubernetes에 올릴 CMP 및 오픈소스 App들을 Nexus3 Docker Registry를 통해 설치가능합니다. public 환경에서 이미지 이름을 사용하는 것과 동일하게 image: grafana/grafana:8.4.11과 같이 선언해도 자동으로 Nexus3에서 이미지를 가져옵니다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: grafana
spec:
...
    spec:
      containers:
      - name: grafana
        image: grafana/grafana:8.4.11
...

결론

폐쇄망 환경에서 CMP 비롯한 다양한 오픈소스 기반 시스템을 구축하는 과정은 필요한 오픈소스를 안정적으로 확보하기 어렵다는 근본적인 문제점을 안고 있습니다. 이번에 구축한, Nexus3와 Ansible을 활용한 전략은 이러한 외부 의존성 확보의 어려움과 버전 불일치 문제를 해결하며, 시스템 구축에 필요한 모든 오픈소스 컴포넌트를 안정적이고 일관되게 공급할 수 있습니다.

  • 일관된 의존성 및 버전 관리: 사전 수집된 모든 패키지와 이미지를 Nexus3라는 단일 중앙 저장소에서 통합 관리함으로써, 배포 과정의 일관성을 극대화하고 의도치 않은 버전 불일치나 누락으로 인한 오류 발생 가능성을 최소화합니다. 시스템의 BOM을 명확히 확보하여 신뢰할 수 있는 배포 환경을 조성합니다.
  • 향상된 운영 편의성: 한 번 구축된 Nexus3 저장소는 향후 시스템 확장이나 업데이트 시에도 안정적이고 예측 가능한 의존성 공급원 역할을 수행하여 운영 및 유지보수의 편의성을 증대시킵니다. Nexus3 노드의 Docker Volume만 교체하면, 업그레이드에 필요한 의존성을 즉시 공급할 수 있습니다.