Post

Ansible 사용해보기 (w/ docker 설치 및 실행)

ansible-logo

홈서버로 사용하던 라즈베리 파이를 우분투 20.04에서 우분투 24.04로 업그레이드하면서 세팅을 다시 해주어야 했다. 이를 자동화 할 수 있으면 좋을것 같아 Ansible을 사용해 보기로 했다.

Ansible이란?

Ansible Introduction | Ansible Documentation

Ansible은 오픈소스 소프트웨어로, 소프트웨어 프로비저닝, 구성 관리, 애플리케이션 배포, 보안 및 오케스트레이션을 위한 IT 자동화 도구이다. Ansible은 SSH를 사용하여 호스트와 통신하며, YAML을 사용하여 작업을 정의한다.

사용 사례로는 다음과 같은 것들이 있다.

  • 반복 제거 및 작업 단순화
  • 시스템 구성 관리 및 유지
  • 복잡한 소프트웨어의 지속적 배포
  • 다운타임 없는 업데이트 수행

Ansible은 Playbook 이라는 human-readable한 스크립트를 사용하여 작업(task)를 정의한다. Playbook에서 로컬 또는 원격 호스트에 대한 상태를 정의하면 Ansible은 시스템이 해다 상태를 유지하도록 보장한다.

Ansible은 다음과 같은 원칙을 기반으로 설계되었다.

  • Agent-less Architecture: IT 인프라 전반에 걸쳐 추가 소프트웨어 설치를 방지하여 관리 복잡성을 줄인다.
  • Simplicity: Ansible Playbook은 사용자가 읽기 쉬운 YAML로 작성한다. 또한 기존에 사용되는 SSH를 사용하여 중앙화되지 않은 방식으로 호스트와 통신한다.
  • Scalability와 flexibility: 다양한 운영 체제, 클라우드 플랫폼 및 네트워크 장치를 지원하는 모듈식 설계를 통해 자동화하는 시스템을 쉽고 빠르게 확장 가능하다.
  • Idempotence와 predictability: 이미 Playbook에 정의된 상태에 있는 시스템에 대해 추가 작업을 수행하지 않는다. 이는 시스템이 예측 가능하게 유지되도록 한다.

Ansible 구성요소

ansible-components

Ansible은 다음과 같은 구성요소로 이루어져 있다.

  • Control node: Ansible이 설치된 시스템. ansible 또는 ansible-inventory와 같은 Ansible 명령을 실행한다.
  • Inventory: 논리적으로 구성된 관리 노드 목록. 배포할 호스트를 정의하기 위해 cotrol node에 생성한다.
  • Managed node: Ansible이 제어하는 ​​원격 시스템 또는 호스트. SSH를 통해 관리 노드와 통신한다.

Ansible 설치

Ansible Installation Guide | Ansible Documentation

Requirements

Control node requirements

Control node의 경우 python이 설치된 거의 모든 UNIX-like 시스템에서 사용할 수 있다. Windows에서는 WSL을 사용하여 설치할 수 있다.

Managed node requirements

Managed node의 경우 Ansible을 설치할 필요가 없지만, Ansible에서 생성된 python 코드를 실행하기 위해서는 python이 필요하다.(모듈에 따라 python 코드를 생성하지 않을 수도 있다) 또한 SSH를 통해 노드에 연결할 수 있는 POSIX 셸과 사용자 계정이 필요하다.

pipx를 사용한 설치

pipx를 사용하여 Ansible을 설치할 수 있다. pipx는 python 패키지를 격리된 환경에서 실행할 수 있도록 해주는 패키지이다. pipx 설치

1
pipx install --include-deps ansible

package manager를 사용한 설치

Ansible Installation on specific OS | Ansible Documentation

대부분의 리눅스 배포판은 패키지 관리자를 통해 Ansible을 설치할 수 있다. 원하는 버전의 Ansible이 있는지 확인하고 설치를 진행하면 된다.

1
2
3
4
5
6
7
8
9
# Debian, Ubuntu
sudo apt search ansible # ansible 검색
sudo apt info ansible # ansible 정보
sudo apt install ansible # ansible 설치

# Fedora, RHEL
sudo dnf search ansible # ansible 검색
sudo dnf info ansible # ansible 정보
sudo dnf install ansible # ansible 설치

Ansible 설치 확인

1
ansible --version

Ansible Inventory

Ansible Inventory | Ansible Documentation

Inventory는 Ansible이 배포하고 구성하는 managed node 또는 호스트 목록을 정의한 파일이다. 파일의 기본 위치는 /etc/ansible/hosts이지만, 다른 위치에 파일을 생성하고 -i 옵션을 사용하여 지정할 수 있다.

Inventory 파일은 YAML 또는 INI 파일 형식으로 작성할 수 있다. 작성 방법은 다음과 같다.

INI

1
2
3
4
5
6
7
8
9
bastion.example.com

[webservers]
web1.example.com
web2.example.com

[dbservers]
db1.example.com
db2.example.com

YAML

1
2
3
4
5
6
7
8
9
10
11
ungrouped:
    hosts:
        bastion.example.com:
