Engineering
Ansible & AWX (설치부터 기본 활용 및 NHN Cloud Deploy 연동까지)
2020년 11월 9일
원문에서 보기 ↗awx 15.0.0 버전 이준으로 작성된 글입니다. 2021년 3월 TCD(TOAST Cloud Deploy)의 명칭이 NCD(NHN Cloud Deploy)로 변경되었습니다. 아래 게시글은 2020년 11월 작성된 글로 TCD로 작성되었습니다.
서버를 증설하거나, 기존 설정이 변경되어, 한대 혹은 여러대의 서버 작업이 필요하다면?
example
20대의 서버에
- 기본 디렉토리 생성.
- jdk 설치.
- scouter 파일 복사 및 설정 변경.
- managerAgent.war 파일 복사.
- script 파일 복사.
- crontab 등록.
- 등등...
위 작업을 과장 조금 더해서 클릭 몇번으로 가능하게 해줍니다.
ansible 이란?
여러대의 서버를 효율적으로 관리하기 위해 고안된 환경 구성 자동화 도구 중 하나로, agentless (ssh), 멱등성, 다양한 모듈 지원, 접근 용이성 (python) 등의 특징이 있습니다.
license
GPL 3.0 (ansible) / Apache 2.0 (awx) 을 따르며, 사내에서 환경설정 용도로 이용한다면 문제 없음을 확인 하였습니다.
ansible 의 경우 GPL 3.0 라이선스를 따르고 있고, GPL이 적용된 오픈소스를 포함하여 배포하는 경우 연관된 소스들은 함께 공개해야하는 의무가 있습니다.
다만 서버에서만 설치하고, 웹/네트워크를 통해 서비스 하는 형태라면(=해당 오픈소스가 다른 곳으로 배포되지 않는다면) 사용 가능합니다.
awx는 Apache 2.0 라이선스를 따르고 있고, 수정해서 사용한다면 수정 목적에 대해 간단하게 소스 상단에 작성을 유지해야합니다. 위 Ansible과 마찬가지로 웹/네트워크를 통해 서비스하게 되는 경우 해당 의무사항은 없습니다.
라이선스 의무 조항만 지킨다면 오픈소스로 사용이 가능합니다.
환경 구성 자동화 도구에는 어떤 것들이 있을까?
| chef | ansible | puppet | salt | |
|---|---|---|---|---|
| 구현 언어 | Ruby / Erlang | Python | Ruby | Python |
| 정의 파일 | DSL (Ruby) | YAML | DSL (Ruby) | YAML, DSL (Python) |
| 에이전트 설치 | Pull (Agent) | Push (AgentLess) | Pull (Agent) | 선택가능 |
| Github Star | 6.4k | 45.5k | 5.9k | 11.3k |
| 장점 | * 프로그래밍이 가능 * Community 및 문서 * 대규모 배포에 안정적 | * 설치 및 설정이 쉽다 * Agentless * 다양한 모듈 지원 * 멱등성 * SSH / winRM 연결 방식은 다른 모델과 비교하여 안전하다 | * 초기 설정 및 구성이 쉽다 * 웹 ui 콘솔은 노드 관리 작업을 쉽게 한다. * Shell 수준의 구조로 작업 할 수 있는 기능이 있다 * 대규모 인프라 관리를 위해 매우 안정적 * Community 및 문서 | * 확장성과 복원력이 뛰어나다 * 미니언의 사용은 더 많은 옵션과 유연성을 제공 |
| 단점 | * 학습곡선이 높음 * 초기 설정이 복잡 * pull-based 방식 | * SSH 통신 속도 | * DSL 이나 Ruby 의 학습곡선 * CLI 의 부제 * pull-based 방식 | * No GUI |
구글 트렌드

awx 란?
ansible tower 의 오픈소스 버전으로, ansible 을 GUI 로 관리하고, api 로 제어할 수 있도록 해주는 시스템.

