..

a101) ansible 기초반

작년 상반기에 연달아 참여했던 스터디가 힘들어서 잠시.. 쉬었다가, 새해맞이로 다시 스터디에 참여하게 되었다. 이번 스터디는 ‘앤서블로 시작하는 인프라 자동화’ 책을 바탕으로 진행된다.

ansible소개

앤서블은 로컬/원격 시스템에 원하는 상태로 선언하여 그 시스템이 해당 상태를 유지하게해주는 자동화 도구이다. 테라폼과 다른 점은 상태를 계속해서 관리해주지 않기때문에 수명주기 관리가 안된다. 그렇기때문에 만약 삭제 되어야하는 리소스라면 삭제 절차를 넣어서 직접 수명주기를 관리해줘야한다. 앤서블은 파이썬으로 구성되어있어, 명령 실행 시 파이썬이 동작된다. 그렇기에 명령 수행을 위해 모든 노드(control+managed)에는 파이썬이 설치 되어있어야 한다. 앤서블 설치는 control node에만 설치하면 되고, managed node에 접근을 ssh protocol을 사용하므로 에이전트를 설치 할 필요가 없다는 장점이 있다. (설치🔗)

ansible 아키텍처 🔗

1-0

주요 컴포넌트들

  • controle node: 앤서블이 설치, 제어하는 노드
  • managed node: 호스트라고도 하며, 관리하려는 노드.
  • inventory: managed node들을 ip 나 호스트명 등으로 작성한 파일. 나열된 리스트를 그룹화하여 태스크나 변수 할당 등이 가능하다.
  • module: 호스트에 작업을 실행 할 수 있는 코드 단위. 명령줄이나 플레이북으로 실행 가능하다.

동작 방식

동작방식은, 앤서블이 설치된 control node에서 앤서블 명령을 실행하게 되면, 인벤토리에 작성되어있는 managed node 목록을 보고 해당 노드를 제어 하게된다. control node에서 앤서블 명령 실행 0(사전작업). 노드간의 연결은 ssh protocol을 사용하기 때문에, ssh 키 인증을 먼저 진행한다.

  1. control node에서 managed node(호스트)에 0에서 인증해둔 키기반으로 접근하여, 각 명령은 모듈로 호스트에 전달된다.
  2. 호스트에서는 요청한 상태가 되도록 모듈이 실행된다.
  3. managed node로 결과 값이 전달되고, 호스트에서 완료한 모듈 스크립트는 제거된다.

ansible 인증 구성

앤서블은 ssh 프로토콜을 사용하기 때문에 인증을 위해 로컬사용자에게 개인 ssh키가 있거나 managed node에서 원격 사용자임을 인증 가능한 키가 구성되어 있어야 자동로그인 된다. ssh 키기반인증을 위해 ssh-keygen으로 키 생성 후 ssh-copy-id로 공개키를 각 호스트에 복사했다.

# control node에서 키 생성
ssh-keygen -t rsa -N "" -f /root/.ssh/id_rsa
# managed node들에게 공개키를 복사
ssh-copy-id root@MANAGED_NODE

inventory 작성

자동화 할 대상, managed node를 선정하여 인벤토리에 설정해둬야합니다. 호스트 목록을 ip 혹은 호스트명으로 적어두는 인벤토리 파일인데, 이를 편리하게 관리하기 위해 그룹 설정을 할 수가 있다. 작성 방법은 대괄호[]를 사용하여 그룹을 설정하고 하위에 그룹 대상이 될 호스트를 넣어두는 방식이다. 예시로 아래와 같이 작성 할 수 있다.

[web]
10.100.0.1
10.100.0.2
10.100.0.3
# 10.100.0.[1:3] 연속된 숫자의 경우 [start:end]로 작성 가능

[was]
a.was.test.com
b.was.test.com
c.was.test.com
# [a:c].was.test.com

[db]
db1.test.com
db2.test.com
# db[1:2].test.com

[all:childeren]
# [그룹명:children]의 경우 중첩 그룹을 의미하며, 하위의 그룹들의 모든 호스트를 포함
web
was
db

실습을 위한 인벤토리 파일 작성

