CloudNative
Ansible Role·Collection과 Docker 자동화
Ansible Role의 표준 구조와 Galaxy·Collection의 관계를 이해하고 현재 Docker APT Repository와 community.docker Module로 Container 배포를 자동화한다
Ansible 변수와 Vault·Facts·제어문에서는 변수, Facts, 반복문과 조건문으로 Host별 실행을 제어했다. 이번에는 길어진 Playbook의 Task, 변수, File과 Handler를 표준 Directory 구조의 Role로 분리한다. 외부 Role과 Collection을 관리하는 ansible-galaxy를 살펴본 뒤 community.docker Collection으로 Docker Container를 배포한다.
1 ) Role이 필요한 이유
Role은 Task, Handler, 변수, 정적 File과 Template을 정해진 Directory 구조로 묶어 재사용하는 단위이다.
하나의 Playbook에 모든 내용을 넣으면 다른 Project에서 일부 기능만 재사용하기 어렵고 여러 사람이 같은 File을 동시에 수정하게 된다. Role은 Web Server, Database 또는 공통 OS 설정처럼 독립된 책임으로 자동화 내용을 나눈다.
Role을 사용하면 다음과 같은 이점이 있다.
-
관련 Task, Handler와 File을 한 Directory에서 관리한다.
-
기본값과 환경별 Override 값을 분리한다.
-
여러 Playbook에서 같은 구성을 재사용한다.
-
Role 단위로 Test, Version과 의존성을 관리한다.
-
Ansible Galaxy 또는 내부 Repository를 통해 공유한다.
2 ) Role 생성과 Directory 구조
Control Node의 Project Root에서 webserver Role 골격을 생성한다.
mkdir -p roles
ansible-galaxy role init roles/webserver
tree roles/webserver
주요 Directory의 역할은 다음과 같다.
| 경로 | 역할 |
|---|---|
tasks/main.yml |
Role이 수행할 기본 Task |
handlers/main.yml |
Role Task가 notify하는 Handler |
defaults/main.yml |
다른 변수로 쉽게 Override할 수 있는 낮은 우선순위 기본값 |
vars/main.yml |
Role 내부에서 강하게 유지할 높은 우선순위 변수 |
files/ |
copy 등에서 사용하는 정적 File |
templates/ |
Jinja2 Template File |
meta/main.yml |
Role Metadata와 의존 Role |
tests/ |
Role 동작 확인용 Inventory와 Playbook |
사용하지 않는 Directory는 반드시 유지할 필요가 없다. Role의 files/와 templates/ Resource는 Role Root를 기준으로 찾으므로 ../files/index.html 같은 상대 경로를 만들 필요가 없다.
3 ) Apache Web Server Role 작성
이번 Role은 Ubuntu와 Debian 계열 Managed Node에 Apache를 설치하고 정적 Page를 배포한다.
3.1 Override 가능한 기본값
roles/webserver/defaults/main.yml을 작성한다.
---
webserver_title: Apache Web Server
webserver_package: apache2
webserver_service: apache2
webserver_document_root: /var/www/html
webserver_security_config: /etc/apache2/conf-available/security.conf
webserver_index_mode: "0644"
webserver_supported_os_families:
- Debian
사용자가 환경별로 바꿀 가능성이 있는 값은 defaults에 둔다. vars/main.yml은 우선순위가 높아 Inventory 값으로 바꾸기 어려우므로 반드시 고정해야 하는 값에만 사용한다.
3.2 정적 Page
roles/webserver/files/index.html을 작성한다.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Hello from Ansible</title>
</head>
<body>
<h1>Hello! Ansible Role</h1>
</body>
</html>
Host별 값이 들어가야 한다면 정적 files 대신 templates/index.html.j2와 ansible.builtin.template Module을 사용한다.
3.3 Task
roles/webserver/tasks/main.yml을 작성한다.
---
- name: Verify the managed operating system family
ansible.builtin.assert:
that:
- ansible_facts['os_family'] in webserver_supported_os_families
fail_msg: >-
{{ ansible_facts['os_family'] }} is not supported by this role.
- name: Install the web server package
ansible.builtin.apt:
name: "{{ webserver_package }}"
state: present
update_cache: true
become: true
- name: Copy the web server index page
ansible.builtin.copy:
src: index.html
dest: "{{ webserver_document_root }}/index.html"
owner: root
group: root
mode: "{{ webserver_index_mode }}"
become: true
- name: Limit the Apache server signature
ansible.builtin.lineinfile:
path: "{{ webserver_security_config }}"
regexp: '^ServerTokens '
line: ServerTokens Prod
become: true
notify: Restart web server
- name: Ensure the web server is enabled and running
ansible.builtin.service:
name: "{{ webserver_service }}"
enabled: true
state: started
become: true
state: latest는 Repository의 새 Version이 공개될 때마다 Package를 변경할 수 있다. 재현 가능한 기본 구성에는 present를 사용하고 Upgrade는 별도 절차로 관리한다.
3.4 Handler
roles/webserver/handlers/main.yml을 작성한다.
---
- name: Restart web server
ansible.builtin.service:
name: "{{ webserver_service }}"
state: restarted
become: true
3.5 Role 호출
role-example.yml에서 import_role로 정적으로 불러온다.
---
- name: Configure web servers with a role
hosts: lab
tasks:
- name: Print the start of the role play
ansible.builtin.debug:
msg: Start the webserver role.
- name: Import the webserver role
ansible.builtin.import_role:
name: webserver
문법과 예상 변경을 확인한 뒤 실행한다.
ansible-playbook --syntax-check role-example.yml
ansible-playbook --check --diff role-example.yml
ansible-playbook role-example.yml
Role을 찾지 못하면 Project의 ansible.cfg에 Search Path를 지정한다.
[defaults]
inventory = ./inventory.ini
roles_path = ./roles
4 ) Ansible Galaxy의 Role 관리
Ansible Galaxy는 Role과 Collection을 검색하고 배포하는 공개 Service이다.
Role 관련 명령을 확인한다.
ansible-galaxy role --help
PostgreSQL Role을 검색하고 선택한 Role의 Metadata를 확인하는 기본 흐름은 다음과 같다.
ansible-galaxy role search postgresql --platforms Ubuntu
ansible-galaxy role info <namespace.role_name>
ansible-galaxy role install -p roles <namespace.role_name>
ansible-galaxy role list -p roles
자료에서 사용한 buluma.postgres는 Galaxy 명령 흐름을 확인하기 위한 특정 시점의 예시이다. Role의 존재 여부, 최신 Release, 지원 OS와 유지보수 상태는 바뀔 수 있으므로 설치 전에 다시 검색한다.
ansible-galaxy role info buluma.postgres
더 이상 사용하지 않는 Role을 Project Directory에서 제거한다.
ansible-galaxy role remove <namespace.role_name>
Galaxy에 공개됐다는 사실만으로 Code의 보안과 품질이 검증됐다고 판단할 수 없다. 설치 전에 Source Repository, License, Release 이력, 지원 Platform, 의존 Role과 Task가 수행하는 Privileged 작업을 확인한다.
5 ) Content Collection
Collection은 Module, Plugin, Role, Playbook과 문서를 Namespace 단위로 묶어 Ansible Core와 독립적으로 배포하는 형식이다.
초기의 Ansible은 많은 Module을 Core Package와 함께 배포했다. Collection은 Core Release와 Module·Plugin Release를 분리하고 필요한 Version을 Project별로 선택할 수 있게 한다.
ansible.builtin.copy는 Builtin Collection의 FQCN이고 community.docker.docker_container는 community.docker Collection의 Module이다.
설치된 Collection을 확인한다.
ansible-galaxy collection list
ansible-doc community.docker.docker_container
community.docker가 없다면 설치한다.
ansible-galaxy collection install community.docker
Project에서 의존성을 재현하려면 검증한 Version을 requirements.yml에 기록한다.
---
collections:
- name: community.docker
version: "<VERIFIED-VERSION>"
Placeholder를 설치 시점에 검증한 실제 Version으로 바꾼 뒤 실행한다.
ansible-galaxy collection install -r requirements.yml
ansible-galaxy collection list community.docker
Collection을 Offline 환경으로 전달해야 한다면 의존성을 포함한 Artifact와 생성된 requirements.yml을 Download할 수 있다.
mkdir -p collection-bundle
ansible-galaxy collection download \
-p collection-bundle \
community.docker
6 ) Docker 설치 자동화 준비
Docker 설치는 docker_hosts Inventory Group에만 적용한다.
[docker_hosts]
master
worker1
worker2
Docker의 현재 Ubuntu 설치 문서는 One-line docker.list보다 DEB822 형식의 /etc/apt/sources.list.d/docker.sources를 안내한다. 배포판 Codename과 Architecture를 고정 문자열로 넣지 않고 Managed Node에서 확인한다.
install-docker.yml을 작성한다.
---
- name: Install Docker Engine from the official repository
hosts: docker_hosts
become: true
tasks:
- name: Install repository prerequisites
ansible.builtin.apt:
name:
- ca-certificates
- curl
state: present
update_cache: true
- name: Create the APT keyring directory
ansible.builtin.file:
path: /etc/apt/keyrings
state: directory
owner: root
group: root
mode: "0755"
- name: Download the Docker repository signing key
ansible.builtin.get_url:
url: https://download.docker.com/linux/ubuntu/gpg
dest: /etc/apt/keyrings/docker.asc
owner: root
group: root
mode: "0644"
- name: Read the Debian package architecture
ansible.builtin.command: dpkg --print-architecture
register: docker_dpkg_architecture
changed_when: false
- name: Configure the Docker DEB822 repository
ansible.builtin.copy:
dest: /etc/apt/sources.list.d/docker.sources
owner: root
group: root
mode: "0644"
content: |
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: {{ ansible_facts['distribution_release'] }}
Components: stable
Architectures: {{ docker_dpkg_architecture.stdout }}
Signed-By: /etc/apt/keyrings/docker.asc
- name: Install Docker Engine packages
ansible.builtin.apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
- python3-requests
state: present
update_cache: true
- name: Ensure Docker is enabled and running
ansible.builtin.service:
name: docker
enabled: true
state: started
자료의 docker-ce-li는 docker-ce-cli의 오탈자이다. 또한 Ubuntu noble, amd64를 고정하면 다른 Ubuntu Release와 ARM64 Host에서 실패하므로 Facts와 dpkg 결과를 사용했다.
자료의 Container 예제는 python3-docker를 설치했지만 현재 community.docker의 해당 Module은 Docker SDK for Python을 직접 사용하지 않고 requests를 요구한다. 설치한 Collection Version의 공식 Requirements를 확인하고 이 예제에서는 Managed Node에 python3-requests를 설치한다.
문법을 검사하고 실행한다.
ansible-playbook --syntax-check install-docker.yml
ansible-playbook --check --diff install-docker.yml
ansible-playbook install-docker.yml -K
Check Mode에서는 APT Repository가 아직 생성되지 않아 뒤의 Package 설치 결과를 완전히 예측하지 못할 수 있다.
설치 결과를 확인한다.
ansible docker_hosts \
-m ansible.builtin.command \
-a "docker --version"
ansible docker_hosts \
-m ansible.builtin.command \
-a "systemctl is-active docker"
현재 Docker Ubuntu Repository 형식과 Package 목록은 Docker Engine Ubuntu 설치 문서에서 확인한다.
7 ) Nginx Container 배포
자료의 community.gerneral은 존재하지 않는 오탈자이다. Docker Resource는 community.docker Collection의 FQCN을 사용한다. nginx-latest도 Docker Hub의 일반적인 Tag 표기인 nginx:latest로 수정한다.
deploy-docker.yml을 작성한다.
---
- name: Deploy an Nginx container
hosts: docker_hosts
become: true
vars:
container_name: my-web-server
image_name: nginx:latest
host_port: 8080
container_port: 80
tasks:
- name: Pull the Nginx image
community.docker.docker_image_pull:
name: "{{ image_name }}"
- name: Create and start the Nginx container
community.docker.docker_container:
name: "{{ container_name }}"
image: "{{ image_name }}"
state: started
restart_policy: always
published_ports:
- "{{ host_port }}:{{ container_port }}"
env:
MY_ENV_VAR: hello-ansible
- name: Print the published endpoint
ansible.builtin.debug:
msg: >-
{{ inventory_hostname }} exposes {{ container_name }}
on TCP {{ host_port }}.
과거 예제에서 community.docker.docker_image로 Image를 Pull할 수 있었고 현재도 지원되지만, 현재 Collection 문서는 Pull 전용 작업에 community.docker.docker_image_pull 사용을 권장한다. 설치한 Collection Version의 요구 Package와 Parameter는 ansible-doc으로 확인한다.
ansible-doc community.docker.docker_image_pull
ansible-doc community.docker.docker_container
ansible-playbook --syntax-check deploy-docker.yml
ansible-playbook deploy-docker.yml -K
Container와 HTTP 응답을 확인한다.
ansible docker_hosts \
-m ansible.builtin.command \
-a "docker ps --filter name=my-web-server"
curl http://192.0.2.11:8080
curl http://192.0.2.12:8080
curl http://192.0.2.13:8080
다른 Computer에서 접속한다면 Managed Node의 Firewall과 Network에서 TCP 8080 접근이 허용되어 있어야 한다.
8 ) 문제 해결
| 증상 | 확인할 내용 |
|---|---|
| Role을 찾지 못함 | roles/ 위치와 roles_path |
| Role 변수 Override가 적용되지 않음 | 값이 defaults가 아니라 우선순위가 높은 vars에 있는지 확인 |
| Collection Module을 찾지 못함 | ansible-galaxy collection list, requirements.yml 설치 여부 |
| Docker APT 서명 오류 | docker.asc Permission과 Signed-By 경로 |
| Docker Package를 찾지 못함 | Ubuntu Codename, Architecture와 apt update 결과 |
| Docker Socket 권한 오류 | become 사용 여부와 /var/run/docker.sock Permission |
| Container Module Parameter 오류 | 설치된 Collection Version의 ansible-doc 결과 |
| TCP 8080 접속 실패 | Container Port Mapping, Host Firewall과 Network 경로 |
| 모든 Host에서 Container가 생성됨 | hosts: docker_hosts Group과 --list-hosts 결과 |
Role 구조는 Ansible Roles, Collection Module은 Community.Docker과 docker_container Module에서 확인할 수 있다.
최종 정리
Role은 Task, Handler, 변수와 File을 표준 Directory 구조로 묶어 Playbook을 재사용한다.
defaults는 쉽게 Override할 값,vars는 높은 우선순위가 필요한 내부 값에 제한해 사용한다.Galaxy의 공개 Role은 설치 전에 Source, 지원 Platform, Version과 Privileged 작업을 검토한다.
Collection은 Module과 Plugin을 Core와 독립적으로 배포하며 Playbook에서는 FQCN을 사용한다.
Docker 설치는 현재 DEB822 Repository 형식과 실제 Architecture를 사용하고 Container는
community.dockerModule로 원하는 상태를 관리한다.Container 제거, Docker Package 제거와 전체 Data 삭제는 영향 범위가 다르므로 별도 절차로 분리한다.
댓글