위 이미지와 같이, LAUNCH 버튼 한번 클릭으로, 서버 설정이 완료 됩니다. :)
ansible & awx 설치
awx 가 docker 기반으로 설치가 되기 때문에, centos 6 에서는 설치가 쉽지 않습니다. (docker 에서 공식 지원을 하지 않... orz)
준비물
centos 7 python 3.6 docker
python 설치
python 3 버전을 기준으로 설명 하겠습니다.
sudo yum install gcc zlib-devel openssl openssl-devel libffi-devel -y
wget https://www.python.org/ftp/python/3.6.8/Python-3.6.8.tar.xz
tar -xvf Python-3.6.8.tar.xz
cd Python-3.6.8
mkdir -p /home/ansible/package
./configure --prefix=/home/ansible/package/Python-3.6.8
make && make install
pip 설치.
wget https://bootstrap.pypa.io/get-pip.py
/home/ansible/package/Python-3.6.8/bin/python3 ./get-pip.py
virtualenv 설치. (option)
이 단계는 꼭 진행할 필요는 없으며, python 패키지를 별도 관리하고 싶을 경우만 진행하시면 됩니다. (진행 추천)
cd /home/ansible/package/Python-3.6.8/bin
./pip3 install virtualenv
./pip3 install virtualenvwrapper
# vi ~/.bashrc
export VIRTUALENVWRAPPER_PYTHON=/home/ansible/package/Python-3.6.8/bin/python3
source /home/ansible/package/Python-3.6.8/bin/virtualenvwrapper.sh
# vi ~/.bash_profile
PATH=$HOME/package/Python-3.6.8/bin:$PATH:$HOME/bin
export WORKON_HOME=$HOME/.virtualenvs
export PATH
source ~/.bash_profile
mkvirtualenv -p /home/ansible/package/Python-3.6.8/bin/python3 ansible
ansible 설치.
순서 대로 진행을 하였다면,

별도로 위 이미지와 같이 workon ansible 을 실행하지 않아도, 앞에 (ansible) 과 같이 표기가 될 겁니다. (virtualenv 를 설치하지 않았다면 무시하시면 되고, 아래에 나오는 pip 를 통한 패키지 설치는 모두 가상환경 상태에서 진행한다고 보시면 됩니다.)
pip install ansible
workon
# 가상환경 ansible 접속
workon ansible
# 가상환경 ansible 종료
deactivate
awx 설치 및 실행.
# docker
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install -y docker-ce docker-ce-cli containerd.io
# docker compose
sudo curl -L "https://github.com/docker/compose/releases/download/1.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
docker-compose --version
# docker 구동 및 등록
sudo systemctl start docker
sudo systemctl enable docker
# root 외에 계정도 docker 를 이용할 수 있도록 usermod 변경.
sudo usermod -a -G docker ansible
# ansible 계정으로 재 접속 (꼭 재접속) 후, 테스트
docker ps
# python docker / docker-compose
pip install docker
pip install docker-compose
# git
sudo yum install -y curl-devel expat-devel gettext-devel perl-ExtUtils-MakeMaker
cd ~/downloads
wget https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.22.0.tar.xz
tar -xvf git-2.22.0.tar.xz
cd git-2.22.0
make prefix=/home/ansible/package/git all
make prefix=/home/ansible/package/git install
# vi ~/.bash_profile
PATH=$HOME/package/git/bin:$PATH
export PATH
# node.js (LTS 10)
curl -sL https://rpm.nodesource.com/setup_10.x | sudo bash -
sudo yum install -y nodejs
# https://github.com/ansible/awx/releases
cd ~/downloads
wget https://github.com/ansible/awx/archive/15.0.0.tar.gz
tar -xvf 15.0.0.tar.gz
ln -s awx-15.0.0 awx
# 혹은
# use github
cd ~/downloads
git clone https://github.com/ansible/awx.git awx_git
ln -s awx_git awx
# vi /home/ansible/downloads/awx/installer/inventory
# 아래 항목들을 찾아 주석을 제거 하거나, 값을 변경해 줍니다.
# use_docker_compose=true 의 경우, 추가.
localhost ansible_connection=local ansible_python_interpreter="/home/ansible/.virtualenvs/ansible/bin/python"
postgres_data_dir=/home/ansible/package/awx/pgdocker
host_port=10080
host_port_ssl=10081
use_docker_compose=true
docker_compose_dir=/home/ansible/package/awx/awxcompose
project_data_dir=/home/ansible/package/awx/projects
# awx 최초 실행 (docker-compose.yml 파일 생성)
# 이때, "Create Docker Compose Configuration" 에서 libselinux-python 에러가 날 경우, selinux 설정을 disabled 시키고 해볼 것.
cd /home/ansible/downloads/awx/installer
/home/ansible/.virtualenvs/ansible/bin/ansible-playbook -i inventory install.yml
# awx 중지 / 시작
cd /home/ansible/package/awx/awxcompose
docker-compose stop
docker-compose start
# awx 로그 확인
docker logs -f awx_task
# docker process 확인
# awx_task, awx_web, awx_postgres, awx_redis 가 동작하고 있어야 함.
docker ps
# awx 접속
# 초기 비번 : admin / password
http://{서버IP}:10080
awx 접속