webservers:
    hosts:
        web1.example.com:
        web2.example.com:
dbservers:
    hosts:
        db1.example.com:
        db2.example.com:

기본 Group

Inventory 파일에는 호스트들을 그룹으로 묶어서 정의할 수 있다. 이를 통해 특정 그룹에 속한 호스트들에 대해 작업을 수행할 수 있다.

만약 Inventory 파일에 그룹을 정의하지 않더라도 Ansible은 all 및 ungrouped라는 기본 그룹을 생성한다. all 그룹은 모든 호스트를 포함하고, ungrouped 그룹은 그룹에 속하지 않은 모든 호스트를 포함한다.

Host Variables

특정 호스트 또는 그룹과 관련된 변수 값을 인벤토리에 저장할 수 있다. 이를 통해 Playbook에서 변수를 사용하여 호스트 또는 그룹에 특정한 작업을 수행할 수 있다.

1
2
3
[webservers]
web1.example.com ansible_port=2222 ansible_user=ubuntu
web2.example.com ansible_port=2222 ansible_user=ubuntu
1
2
3
4
5
6
7
8
webservers:
    hosts:
        web1.example.com:
            ansible_port: 2222
            ansible_user: ubuntu
        web2.example.com:
            ansible_port: 2222
            ansible_user: ubuntu

여기까지는 Ansible Inventory에 대한 기본적인 내용이다. 자세한 내용은 위에 첨부한 링크를 참고하면 된다.

Ansible Playbook

Ansible Playbooks | Ansible Documentation

Ansible Playbooks은 Ansible에서 사용하는 human-readable한 스크립트이다. Playbook은 YAML 형식으로 작성되며, 작업(task)을 정의한다.

Ansible Playbooks는 반복 가능하고 재사용 가능하며 간단한 구성 관리 및 다중 머신 배포 시스템을 제공하며, 이는 복잡한 애플리케이션 배포에 적합하다.

Playbook 구조

Playbook은 YAML 파일로 작성된다. Playbook은 순서가 있는 하나 또는 여러 개의 play로 구성된다. 각 play는 하나 이상의 task로 구성된다. 각 task는 모듈을 실행하거나 변수를 설정하는 등의 작업을 수행한다.

Playbook은 위에서 아래로 순차적으로 실행되며, 각 task는 순차적으로 실행된다. Playbook은 idempotent하다. 즉, 이미 정의된 상태에 있는 시스템에 대해 추가 작업을 수행하지 않는다.

아래는 PostgreSQL을 설치 또는 업데이트하는 Playbook 예시이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- name: Update db servers
  hosts: dbservers
  remote_user: root

  tasks:
  - name: Ensure postgresql is at the latest version
    ansible.builtin.yum:
      name: postgresql
      state: latest

  - name: Ensure that postgresql is started
    ansible.builtin.service:
      name: postgresql
      state: started

위 Playbook은 dbservers 그룹에 속한 모든 호스트에 대해 PostgreSQL을 최신 버전으로 업데이트하고 시작한다. Playbook을 실행하려면 다음과 같이 실행하면 된다.

1
ansible-playbook -i inventory playbook.yaml

Ansible module

Ansible 모듈은 특정 작업을 수행하는 단위 프로그램이다. Playbook에서 task를 정의할 때 모듈을 사용하여 작업을 수행한다. 모듈은 단순히 정해진 작업만을 수행하는 것이 아니라 내부적으로 idempotent하게 동작하도록 설계되어 있다. 따라서 Playbook에 Task를 정의할 때는 작업을 위한 적절한 모듈을 선택하여 사용해야 한다.

예를 들어 docker를 설치하는 task를 정의할 때는 ansible.builtin.shell 모듈을 사용하여 쉘 명령어를 실행할 수 있지만, ansible.builtin.apt 모듈을 사용하여 apt 패키지를 설치하는 것이 더 안전하다.

Ansible에는 매우 다양한 모듈이 존재하므로 필요한 모듈을 검색하고 문서를 참고해 사용하면 된다.

예를 들어 위에 언급한 ansible.builtin.shell 모듈ansible.builtin.apt 모듈에 대한 문서도 검색하여 참고할 수 있다.

여기까지는 아주 간단한 Playbook 예시이다. Ansible Playbook에 대한 더 자세한 내용은 위에 첨부한 링크를 참고하면 된다.

Ansible로 홈서버 세팅하기 (w/ syncthing with docker)

이제 Ansible을 사용하여 홈서버를 세팅해보자. 홈서버는 라즈베리 파이 4B를 사용하며, 우분투 24.04를 사용한다. 홈서버에는 syncthing을 사용하여 파일을 동기화하고, docker를 사용하여 syncthing을 실행한다.

synching은 평소 사용하던 오픈소스 파일 동기화 프로그램으로 관련된 게시글을 삭성한 적이 있다.

Inventory 파일 작성

Inventory 파일을 작성하여 홈서버에 대한 정보를 정의한다.

1
2
[home]
home.example.com #도메인이 아니라 IP 주소를 사용해도 된다.

Playbook 작성

Playbook에 docker와 syncthing을 설치하고, syncthing을 실행하는 task를 작성한다. 이때 remote_user는 설치에 사용할 유저 이름으로 설정하면 된다.