실습 환경은 위의 이미지와 같이 하나의 control node와 3개의 managed node로 구성 하였다. web,was,db로 나눈 것은 그냥 단순히 구분을 위해 나누었고 실제 이에 맞는 서비스를 올리진 않았다.

# 호스트 설정
root@server:~/my-ansible# cat /etc/hosts | grep tnode*
10.10.1.11 tnode1
10.10.1.12 tnode2
10.10.1.13 tnode3

# 인벤토리 파일 내부
root@server:~/my-ansible# cat inventory 
[web]
tnode1
[was]
tnode2
[db]
tnode3

[all:children]
web
was
db

# 작성완료 후에는 $ansible-inventory -i ./inventory --list 혹은 --graph 옵션 등으로 인벤토리 검증을 받을 수 있다.
root@server:~/my-ansible# ansible-inventory -i ./inventory --graph
@all:
  |--@ungrouped:
  |--@web:
  |  |--tnode1
  |--@was:
  |  |--tnode2
  |--@db:
  |  |--tnode3

ansible.cfg 🔗

앤서블 실행시 사용되는 환결 설정 파일 작성이 필요하다. 설정 파일의 위치는 ‘환경변수 > 현 디렉토리의 ansible.cfg > home 디렉토리의 config파일 > /etc/ansible/ansible.cfg’ 순으로 우선순위가 높게 적용된다. 현 프로젝트 디렉토리에 ansible.cfg를 만들어서 아래와 같이 작성했다.

root@server:~/my-ansible# cat ansible.cfg 
[defaults]
inventory = ./inventory

# 인벤토리 검증시 위치를 추가 하지 않아도 설정파일에 있는 인벤토리 위치의 파일을 참조한다.
root@server:~/my-ansible# ansible-inventory --graph -v
Using /root/my-ansible/ansible.cfg as config file  # 어느 config file을 참조하는지 알 수 있다.
@all:
  |--@ungrouped:
  |--@web:
  |  |--tnode1
  |--@was:
  |  |--tnode2
  |--@db:
  |  |--tnode3
root@server:~/my-ansible# ansible-config view
[defaults]
inventory = ./inventory

파일의 구성은 [defaults]와 [privilege_escalation] 두 개의 섹션으로 나뉜다. [default]의 remote_user로 먼저 접근하여 작업 중 권한 필요시, 권한을 에스컬레이션하여 [privilege_escalation]의 become_user의 권한으로 작업하게 된다.

root@server:~/my-ansible# ansible-config view
[defaults]
inventory = ./inventory #인벤토리 경로
remote_user = root #앤서블이 managed node에 연결 시 사용하는 사용자 이름 지정
ask_pass = false #ssh 암호를 묻는 메세지 표시 여부

[privilege_escalation]
become = true #권한 에스컬레이션 활성화 여부
become_method = sudo #사용자 전환 방식 
become_user = root #전환할 사용자 
become_ask_pass = false #전환 시 암호를 묻는 메세시 표시 여부

앤서블 명령줄 실행(ad hoc commands) 🔗

앤서블 명령어로도 실행 가능하다. 명령어 실행시, 위에서 작성한 인벤토리에서 호스트를 찾아가 실행되는 모듈을 수행한다. $ansible PATTERN -m MODULE -a OPTION 으로 명령어를 실행 할 수 있다. 예시로 Ping을 한 번 날려보았다. ansible에서의 ping은 우리가 흔히 아는 icmp를 사용한 것이 아니라, ansible에서 python으로 만든 테스트 모듈이다.

root@server:~/my-ansible# ansible -m ping web 
tnode1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3" #icmp프로토콜이 아니라 python3이 동작했다.
    },
    "changed": false,
    "ping": "pong"
}

# managed node로 공유된 공개키는 root이기 때문에 ubuntu user로 접근시 --ask-pass로 키 인증을 받아야한다.
root@server:~/my-ansible# ansible -m ping web -u ubuntu
tnode1 | UNREACHABLE! => {
    "changed": false,
    "msg": "Failed to connect to the host via ssh: ubuntu@tnode1: Permission denied (publickey,password).",
    "unreachable": true
}
root@server:~/my-ansible# ansible -m ping web -u ubuntu --ask-pass
SSH password: 
tnode1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3
    },
    "changed": false,
    "ping": "pong"
}