이 화면이 보이고, 초기 비번으로 로그인이 되었다면, 무사히 설치가 되었습니다 :)
최초 구동 시, loading 화면에서 조금 오래 걸릴 수 있습니다. docker logs -f awx_task 로 확인 시, 오류가 발생 중이라면, 위에 중지 / 시작 단계를 통해서 재시작을 시켜주세요.
사내 계정 연동 (LDAP)
사내 계정으로 로그인을 하고 싶다면??

Settings -> Authentication -> LDAP 을 통해서, 등록이 가능합니다. :)
ansible 기본 사용. (command)
jdk 파일을 배포하는 방법으로 기본 사용법을 설명하겠습니다.
준비물
virtualbox 를 이용해, centos 7 vm 을 하나 만들고, 거기에 ansible / awx 를 설치 후, 진행합니다. (네트워크 : 호스트 전용 등, ssh 접근이 가능한 형태)
디렉토리 구조 및 파일 상세
/home/ansible/package/awx/projects/ansible_first 에 아래 파일들을 생성 합니다. (awx 설치가 완료 되고 나면 projects 경로가 root 권한으로 되어 있습니다. super user 를 통해서 ansible_first 를 생성하고, 소유자를 변경해주세요.)
sudo mkdir -p /home/ansible/package/awx/projects/ansible_first
sudo chown ansible:ansible /home/ansible/package/awx/projects/ansible_first
ansible.cfg
group_vars/
common.yml
hosts/
local
playbooks/
commons/
openjdk-role.yml
roles/
commons/
openjdk/
tasks/
main.yml
vars/
main.yml
ansible.cfg
기본 설정으로, roles 의 경로를 지정 합니다. 지정하지 않을 경우, playbook 파일이 있는 곳의 roles (~/playbooks/commons/roles) 가 기본이 됩니다.
[defaults]
roles_path = ./roles
group_vars/common.yml
공통으로 이용하는 값을 설정 합니다.
# project
project_home: /home/ansible
apps_home: /home/ansible/apps
download_home: /home/ansible/downloads
hosts/local
ansible 을 이용하여 배포할 서버 정보. (local:children 과 같이, 여러개의 서버를 한 그룹으로 묶을 수 있습니다.)
[local:children]
centos7
[centos7]
{{vm_ip}} ansible_user=ansible
playbooks/commons/openjdk-role.yml
ansible & awx 에서 실행하는 하나의 단위라고 보면 될 것 같습니다. (하나 혹은 여러 roles 를 지정)
---
- name: install openjdk
vars_files:
- ../../group_vars/common.yml
hosts: "{{ host_vars }}"
roles:
- commons/openjdk
roles/commons/openjdk
하나의 실행 시나리오.
# tasks/main.yml
---
- name: Directory exists (downloads)
file:
path: "{{ download_home }}"
state: directory
- name: Download JDK
get_url:
url: "https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u265-b01/{{ file_jdk }}.tar.gz"
dest: "{{ download_home }}/{{ file_jdk }}.tar.gz"
mode: 0755
- name: Directory exists (path jdk)
file:
path: "{{ path_jdk }}"
state: directory
- name: Extract archive (jdk)
unarchive:
src: "{{ download_home }}/{{ file_jdk }}.tar.gz"
dest: "{{ path_jdk }}"
extra_opts: [--strip-components=1]
remote_src: "yes"
- name: Create a symbolic link
file:
src: "{{ path_jdk }}"
dest: "{{ java_home }}"
state: link
- name: Remove file (jdk)
file:
path: "{{ download_home }}/{{ file_jdk }}.tar.gz"
state: absent
- name: profile
lineinfile:
path: "{{ project_home }}/.bashrc"
line: "{{ item }}"
insertafter: EOF
state: present
loop:
- "export LC_ALL=ko_KR.UTF-8"
- "export LANG=ko_KR.UTF-8"
- "export JAVA_HOME={{ java_home }}"
- "export PATH=${JAVA_HOME}/bin:$PATH"
# vars/main.yml
# file
file_jdk: OpenJDK8U-jdk_x64_linux_hotspot_8u265b01
# path
path_jdk: "{{ apps_home }}/{{ file_jdk }}"
# java
java_home: /home/ansible/apps/jdk
ansible 실행
command line 명령으로 jdk 설치를 진행해 보겠습니다.
# -vvv 옵션은 디버그 모드
# openjdk-role.yml 라는 플레이 북을, hosts/local 에 있는 "local" 그룹에 실행 시켜라.
workon ansible
ansible-playbook -i hosts/local playbooks/commons/openjdk-role.yml --extra-vars '{"host_vars":"local"}' -vvv

