글 작성자: beaniejoy

개인 프로젝트의 배포 툴로 Jenkins와 Provisioning 도구로 Ansible(이하 앤서블)을 사용해보고 있습니다.
배포 대상이 되는 서버에는 애플리케이션 실행에 필요한 패키지들이 설치되어 있어야 하는데요. 앤서블은 이러한 패키지들이 설치되어 있지 않다면 설치하고 이후 애플리케이션 배포 프로세스가 진행되도록 자동화할 수 있게 해줍니다.

여기서 문제는 기존의 배포 서버를 다른 운영체제의 서버로 migration 한다거나 추가했을 때 기존 앤서블 playbook 스크립트에 설정된 내용들을 고쳐야 하는 번거로움이 생길 수 있습니다. 이게 어떤 문제 상황인지 구체적으로 알아보고 어떻게 하면 해결을 할 수 있을지 알아보겠습니다.

(기본적인 앤서블 사용방법에 대해서 알고계셔야 합니다. ansible roles, ansible 사용법에 대해서 알고 계신 상태로 아래 게시글을 읽으시기를 권장드립니다.)

 

1. 문제 상황

애플리케이션 서버에 reverse proxy로 nginx를 사용한다면 배포 전에 nginx가 설치되어 실행되고 있어야 합니다.
또한 spring boot 기반의 애플리케이션이므로 build된 jar파일을 실행하려면 버전에 맞는 java 패키지가 설치되어 있어야 합니다.
만약 없다면 이에 대해서 대비하는 설정이 배포 프로세스에 있어야 합니다. 앤서블은 이러한 패키지 관리를 깔끔하게 해줍니다.

# roles/package/tasks/main.yaml
---
- name: Install java
  ansible.builtin.dnf:
    name: "{{ java_package }}"
    state: present
  tags:
    - init

- name: Install nginx
  ansible.builtin.dnf:
    name: "{{ nginx_package }}"
    state: present
  tags:
    - init
    
# roles/package/vars/main.yaml
java_package: java-17-amazon-corretto
nginx_package: nginx

package role 단계에서 앤서블에 등록된 배포서버에 java와 nginx 패키지가 설치되어 있지 않다면 install 실행을 하게 됩니다.

여기서 주목해야할 점은 패키지 관리 툴로 dnf를 사용했습니다. 앤서블은 참고로 dnf 뿐만 아니라 Debian계열의 운영체제에서 사용되는 apt, RedHat 진영에서 사용되는 yum 등 여러 패키지 관리 툴을 지원하고 있습니다.

저는 배포서버로 AWS EC2를 사용했고 운영체제는 Amazon Linux 2023을 사용하고 있는 상황입니다. 그렇기에 ansible의 dnf 모듈을 사용해서 install을 진행했는데요. 문제 상황은 바로 여기서 발생하게 됩니다.

해당 앤서블 스크립트를 가지고 다른 운영체제의 배포서버에 배포를 진행하게 된다면 어떻게 될까요. 예를 들어 Ubuntu 운영체제를 가진 배포서버에 해당 앤서블 스크립트를 가지고 배포 진행을 한다면 당연히 에러가 발생하게 될 것입니다.

또한 Amazon Linux 2023 운영체제에서는 java 패키지로 java-17-amazon-corretto를 사용해야 하지만 다른 운영체제의 서버에서는 openjdk-17을 사용할 수 있습니다. 다시 말하면 위의 앤서블 스크립트는 오로지 Amazon Linux를 사용하는 EC2 서버에서만 사용가능한 파일이라고 할 수 있습니다.

앤서블을 사용하는 여러 이유 중 하나는 범용성에 있다고 생각합니다. 특정 운영체제와 환경을 가진 서버 한 대에만 동작하는 배포 스크립트는 추후에 변경될 여지가 많습니다. 물론 특정 운영체제의 서버로 고정해서 사용할 거라면 별 문제는 되지 않겠지만 다른 운영체제의 서버에 대해서도 적용이 필요하다면 위의 스크립트는 수정해서 따로 관리를 해야하는 이슈가 생기게 됩니다.

# roles/package/tasks/main.yaml
---
- name: Install java
  ansible.builtin.dnf:
    name: "{{ java_package }}"
    state: present
  tags:
    - init

- name: Install nginx
  ansible.builtin.dnf:
    name: "{{ nginx_package }}"
    state: present
  tags:
    - init
    