playbook 🔗

간단한 테스트는 명령줄로 할 수 있지만, 재사용 가능한 파일로 플레이북을 만들어서 실행하는 것이 앤서블을 사용하는데에 있는 이점이다. 플레이북은 yaml 형식으로 작성되어 위에서부터 task가 수행된다. 예시로, 아래의 플레이북의 경우 ‘db에서 debug 메세지 출력 > 모든 호스트에서 sshd 재시작’ 순으로 실행된다.

$ansible-playbook NAME.yaml 형태로 플레이북 실행을 할 수 있다. 여기에 –syntax-check 옵션을 붙여 실행 전 문법을 점검한다. 1-1

# 몇가지 옵션들
# --step: 스텝마다 진행 여부 확인
root@server:~/my-ansible# ansible-playbook test.yaml --step
PLAY [db] ************************************************************************************************************
Perform task: TASK: Gathering Facts (N)o/(y)es/(c)ontinue: yes
Perform task: TASK: Gathering Facts (N)o/(y)es/(c)ontinue: ***********************************************************
TASK [Gathering Facts] ***********************************************************************************************
ok: [tnode3]
...

# --start-at-task: 시작할 태스크 지정
# PLAY[db]는 facts조차 수집하지 않고 넘어가는 것을 확인
root@server:~/my-ansible# ansible-playbook test.yaml --start-at-task="Restart sshd service"
PLAY [db] ************************************************************************************************************
PLAY [all] ***********************************************************************************************************
TASK [Gathering Facts] ***********************************************************************************************
ok: [tnode3]
ok: [tnode2]
ok: [tnode1]
TASK [Restart sshd service] ******************************************************************************************
changed: [tnode2]
changed: [tnode3]
changed: [tnode1]
PLAY RECAP ***********************************************************************************************************
tnode1                     : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
tnode2                     : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
tnode3                     : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

# --limit: 특정 호스트만 실행
# 'hosts: db'의 경우 해당사항이 없어서 스킵됨
root@server:~/my-ansible# ansible-playbook test.yaml --limit web
PLAY [db] ************************************************************************************************************
skipping: no hosts matched
PLAY [all] ***********************************************************************************************************
TASK [Gathering Facts] ***********************************************************************************************
ok: [tnode1]
TASK [Restart sshd service] ******************************************************************************************
changed: [tnode1]
PLAY RECAP ***********************************************************************************************************
tnode1                     : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

이 외에도 태스크에 태그를 붙여서 해당 태스크부터 혹은 해당 태그가 붙은 태스크는 스킵되도록 설정 할 수 있다. 예시로 hosts: db 하위에 tag를 붙이고 이를 스킵하도록 설정하였는데, 이 경우에는 db에 대해 facts 는 수집하지만 태그가 달린 태스크에 대해서는 실행되지 않고 넘어간다. 1-2

위에서 말한 것처럼 앤서블은 위에서부터 하나씩 태스크가 수행된다. 여러 태스크를 다중 호스트에서 실행시 중간에 실패한 호스트는 태스크가 실행되지않고, 성공한 호스트에서만 실행된다. 예를 들어 host1,2 모두 task1,2,3이 수행되어야하는 플레이북이 실행되었는데, tast2에서 host1만 성공하고 host2가 실패하였다면, 그 다음 작업인 task3은 host1에만 실행된다. 이 때, 두 호스트가 연동되어서 함께 태스크가 돌아야한다면 하나의 호스트만에서 실패가 나더라도 멈추도록 ‘any_error_fetal: true’ 옵션을 쓸 수 있다. 🔗

변수