위와 같이 permission denied 가 발생합니다. 기본적으로 ansible 의 경우, ssh 통신을 하기 때문에, known_host 등록 과 공개키 등록하는 작업이 필요합니다. (awx credentials 에 등록할 개인키의 쌍이되는 공개키를 각 서버에 등록)
그럼 여기서, 수동으로 다 등록을 해야 하는거냐? 라는 생각이 들 수 있는데, 이후에 TCD (TOAST CLOUD DEPLOY) 를 이용하는 방법을 설명 하겠습니다.
# 개인키 및 공개키 생성
ssh-keygen -t rsa
# 공개키 등록
ssh-copy-id -i ~/.ssh/id_rsa.pub ansible@{{vm_ip}}

위와 같이 failed=0 이 나와야 합니다. (이렇게 나오면 정상적으로 jdk 설치가 완료!)
기본 용어
playbooks
설정을 관리하고, ansible 실행 기본 단위. (하나 혹은 여러개의 roles 로 이루어짐. 위 예제에서는 openjdk 하나)
roles
playbook 에서 다양한 서버 설정에 대한 그룹을 구분짓는 단위. (하나 혹은 여러개의 task 로 이루어짐. 디렉토리 생성, 파일 다운로드 등)
tasks
ansible 의 실제 작업 단위. (디렉토리를 생성하고, 파일을 다운로드 합니다.)
inventory
관리 노드 목록. (서버 리스트. 위 예제에서는 centos7 하나를 local 로 그룹핑 함.)
vars
role 에서 사용할 변수를 정의. (vars 의 종류가 다양하며, 우선순위 또한 존재합니다.)
아래 이미지의 번호가 클수록 우선순위가 높습니다.

AWX 등록 과 실행
위에서는 기본적인 command 라인 명령어와 기본 문법을 봤다면, awx 에서는 그걸 gui 에서 실행하는 방법을 알아보겠습니다.
참고로, awx gui 메뉴에서는 코드 수정이 불가능 합니다! 그래서 저 같은 경우는 awx 에 프로젝트를 github 연동을 해 놓고, 로컬에서 코드 수정 후, 간단히 테스트 하고, commit 후, awx 에서 테스트를 진행 합니다.
프로젝트 등록
앞으로 우리가 실행 할, playbook 등의 파일 위치가 정해집니다. (github 의 repository 선택 단계라고 보시면 편할 것 같습니다.)

SCM TYPE 을 Manual 로 선택을 하면, 처음 우리가 awx 설치 할때, inventory 파일 수정했던, project_data_dir 이 고정 됩니다. (즉 awx 서버에서 직접 소스 코드를 수정하고, 바로 실행하려면 이 방식을 선택)
inventory
playbook 을 실행 시킬 서버 등록 inventory / group / host / group 추가 순서대로 진행. (git 연동 시에는 좀 더 간단한 방법으로 처리 가능)

credential 등록
배포 대상 서버에 접근 시 이용할 개인키 정보를 등록해 줍니다. (여기서는 awx 서버에서 생성한 키정보를 이용합니다.)
cat ~/.ssh/id_rsa

template 등록
실제 실행시킬 playbook 을 등록하는 메뉴.

prompt on launch 를 체크 할 경우, 해당 항목은 실행 시, 한번 더 물어보고, 해당 화면에서 변경이 가능합니다.
template 실행

실행 이력 및 상태 확인 (Jobs)
jobs 메뉴에서 현재 실행중인 job 과 이력등을 확인 할 수 있다. 선택 시, 상세 결과 화면 조회 가능.

별책부록 (github 연동 편)
AWX Github project 등록
credential
github repository 에 접근할 계정 정보를 등록. (위에서 생성한 credential 과는 다른 용도 입니다.)

project
playbook 이 등록된 repository 주소를 등록.

프로젝트 등록을 하고, 리스트 조회를 하면, 기존 ansible first 프로젝트와는 다른 버튼들이 생긴것을 확인 할 수 있습니다. 오른쪽에 화살표 버튼은 github 에서 최신 정보를 update 하는 버튼이고, 왼쪽 빨간 느낌표는 update 상태인데, 현재 VM 에서 회사 github 접속을 하지 못하기 때문에 경고 표시가 뜬 것 입니다. (VM 의 네트워크를 호스트 전용으로 하고, 호스트 컴퓨터에서 VPN 을 연결 하였다면 접속이 가능합니다.)
inventory
github 연동 프로젝트의 경우, host 를 하나하나 등록하지 않아도 되며, inventory 생성 후, source 를 등록하고, 싱크를 맞춰주면 host 가 자동으로 등록이 됩니다. (위 예제를 github 에 올렸다면 hosts/local 파일에 등록된 host 가 등록 가능)