Docker의 설치는 Install Docker Engine on Ubuntu 문서를 참고하였다. (Ubuntu 24.04 기준 동작 확인)

Syncthing docker에 대해서는 Syncthing README-Dokcer.md를 참고하였다.

docker 모듈을 사용하기 위해서는 docker collection(모듈 역할 등의 모음집)이 설치되었는지 확인해야 한다. ansible-galaxy collection list | grep docker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
- name: Update all packages to the latest version
  hosts: home
  remote_user: user # 설치에 사용할 유저 이름
  become: true
  tasks:
    - name: Update all packages to the latest version
      ansible.builtin.apt:
        update_cache: true
        upgrade: dist
    
    - name: Unconditionally reboot the machine with all defaults
      ansible.builtin.reboot:

- name: Setup Docker
  hosts: home
  remote_user: user # 설치에 사용할 유저 이름
  become: true
  vars:
    arch_mapping:  # Map ansible architecture  names to Docker's architecture names
      x86_64: amd64
      aarch64: arm64

  tasks:
    - name: Install required packages
      ansible.builtin.apt:
        name:
          - ca-certificates
          - curl
    
    - name: Create directory for Docker's GPG key
      ansible.builtin.file:
        path: /etc/apt/keyrings
        state: directory
        mode: '0755'

    - name: Add Docker's official GPG key
      ansible.builtin.get_url:
        url: https://download.docker.com/linux/ubuntu/gpg
        dest: /etc/apt/keyrings/docker.asc
      
    - name: Change permissions of Docker's GPG key
      ansible.builtin.file:
        path: /etc/apt/keyrings/docker.asc
        mode: a+r

    - name: Print architecture variables
      ansible.builtin.debug:
        msg: "Architecture: {{ ansible_architecture }}, Codename: {{ ansible_lsb.codename }}"

    - name: Add Docker repository
      ansible.builtin.apt_repository:
        repo: >-
          deb [arch={{ arch_mapping[ansible_architecture] | default(ansible_architecture) }}
          signed-by=/etc/apt/keyrings/docker.asc]
          https://download.docker.com/linux/ubuntu {{ ansible_lsb.codename }} stable 
        filename: docker
        state: present

    - name: Install Docker and related packages
      ansible.builtin.apt:
        name:
          - docker-ce
          - docker-ce-cli
          - containerd.io
          - docker-buildx-plugin
          - docker-compose-plugin
        update_cache: true

    - name: Add Docker group
      ansible.builtin.group:
        name: docker
        state: present

    - name: Add user to Docker group
      ansible.builtin.user:
        name: "{{ ansible_user }}"
        groups: docker
        append: true

    - name: Enable and start Docker services
      ansible.builtin.systemd_service:
        name: "{{ item }}"
        enabled: true
        state: started
      loop:
        - docker.service
        - containerd.service 

- name: Setup syncthing using Docker
  hosts: home
  remote_user: user # 설치에 사용할 유저 이름
  tasks:
    - name: Run Syncthing container
      community.docker.docker_container:
        name: syncthing-server
        image: syncthing/syncthing:latest
        hostname: syncthing-server
        pull: always
        restart_policy: always
        ports:
          - "8384:8384"
          - "22000:22000"
          - "22000:22000/udp"
          - "21027:21027/udp"
        volumes:
          - "{{ ansible_facts.user_dir }}/syncthing:/var/syncthing" # syncthing 디렉토리로 원하는 경로를 지정하면 된다.
        env: 
          PUID: "{{ ansible_facts.user_uid }}" # syncthing을 root가 아닌 사용자로 실행하기 위해 사용자 정보를 전달한다.
          PGID: "{{ ansible_facts.user_gid }}"
        state: started

Playbook 실행

위 Playbook을 실행하여 홈서버를 세팅한다.

1
ansible-playbook -i inventory playbook.yaml

개인적으로 홈서버를 구축할때 사용한 전체 코드를 공유하고 싶지만, 보안에 민감한 정보가 포함되어 있어 공유하지 못하였다.

추가적인 참고 자료

여기까지 진짜 간단한 Ansible 사용법에 대해 알아보았다. Ansible에서는 이외에도 더 강력한 기능들이 많이 존재한다. 추가로 참고할만한 자료들을 정리해보았다.

  • Ansible Documentation: Ansible 공식 문서
  • Ansible Galaxy: Ansible 커뮤니티에서 제공하는 collecttion 및 role을 검색할 수 있는 사이트
  • Ansible Collections: Ansible의 모듈, 플러그인, 역할, 플레이북 등을 패키징하여 배포할 수 있는 방식. 여러 관련된 리소스를 하나의 단위로 묶어 관리하고 공유하는 것을 쉽게 해준다.
  • Ansible Vault: Ansible에서 제공하는 암호화 도구로, Playbook에 민감한 정보를 저장할 때 사용한다.
  • Ansible Roles: Role은 Playbook을 더 작은 단위로 나누어 재사용 가능한 구성 요소로 만드는 방법이다.
This post is licensed under CC BY 4.0 by the author.