반복적이거나 값이 계속해서 변경되는 경우 변수를 사용 할 수도 있다. 앤서블에서 변수 종류는 ‘그룹 변수, 호스트 변수, 플레이 변수, 추가 변수’가 있으며 순서대로 적용되는 우선순위가 높아진다. 그룹 변수는 변수를 호스트 그룹에서 사용 할 수 있도록 인벤토리 파일에 작성한다. 하단에 [group_name:vars] 섹션을 만들어서 사용할 ‘변수명=값’을 선언하여 사용할 수 있다. 호스트 변수는 인벤토리 파일에서 호스트가 정의되는 부분에 변수를 넣어주는 방식이고, 플레이 변수는 플레이북의 hosts 하위로 넣어준다. 이 세가지 변수 종류를 함께 사용하는 예시로 아래 파일을 작성하였다. web의 경우는 그룹 변수, was의 경우 그룹 변수와 호스트 변수, db의 경우 그룹 변수와 호스트 변수 그리고 플레이 변수까지 모두 해당되지만, 우선순위가 높은 변수의 값으로 변수값이 들어가게 된다. 그래서 web의 경우 apache로 was의 경우 ansible1, db는 ansible2 로 user가 생성된 것을 확인 할 수있다. 1-3

마지막으로 제일 우선순위가 높은 추가 변수는 명령 줄에 함께 작성하는 추가 변수이다. 위의 파일을 다시 실행할 때, -e (–extra-vars) 옵션을 넣어줬다. 이 때 모든 자리에 ansible3이 적용되며 모든 호스트에 ansible3 user가 생성된 것을 확인 할 수 있다.

root@server:~/my-ansible# ansible-playbook -e user=ansible3 test.yaml 
...
root@server:~/my-ansible# ssh tnode1 ls /home/
ansible3
apache
ubuntu
root@server:~/my-ansible# ssh tnode2 ls /home/
ansible1
ansible3
ubuntu
root@server:~/my-ansible# ssh tnode3 ls /home/
ansible1
ansible2
ansible3
ubuntu

user 리스트를 보면 이전에 생성된 ansible,1,2, 모두 남아 있는 것을 볼 수 있다. 위에서 말한 것처럼 앤서블은 테라폼처럼 수명 주기를 관리하지않기 때문에 삭제되지 않고 남아있다. 테라폼과 같이 점으로는 멱등성을 가지고 있다는 점인데, 위의 같은 명령어를 한번 더 날려도 user: ansible3을 생성하는게 아니라, ansible3 user의 존재를 확인하고 있다면 재생성을 하지않고 상태를 유지하는 것을 확인할 수 있다.

작업 변수

작업변수는 위의 변수와 다르게 테라폼의 output과 같이 앤서블 실행하여 나오는 결과값을 변수에 저장하는 것을 말한다. 아래 예시처럼 task수행 하위에 register: creste_user_result로 후, 디버그로 creste_user_result를 출력하여 결과를 알 수 있다. 이렇게 출력하면 호스트에 직접 들어가서 어떤 변경사항이 있는지 확인하지 않아도 된다. 1-4

리소스 정ㄹㅣing 💬

다음 테스트를 하기 전 만들었던 사용자들을 모두 삭제 정리하였다. 추가 변수를 사용하여 만든 ansible3까지 다시 추가 변수를 사용하여 삭제하였다. 1-5

1-6

ansible vault

앤서블은 코드기반으로 실행되다보니, 패스워드나 키 등 중요한 것들이 플레이북이나 변수에 그대로 작성될 수가 있다. 이렇게 되면 접근 권한이 있는 사용자 모두가 파일을 읽을 수 있으니, 보안을 위해 데이터 파일을 암호화하고 해독하는 ansible vault가 있다. 암호화/복호화 모두 파일을 생성한 소유자만 읽고 쓸 수 있게 설정된다.

암호화

ansible-vault create 명령 실행시 암호를 입력하고 들어가 암호화 할 내용을 작성한다. 만약 입력 할 암호가 적힌 기존 파일이 있다면, –vault-pass-file옵션을 사용하여 암호화 할 내용을 작성하는 단계로 바로 넘어 갈 수 있다.

$ansible-vault create 암호화_될_파일.yaml

$ansible-vault create –vault-pass-file 암호가_작성된_파일 암호화_될_파일.yaml

$ansible-vault encrypt 암호화_할_파일.yaml