template 등록 / 실행
예제에 나왔던 것과 동일 합니다.
TCD 를 통해 공개키 등록하기.
TCD 는 어플리케이션 배포 서비스로, NHN 에서는 TCD 를 이용해 서비스 배포를 하고 있습니다 :) (TCD 를 활용할 경우, 20대 서버에 배포한다고 가정할 때, 4대씩 혹은 1대씩 순차적으로 배포가 가능 합니다.)
awx 서버의 StrictHostKeyChecking 옵션 변경
TCD 를 통한 공개키 등록 전에, 먼저 known_host 설정을 변경해야 합니다.
# vi ~/.ssh/config
Host *
StrictHostKeyChecking no
chmod 400 ~/.ssh/config
위와 같이 설정을 등록할 경우, known_host 등록 여부를 묻지 않습니다.
공개키 등록
TCD 시나리오를 활용하여, awx 서버의 공개키 (위에서 등록한 개인키와 쌍이되는) string 을 python 스크립트를 통해 각 서버에 등록 하는 방법 입니다. (리소스에 python 스크립트를 등록, 배포할 서버 그룹을 선택하여 공개키 등록)

write_authorized_keys.py
# -*- coding: utf-8 -*-
import errno
import os
import stat
import sys
ANSIBLE_CLIENT = "ansible@{{ awx hostname }}"
ANSIBLE_PUBLIC_KEY = "{{ 공개키 }}"
# 디렉토리 만들기
def make_dir(dir_path):
try:
if not(os.path.isdir(dir_path)):
os.makedirs(os.path.join(dir_path))
except OSError as e:
if e.errno != errno.EEXIST:
print("Failed to create directory!!!!!")
raise
# 공개키 등록
def write_authorized_keys(file_path):
fp = os.path.join(file_path, "authorized_keys")
lines = []
if os.path.isfile(fp):
with open(fp, "r") as f:
lines = f.readlines()
with open(fp, "w") as f:
for line in lines:
if line.find(ANSIBLE_CLIENT) == -1:
f.write(line)
f.write(ANSIBLE_PUBLIC_KEY)
os.chmod(fp, stat.S_IREAD | stat.S_IWRITE)
if __name__ == '__main__':
if len(sys.argv) == 2:
ssh_path = sys.argv[2]
else:
ssh_path = os.path.expanduser('~') + "/.ssh"
make_dir(ssh_path)
write_authorized_keys(ssh_path)
awx api 활용.
특정 작업을 진행 시, 서비스를 내리고, 교체 작업을 진행해야 하는 경우에 활용이 가능 합니다. (scouter / jdk 버전 업 등)
awx 토큰 발급
awx api 를 이용하기 위해서는 토큰 발급 과정이 필요 합니다. (여기서는 tcd 라는 계정 또한 신규로 생성)
application 생성
awx -> Applications -> +

이때 생성되는 secret 정보는 아래 작업에서 필요하지 않습니다.
user 생성
유저 생성 후, 권한 추가 필요.