# ubuntu에서 사용하려면 ansible.builtin.apt 모듈을 사용해야 하는데 
# 스크립트 파일을 분리해서 따로 관리??

 

2. 해결 방법

앤서블은 ansible facts를 사용하여 위와 같은 상황에서도 하나의 스크립트 파일로 관리할 수 있도록 할 수 있습니다.

ansible에는 facts라는 변수들이 있습니다. ansible에 설정된 remote server에 대한 여러 정보들을 facts라는 변수들로 제공해주고 있습니다. ansible 공식 문서에 facts 관련 내용을 살펴보면 원격 서버에 대한 다양한 정보들이 담긴 변수 내용들을 확인해볼 수 있습니다.

 

Discovering variables: facts and magic variables — Ansible Documentation

Ansible facts are data related to your remote systems, including operating systems, IP addresses, attached filesystems, and more. You can access this data in the ansible_facts variable. By default, you can also access some Ansible facts as top-level variab

docs.ansible.com

수 많은 facts 내용 중에 ansible_distribution이라는 변수를 사용해보려 합니다. remote server의 운영체제를 알려주는 변수입니다. 이를 활용하면 원격 서버의 운영체제 종류에 따른 패키지 내용과 패키지 관리 툴을 설정할 수 있을 것 같습니다.

 

3. 적용해보기

---
- name: Check OS
  ansible.builtin.debug:
    msg: "OS: {{ ansible_distribution }} / OS version: {{ ansible_distribution_version }}"
  tags:
    - always

- name: Install java
  action: >
    {{ pkg_mgr[ansible_distribution] }} name={{ java_package[ansible_distribution] }} state=present
  tags:
    - init

- name: Install nginx
  action: >
    {{ pkg_mgr[ansible_distribution] }} name={{ nginx_package[ansible_distribution] }} state=present
  tags:
    - init

Check 단계에서 OS 배포판과 배포판 버젼을 체크하여 로그를 남겼습니다. 그 다음에 패키지 설치를 해야하는데, 운영체제별 패키지 관리 툴 설정이 필요합니다. ansible_facts에 ansible_pkg_mgr이라는 것이 있어서 remote server의 운영체제 맞는 패키지 관리 툴을 설정할 수 있을 것 같습니다. 다만 저는 직접 관리하고 싶어서 ansible playbook vars에 따로 패키지 관리 툴을 OS별로 설정했습니다.

# roles/package/vars/main.yml
pkg_mgr:
  Amazon: dnf
  Debian: apt

java_package:
  Amazon: java-17-amazon-corretto
  Debian: openjdk-17-jre
nginx_package:
  Amazon: nginx
  Debian: nginx

 package roles에 vars/main.yml 파일에다가 위와 같이 변수를 등록하면 package tasks 안에서 해당 변수를 사용할 수 있게 되는데요.

- name: Install java
  action: >
    {{ pkg_mgr[ansible_distribution] }} name={{ java_package[ansible_distribution] }} state=present
  tags:
    - init

이렇게 {{ }} 안에 pkg_mgr을 사용해서 ansible_distribution facts에 따라 OS에 맞는 패키지 관리툴을 지정할 수 있습니다. java_package도 마찬가지로 ansible_distribution에 따라 위에서 vars로 미리 설정한 java package 이름이 적용됩니다.

 

4. 참고 사항

마지막으로 위와 같이 ansible_facts를 사용하려면 gather_facts 모드를 활성화해야 하는데요. ansible playbook에 따로 설정한 것이 없으면 default로 활성화되어 있어서 playbook 스크립트 파일 내에 ansible_facts을 사용할 수 있긴합니다.

- name: "Deploy service-api({{ spring_profile }})"
  hosts: ec2
  gather_facts: false
  ...

다만 혹여나 gather_facts를 false 처리했다면 위와 같이 facts를 사용할 수 없게 되니 facts를 사용하고 싶으시다면 해당 값을 true로 설정하거나 제거하시면 됩니다. 이 점 참고하세요.

그리고 해당 게시글에 대한 전체 ansible 스크립트 내용은 github repo를 참고하시면 될 것 같습니다.

 

GitHub - beaniejoy/ansible-deploy-script: ansible playbook for application(Spring Boot) deployment

ansible playbook for application(Spring Boot) deployment - GitHub - beaniejoy/ansible-deploy-script: ansible playbook for application(Spring Boot) deployment

github.com