root@server:~/my-ansible# ansible-vault create mysecret.yaml
New Vault password: 
Confirm New Vault password:

# 생성된 파일은 생성한 소유자만 읽고 쓸 수 있다.
root@server:~/my-ansible# ls -l vars/
-rw------- 1 root root 355 Jan 14 01:03 mysecret.yaml

복호화

$ansible-vault view 암호화_된_파일.yaml

$ansible-vault view –vault-pass-file 암호가_작성된_파일 암호화_된_파일.yaml

$ansible-vault decrypt 암호화_된_파일.yaml –output=복호화_될_파일.yaml

#ansible-vault view
root@server:~/my-ansible# cat passwd 
P@ssw0rd
root@server:~/my-ansible# ansible-vault view --vault-pass-file passwd mysecret.yaml 
user: test

#ansible-vault decrypt
root@server:~/my-ansible# ansible-vault decrypt mysecret.yaml --output=decrypt_mysecret.yaml
Vault password: 
Decryption successful
root@server:~/my-ansible# cat decrypt_mysecret.yaml 
user: test

암호 변경

$ansible-vault rekey 암호화_된_파일.yaml

$ansible-vault rekey –new-vault-password-file=새_암호_파일 암호화_된_파일.yaml

root@server:~/my-ansible# ansible-vault rekey mysecret.yaml 
Vault password: 
New Vault password: 
Confirm New Vault password: 
Rekey successful

암호화 파일 사용

암호화된 파일이 포함된 플레이북 실행시에는 vault-id @promp 옵션을 붙여서 사용해야 한다. 암호화되어있는 파일을 ansible이 열지를 못해 암호를 입력하는 프롬프트가 한번 뜬다. 만약 암호가 적힌 파일을 사용한다면 –vault-password-file=암호파일 옵션을 붙여서 사용 가능하다(이 때 암호 적힌 파일의 권한 관리를 잘해줘야한다.. 600). 혹은 ansible.cfg에서 ‘vault_password_file=암호파일위치’ 를 넣어서도 참조가 가능하다.

$ansible-playbook vault-id @prompt test.yaml

1-7

ansible을 여러 환경에서 사용하다보면, ansible-vault도 구분이 필요해질 것이다. 그래서 있는 것이 vault-id로 여러 비밀번호를 관리 할 수가 있다. 암호화 할 때부터 –vault-id=dev@prompt(혹은 암호나 암호파일위치)를 사용 할 수 있다.

$ansible-playbook –vault-id=username@prompt

1-8

ansible은 암호화된 파일의 콘텐츠의 필요 유무를 알 수 없기 때문에, 플레이북에서 참조되는 암호화 파일을 모두를 복호화 한다. 그렇기 때문에 만약 아주 일부의 암호화된 변수 사용이 필요하다면 encrypt_string으로 일부만 암호화하여 사용 가능하다.

$ansible-vault encrypt_string '' --name ''

facts

앤서블이 기본적으로 managed node에서 수집하는 호스트 정보이다. 예를 들어 호스트이름, 커널버전, 네트워크 인터페이스 이름 등 많은 값이 수집된다. 전체 수집정보는 아래 플레이북을 돌려서 확인 가능하다. 1-9

위에서 수집되는 facts들을 변수로도 사용이 가능하다. 1-10

facts 비활성화

원할시에는 이렇게 facts가 수집되는 기능을 hosts 하위에 gather_facts: no 옵션을 넣어 비활성화 할 수도 있다. 일부 facts만 수집을 원한다면 ansible.buildin.setup모듈을 사용하여 gather_subset으로 수집할 facts를 지정한다. 🔗 gather_facts: no로 설정하였기 때문에, fats로 수집되는 정보를 가져오려하면 오류가 발생한다. 1-11

gather_subset으로 network만 수집하고 나머지는 수집하지 않는다고 설정 후, 정말 그런지 확인해보았다. 결과를 보면 network 관련 정보는 잘 가져오는데, bios같은 외의 정보는 수집하지 못해 정보가 없는 것으로 확인된다.

1-12

이 외에도 엄청나게 많은 정보들이 있어서 독스의 바다를 헤매는 일주차였다 🐟