토큰 생성
tcd 계정으로 재 로그인 후, 토큰 발급을 진행한다.
awx -> Users -> tcd -> TOKENS -> +
SCOPE 를 Write 로 설정하지 않으면, template 실행 불가.
(이 TOKEN 정보 기억 필요)
API 테스트
API 호출 시, JSON String 으로 응답.
https://docs.ansible.com/ansible-tower/latest/html/towerapi/api_ref.html 위 사이트에서 API 확인 가능
job_templates 조회
# 요청
curl --location --request GET 'http://192.168.1.205:10080/api/v2/job_templates' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer TMMeHBAmFHBKrGlktjS6XO3TWvQjF4'
# 응답
{
"count":1,
"next":null,
"previous":null,
"results":[
{
"id":9,
"type":"job_template",
"url":"/api/v2/job_templates/9/",
"created":"2020-10-14T09:01:21.536040Z",
"modified":"2020-10-14T09:01:21.536066Z",
"name":"template first",
"playbook":"playbooks/commons/openjdk-role.yml",
}
]
}
template 실행
job_template 조회에서 확인된 id 값을 path_variable 로 전달. (여기선 9)
# 요청
curl --location --request POST 'http://192.168.1.205:10080/api/v2/job_templates/9/launch/' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer TMMeHBAmFHBKrGlktjS6XO3TWvQjF4' \
--data '{
"extra_vars":{
"host_vars":"local"
}
}'
# 응답
{
"job":4,
"ignored_fields":{
},
"id":4,
"type":"job",
"url":"/api/v2/jobs/4/",
}
job 상태 확인
template 실행 후, 받은 응답값의 id 값을 가지고 job 상태 확인이 가능. (여기서는 4)
# 요청
curl --location --request GET 'http://192.168.1.205:10080/api/v2/jobs/4/' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer TMMeHBAmFHBKrGlktjS6XO3TWvQjF4'
# 응답
{
"id":4,
"type":"job",
"url":"/api/v2/jobs/4/",
"created":"2020-10-14T14:34:31.647515Z",
"modified":"2020-10-14T14:34:32.005339Z",
"name":"template first",
"job_type":"run",
"playbook":"playbooks/commons/openjdk-role.yml",
"extra_vars":"{\"host_vars\": \"local\"}",
"status":"successful",
"failed":false,
"started":"2020-10-14T14:34:32.048388Z",
"finished":"2020-10-14T14:35:07.141185Z",
}
응답값 중 status 값을 가지고 완료 여부를 판단.
TCD 에서 활용.
TCD 에서 이용하기 위해서, 위와 같이 curl 을 호출하고, 결과 parsing 하는걸 shell script 로 작성하기 어려워 이것 또한 python script 를 활용 하였습니다.

위와 같이, template name 과 host_vars 등을 인자로 받아서, 실행. (awx api 를 이용해, template 을 실행 시키고, 완료 될때까지 대기 하는 과정은 python script 내부에서 처리.)
awx_run_template.py
#!/usr/bin/python
# -*- coding:utf-8 -*-
import datetime
import sys
import time
import urllib2
import json
AWX_API_URL = "http://192.168.1.205:10080"
AWX_KEY = "TMMeHBAmFHBKrGlktjS6XO3TWvQjF4"
def get_templates():
url = AWX_API_URL + '/api/v2/job_templates'
headers = {'Authorization': 'Bearer ' + AWX_KEY}
req = urllib2.Request(url, None, headers)
response = urllib2.urlopen(req)
json_data = json.loads(response.read())
return json_data.get('results')
def get_template_id(template_name):
templates = get_templates()
for ii in templates:
if ii.get('name').encode('utf-8') == template_name:
return ii.get('id')
return None
def run_template(template_id, parameters):
url = "{url}/api/v2/job_templates/{id}/launch/".format(url=AWX_API_URL, id=template_id)
headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AWX_KEY}
req = urllib2.Request(url, parameters, headers)
response = urllib2.urlopen(req)
return json.loads(response.read()).get('id')
def check_job_status(job_id):
url = AWX_API_URL + '/api/v2/jobs/{id}/'.format(id=job_id)
headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AWX_KEY}
req = urllib2.Request(url, None, headers)
while True:
response = urllib2.urlopen(req)
json_data = json.loads(response.read())
print("{dt} job status : {status}".format(
dt=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), status=json_data.get('status')))
if json_data.get('status') == 'successful':
return True
else:
time.sleep(3)
if __name__ == "__main__":
template_name = None
parameters = ""
if len(sys.argv) == 2:
template_name = sys.argv[1]
parameters = '{"extra_vars":{"host_vars":"alpha","dist":"alpha","target":"all"}}'
elif len(sys.argv) == 3:
template_name = sys.argv[1]
parameters = sys.argv[2]
else:
print("usage : awx_run_template.py "
"\"playbook-first\" "
"'{\"extra_vars\":{\"host_vars\":\"alpha\",\"dist\":\"alpha\",\"target\":\"all\"}}'")
exit(-1)
try:
template_id = get_template_id(template_name)
print("{dt} template name : {name}, id : {id}".format(
dt=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), name=template_name, id=template_id))
job_id = run_template(template_id, parameters)
print("{dt} job id : {id}".format(
dt=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), id=job_id))
check_job_status(job_id)
except Exception as e:
print("awx run template error : {error}".format(error=e))
exit(-1)
부록 1 : backup & restore
awx 의 경우, awx 내에서 한 작업들은 대부분 postgresql 에 저장이 되므로, 이것만 백업해두면 됩니다. (playbook 등의 소스는 github 으로 관리)
# backup
docker exec -t awx_postgres pg_dump -c -d awx -U awx > awx_backup_db.dmp
# restore
cat awx_backup_db.dmp | docker exec -i awx_postgres psql -U awx
부록 2 : awx 버전 업
https://github.com/ansible/awx/blob/devel/INSTALL.md#upgrading-from-previous-versions
- postgresql backup (버전업 실패를 대비해서)
- 기존 구동 중인 docker 컨테이너 중지.
- 신규 버전을 다운로드 하여, 설치편의
ansible-playbook -i inventory install.yml전 까지 진행.- 버전업 파일 다운로드 후, 압축 풀고
- inventory 내부 정보 수정.
- ansible-playbook -i inventory install.yml
- awx 사이트 접속해 보고, 버전업이 되었는지 확인.
- 혹 정상 동작을 하지 않거나 하면, docker logs -f awx_task 를 확인하고, 상황봐서 기존 버전으로 원복.

주의
https://github.com/ansible/awx/releases
awx 의 경우, 버전업 속도가 꽤 빠른편으로, 예를 들어, 9.3.0 버전을 이용 중에, 15.0.0 버전으로 바로 업데이트는 추천하지 않는다. (바로 윗 단계의 버전으로 업데이트 하는 형태로 순차적 진행을 추천)
부록 3 : 모듈 및 문법
https://docs.ansible.com/ansible/2.5/modules/list_of_all_modules.html#all-modules ansible 에서는 다양한 모듈을 제공하여, 편리하게 이용할 수 있습니다. (yum / get_url / copy / unarchive / file 등등)
become / become_method
super user 로 실행.
---
- hosts: all
become: yes
become_method: sudo
tasks:
- name: install the latest version of Apache
yum:
name: httpd
state: latest
become / become_method 를 이용하지 못 하는 경우.
ansible_user 전환 후, command 명령어를 통해 이용하는 방법이 있습니다.
- name: change user (ansiblesu)
set_fact:
ansible_user: ansiblesu
- name: CAP_NET_BIND_SERVICE
command: "sudo /usr/sbin/setcap CAP_NET_BIND_SERVICE=+eip {{ path_nginx_home }}/sbin/nginx"
- name: change user (ansible)
set_fact:
ansible_user: ansible
단, command 의 경우, 멱등성을 제공하지 않습니다.
loop / with_items
반복문과 비슷한 성격으로, 동일 작업을 여러번 적을 필요가 없습니다.
- name: Copy template file (script)
template:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
mode: 0755
loop:
- { id: 1, src: "scripts/{{ file_rotatelog_sh }}.j2", dest: "{{ path_crontab }}/{{ file_rotatelog_sh }}" }
- { id: 2, src: "scripts/{{ file_compresslog_sh }}.j2", dest: "{{ path_crontab }}/{{ file_compresslog_sh }}" }
- name: Downloading NGINX sources
get_url:
url: "{{ item.url }}"
dest: "{{ item.dest }}"
with_items:
- { id: 1, url: "{{ file_server_url }}/{{ file_nginx }}.tar.gz", dest: "{{ download_home }}/{{ file_nginx }}.tar.gz" }
- { id: 2, url: "{{ file_server_url }}/{{ file_openssl }}.tar.gz", dest: "{{ download_home }}/{{ file_openssl }}.tar.gz" }
register / when
yml 파일을 작성하면서, 특정 작업에서 변경이 있을 경우만 특정 작업을 진행하고 싶을때가 있습니다. register 로 현재 상태를 등록하고, when 을 통해, change 일 경우만 압축을 푸는 경우의 예제 입니다.
- name: Downloading NGINX sources
get_url:
url: "{{ item.url }}"
dest: "{{ item.dest }}"
with_items:
- { id: 1, url: "{{ file_server_url }}/{{ file_nginx }}.tar.gz", dest: "{{ download_home }}/{{ file_nginx }}.tar.gz" }
register: nginx_source
- name: Unpacking NGINX
unarchive:
src: "{{ nginx_source.results.0.dest }}"
dest: "{{ path_nginx_unpack_dir }}"
extra_opts: [--strip-components=1]
remote_src: "yes"
when: nginx_source is changed
register: nginx_source_unpack
when 의 경우, register 된 상태 뿐만 아니라, 특정 값일 때 처리도 가능합니다. 아래는 service 라는 vars 값이 hangame / payco 가 아닐 경우에만 해당 작업을 실행해라 라는 의미입니다.
- name: nginx (source)
vars:
nginx_version: 1.17.8
include: "{{ item.role }}"
with_items:
- { id: 1, role: "source.yml", service: "{{ service }}" }
when: item.service != "hangame" and item.service != "payco"
부록 4 : sample
nginx 소스 설치
- name: change user (ansiblesu)
set_fact:
ansible_user: ansiblesu
- name: install compile library
shell: "sudo yum install -y gcc gcc-c++"
- name: change user (ansible)
set_fact:
ansible_user: ansible
- name: delete source dir & file
file:
path: "{{ item }}"
state: absent
with_items:
- "{{ path_nginx_source_dir }}"
- "{{ download_home }}/{{ file_nginx }}.tar.gz"
- "{{ download_home }}/{{ file_openssl }}.tar.gz"
- "{{ download_home }}/{{ file_pcre }}.tar.gz"
- "{{ download_home }}/{{ file_zlib }}.tar.gz"
- name: Directory exists (nginx)
file:
path: "{{ item }}"
state: directory
with_items:
- "{{ download_home }}"
- "{{ path_nginx_source_dir }}"
- "{{ path_nginx_home }}/conf/conf.d"
- "{{ path_nginx_home }}/ssl"
- "{{ path_nginx_log }}"
- name: Downloading NGINX sources
get_url:
url: "{{ item.url }}"
dest: "{{ item.dest }}"
with_items:
- { id: 1, url: "{{ file_server_url }}/{{ file_nginx }}.tar.gz", dest: "{{ download_home }}/{{ file_nginx }}.tar.gz" }
- { id: 2, url: "{{ file_server_url }}/{{ file_openssl }}.tar.gz", dest: "{{ download_home }}/{{ file_openssl }}.tar.gz" }
- { id: 3, url: "{{ file_server_url }}/{{ file_pcre }}.tar.gz", dest: "{{ download_home }}/{{ file_pcre }}.tar.gz" }
- { id: 4, url: "{{ file_server_url }}/{{ file_zlib }}.tar.gz", dest: "{{ download_home }}/{{ file_zlib }}.tar.gz" }
register: nginx_source
- name: Unpacking NGINX
unarchive:
src: "{{ nginx_source.results.0.dest }}"
dest: "{{ path_nginx_source_dir }}"
extra_opts: [--strip-components=1]
remote_src: "yes"
when: nginx_source is changed
register: nginx_source_unpack
- name: Extract archive (openssl)
unarchive:
src: "{{ nginx_source.results.1.dest }}"
dest: "{{ path_nginx_source_dir }}/"
remote_src: "yes"
- name: Extract archive (pcre)
unarchive:
src: "{{ nginx_source.results.2.dest }}"
dest: "{{ path_nginx_source_dir }}/"
remote_src: "yes"
- name: Extract archive (zlib)
unarchive:
src: "{{ nginx_source.results.3.dest }}"
dest: "{{ path_nginx_source_dir }}/"
remote_src: "yes"
- name: Configuring NGINX source with custom modules
command: "./configure --with-pcre=./{{ file_pcre }} --with-zlib=./{{ file_zlib }} --with-openssl={{ file_openssl }} --with-http_ssl_module --with-http_realip_module --prefix={{ path_nginx_home }} --user=ansible --group=ansible"
args:
chdir: "{{ path_nginx_source_dir }}"
when: nginx_source_unpack is changed
register: nginx_configure
- name: Installing NGINX
shell: make && make install
args:
chdir: "{{ path_nginx_source_dir }}"
when: nginx_configure is changed
- name: change user (ansiblesu)
set_fact:
ansible_user: ansiblesu
- name: CAP_NET_BIND_SERVICE
command: "sudo /usr/sbin/setcap CAP_NET_BIND_SERVICE=+eip {{ path_nginx_home }}/sbin/nginx"
- name: change user (ansible)
set_fact:
ansible_user: ansible
- name: delete source dir & file
file:
path: "{{ item }}"
state: absent
with_items:
- "{{ path_nginx_source_dir }}"
- "{{ download_home }}/{{ file_nginx }}.tar.gz"
- "{{ download_home }}/{{ file_openssl }}.tar.gz"
- "{{ download_home }}/{{ file_pcre }}.tar.gz"
- "{{ download_home }}/{{ file_zlib }}.tar.gz"
- "{{ path_nginx_home }}/logs"
- name: symbolic link (log)
file:
src: "{{ path_nginx_log }}"
dest: "{{ path_nginx_home }}/logs"
state: link
참고 사이트
- https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable
- https://docs.ansible.com/ansible-tower/latest/html/towerapi/api_ref.html
- https://docs.ansible.com/ansible/2.5/modules/list_of_all_modules.html#all-modules
- https://www.edureka.co/blog/chef-vs-puppet-vs-ansible-vs-saltstack/
끝.