개요
서비스를 구축하고 사용하기 위해 가장 먼저 안정적인 시스템을 구축한다.
그리고 사용자 계정을 만들어 해당 사용자 계정으로 접속할 수 있도록 SSH 키를 생성한다.
이때 여러 시스템의 시간대를 설정하고 동기화하는 작업도 진행한다.
이런 상황에서 효율적으로 구축할 수 있는 앤서블 활용법을 이번 실습을 통해 진행해보도록 하겠다.
구성도
1. 사용자 계정 생성하기
시스템 구축시 가장 먼저 하는 일은 사용자 계정을 만드는 것이다.
사용자 계정은 목적에 따라 시스템에 접근하기 위해 생성할 수도 있고, 서비스를 설치하거나 구축하기 위해 생성할 수도 있다.
사용자 계정 생성 업무만 할 경우에는 굳이 앤서블을 사용하지 않고 셸 스크립트만으로도 충분히 가능하다.
그러나 앤서블 플레이북을 통해 사용자 계정을 생성할 줄 알면 시스템이나 서비스를 구축할 때 해당 플레이북의 태스크를 재활용할 수 있다.
How ?
모든 개발에는 분석과 설계 단계가 필요하다.
앤서블 역시 플레이북을 개발하는 것이므로 사전 분석과 플레이북 설계가 이루어져야 한다.
- 사용자 계정과 패스워드는
Vault
를 이용해 암호화 처리 - 사용자 계정 생성은
ansible.builtin.user
모듈을 사용
플레이북 개발
실습 진행을 위해 01_사용자계정생성
이라는 디렉터리를 하나 생성해주자.
mkdir -p /ansible/01_사용자계정생성
그리고 inventory 파일을 생성하자.
호스트 그룹을 따로 나누지 않고 다음과 같이 호스트명으로만 구성된 inventory를 작성하도록 한다.
ubuntu1
ubuntu2
ansible.cfg 파일은 아래와 같이 작성한다.
[defaults]
inventory = ./inventory
remote_user = root
ask_pass = false
roles_path = ./roles
collection_paths = ./collection
interpreter_python=auto
[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false%
이번에는 ansible-vault
로 사용자 계정 정보가 정의된 변수 파일을 생성하자.
이때 Vault Password를 입력하라는 프롬프트가 나오면 사용하고자 하는 패스워드를 입력한다.
$ ansible-vault create vars/secret.yaml
패스워드 입력 시 에디터 창으로 전환된다.
여기에 user_info
변수에 userid
와 userpw
가 같이 있는 사전형 변수를 정의하도록 하자.
---
user_info:
- userid: "ansible_auto"
userpw: "qwer123"
- userid: "ansible_auto2"
userpw: "qwer123"
Vault로 사용자 계정을 정의했다면, 이번에는 사용자 계정을 생성하는 플레이북을 작성해보자.
사용자 계정은 모든 호스트에 동일하게 생성하며 vault로 작성된 변수 파일을 읽어 사용한다.
사용자 계정은 ansible.builtin.user
모듈과 loop 문을 사용하여 생성한다.
---
- hosts: all
vars_files:
- vars/secret.yaml
tasks:
- name: "Create {{ item }}"
ansible.builtin.user:
name: "{{ item.userid }}"
password: "{{ item.userpw }}"
state: present
loop: "{{ user_info }}"
플레이북 작성이 끝나면 플레이북 문법을 체크하고 실행하여 인벤토리에 등록된 전 노드에 secret.yaml
파일에 등록한 사용자 계정이 생성되는지 테스트해보자.
먼저 문법을 체크하자.
$ ansible-playbook --syntax-check --ask-vault-pass create-user.yaml
Vault password:
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
playbook: create-user.yaml
문법 체크가 완료되었다면 create-user.yaml
플레이북을 실행해보자.
$ ansible-playbook -i ./inventory --ask-vault-pass create-user.yaml
ok: [ubuntu1]
TASK [Create {{ item }}] ***************************************************************************************************************************************************************************************************************************************************
ok: [ubuntu1] => (item={'userid': 'ansible_auto', 'userpw': 'qwer123'})
ok: [ubuntu1] => (item={'userid': 'ansible_auto2', 'userpw': 'qwer123'})
[WARNING]: The input password appears not to have been hashed. The 'password' argument must be encrypted for this module to work properly.
PLAY RECAP *****************************************************************************************************************************************************************************************************************************************************************
ubuntu1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
정상적으로 플레이북이 실행되었다.
이후 관리 노드(ubuntu1
)에 들어가 사용자 계정을 조회해보면 해당 계정이 생성된 것을 볼 수 있다.
# ubuntu1
$ tail -n 2 /etc/passwd
ansible_auto:x:1004:1004::/home/ansible_auto:/bin/sh
ansible_auto2:x:1005:1005::/home/ansible_auto2:/bin/sh
2. SSH 키 생성 및 복사하기
시스템을 구축하거나 애플리케이션을 설치하는 경우,
해당 애플리케이션을 사용하는 서버들간에 SSH 접속을 할 때는 패스워드 대신 SSH 키를 주로 사용한다.
앤서블 역시 대상 노드에 접속하여 모듈을 실행할 경우 SSH 접근을 하는데 이때 SSH 키를 사용하여 접근하면 쉽게 작업할 수 있다.
이렇게 생성한 SSH 공개 키를 여러 서버에 복사할 때도 앤서블을 활용하면 매우 편리하다.
How ?
먼저 앤서블 콘텐츠 컬렉션의 어떤 모듈을 사용하면 SSH 키를 생성하고 복사할 수 있는지를 찾아야 한다.
분석을 통해 플레이북 설계와 개발에 필요한 모듈을 찾아보자.
- 사용자 아이디는 외부 변수로 받는다.
- ansible-server(제어 노드)에서 ansible 계정을 생성하고 SSH 키를 생성한다.
- ansible-server(제어 노드)에 생성된 SSH 공개 키를 각 관리 node에 복사한다.
- 계정을 생성할 때는
ansible.builtin.user
모듈을, SSH 공개 키를 복사할 때는ansible.posix.authorized_key
모듈을 사용한다.
플레이북 개발
실습 진행을 위해 02_SSH키생성및복사
이라는 디렉터리를 하나 생성해주자.
mkdir -p /ansible/02_SSH키생성및복사
그리고 inventory 파일을 생성하자.
호스트 그룹을 따로 나누지 않고 다음과 같이 호스트명으로만 구성된 inventory를 작성하도록 한다.
ubuntu1
ubuntu2
ansible.cfg 파일은 아래와 같이 작성한다.
[defaults]
inventory = ./inventory
remote_user = root
ask_pass = false
roles_path = ./roles
collection_paths = ./collection
interpreter_python=auto
[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false%
그리고 외부 변수를 주입하기 위한 secret.yaml
파일을 vars 디렉토리 하위에 생성해주자.
$ vi vars/secret.yaml
userid: ansible
그리고 ansible-vault encrypt
명령어로 암호화해주자.
ansible-vault encrypt vars/secret.yaml
이번에는 SSH 키를 생성하고 복사하는 플레이북을 작성해볼거다.
create_sshkey.yaml
이라는 파일을 만들고 다음과 같은 내용으로 플레이북을 생성한다.
여기서는 실행될 호스트별로 태스크가 작성되었다.
localhost인 ansible-server(제어 노드)
에서 생성된 SSH 공개 키는 ansible.posix.authorized_keys
모듈을 이용하여 인벤토리의 관리 노드 각 서버로 복사된다.
이때 키를 등록하기 위해 lookup
함수가 등록되었다.
---
- hosts: localhost
vars_files:
- vars/secret.yaml
tasks:
- name : Create ssh key
ansible.builtin.user:
name: "{{ userid }}"
generate_ssh_key: true
ssh_key_bits: 2048
ssh_key_file: /home/{{ userid }}/.ssh/id_rsa
- hosts: ubuntu1
vars_files:
- vars/secret.yaml
tasks:
- name: Copy SSH Pub key
ansible.posix.authorized_key:
user: "{{ userid }}"
state: present
key: "{{ lookup('file', '/home/{{ userid }}/.ssh/id_rsa.pub') }}"
첫 번쨰 태스크 설명
generate_ssh_key: true
: Ansible의generate_ssh_key
모듈을 사용하여 SSH 키 쌍을 생성함.ssh_key_bits: 4096
: 생성되는 SSH 키의 길이를 4096비트로 지정. 이는 강력한 암호화 보안을 제공함.ssh_key_file: /home/{{ userid }}/.ssh/ansible_id_rsa
: 생성된 SSH 키 파일이 저장될 경로를 지정{{ userid }}
는 Ansible 변수로, 실제로는 사용자 ID로 치환됨. (userid 라는 변수를 주입해줘야 함)- 여기서 SSH 키 파일은 사용자의 홈 디렉터리에 위치하게 된다.
두 번째 태스크 설명
ansible.posix.authorized_keys
:authorized_keys
모듈을 사용하여 사용자의 authorized_keys 파일에 SSH 공개 키를 추가. 이 모듈은 SSH 키 인증을 통해 사용자의 액세스를 제어하는 데 사용된다.user: "{{ userid }}"
: SSH 키가 추가될 대상 사용자 계정을 지정.{{ userid }}
는 변수로 사용자의 ID로 치환됨 (userid 라는 변수를 주입해줘야 함).
state: present
: 지정된 키가 존재하도록 보장.- 즉, 키가 존재하지 않으면 추가하고, 이미 존재하면 변경하지 않음.
key: "{{ lookup('file', '/home/{{ userid }}/.ssh/ansible_id_rsa.pub') }}"
:lookup('file', ...)
함수를 사용하여 로컬 파일 시스템에서 SSH 공개 키 파일을 읽는다.- 이 파일의 경로는
/home/{{ userid }}/.ssh/ansible_id_rsa.pub
로 지정되어 있으며, - 이는 첫 번째 Play에서 생성된 SSH 공개 키 파일이다.
- 이 파일의 경로는
플레이북을 실행해보면 다음과 같다.
$ ansible-playbook -i ./inventory --ask-vault-pass create_sshkey.yaml
TASK [Gathering Facts] *****************************************************************************************************************************************************************************************************************************************************
[WARNING]: Platform linux on host ubuntu1 is using the discovered Python interpreter at /usr/bin/python3.10, but future installation of another Python interpreter could change the meaning of that path. See https://docs.ansible.com/ansible-
core/2.17/reference_appendices/interpreter_discovery.html for more information.
ok: [ubuntu1]
TASK [Copy SSH Pub key] ****************************************************************************************************************************************************************************************************************************************************
ok: [ubuntu1]
PLAY RECAP *****************************************************************************************************************************************************************************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ubuntu1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
자 그러면, 제대로 실행되었는지 검증해보자.
우선 ansible-server(제어 노드)
에서 ansible
계정으로 전환 후 ansible 홈 디렉토리의 .ssh
디렉토리에 SSH 공개 키와 개인 키가 생성되었는지 확인하자.
$ su - ansible
$ ls -lh ~/.ssh
-rw------- 1 ansible ansible 1.8K 9월 5일 18:16 id_rsa
-rw-r--r-- 1 ansible ansible 407 9월 5일 18:16 id_rsa.pub
공개 키와 개인 키가 정상적으로 생성되었다면 각 제어 노드(ubuntu1
)에 접속하여 /home/ansible/.ssh
디렉토리에 authorized_keys
파일이 생성되었는지 확인하자.
# ubuntu1
root@ubuntu1:~# ls -lh ~/.ssh
total 4.0K
-rw------- 1 root root 1.2K Sep 5 09:19 authorized_keys
정상적으로 파일이 생성되어있다.
3. 호스트명 설정하기
초기에 시스템을 구성하다 보면 여러 서버의 호스트명을 설정하고 /etc/hosts
파일에 호스트명을 추가하는 작업들을 종종 하게 된다.
호스트명을 추가하는 것은 각 서버에서 수동으로 해줘도 될 만큼 쉬운 작업이지만
서버 대수가 늘어난다면 정신/육체적인 피로가 폭발하게 된다.
이때 앤서블을 이용해 호스트명을 설정하고 /etc/hosts
파일에 해당 호스트 정보까지 추가하는 작업을 한다면 매우 효율적이다.
How ?
호스트명을 설정할 때는 단순히 호스트명만 설정할 때가 있고, FQDN(Fully Qualified Domain Name) 형식의 호스트명을 설정할 때가 있다.
시스템을 구성할 때는 단순 이름보다는 FQDN 형식의 이름을 호스트명으로 자주 사용한다.
앤서벌의 어떤 모듈을 사용하여 호스트명을 확인하고 설정할 수 있는지 알아보자.
- 앤서블로 접근하기 위한 대상 서버들은 이미 제어 노드의 인벤토리에 등록되어 있다.
- 호스트명을 설정하기 위해
ansible.bultin.hostname
모듈을 사용한다. /etc/hosts
에 노드 정보들을 등록하기 위해 필요한 정보들을 변수로 정의한다.- 호스트명을 hosts 파일에 추가할 때는
ansible.bultin.lineinfile
모듈을 사용한다.
플레이북 개발
실습 진행을 위해 03_hostname
디렉토리를 생성해주자.
mkdir ./03_hostname
그리고 inventory 파일을 생성하자.
호스트 그룹을 따로 나누지 않고 다음과 같이 호스트명으로만 구성된 inventory를 작성하도록 한다.
[nodes]
ubuntu1
ubuntu2
ansible.cfg 파일은 아래와 같이 작성한다.
[defaults]
inventory = ./inventory
remote_user = root
ask_pass = false
roles_path = ./roles
collection_paths = ./collection
interpreter_python=auto
[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false%
hosts 파일에 추가할 호스트 정보들을 vars_hosts_info.yaml
파일로 생성하고 다음 내용을 변수로 설정한다.
이때 변수는 사전형 변수로 정의하여 반복문을 사용할 수 있도록 한다.
nodes:
- hostname: ubuntu1
fqdn: ubuntu1-ubuntu.exp.com
net_ip: 192.168.219.192
- hostname: ubuntu2
fqdn: ubuntu2-ubuntu.exp.com
net_ip: 192.168.219.193
마지막으로 메인 플레이북을 개발한다.
이번엔 변수를 외부 파일로부터 읽어와 사용한다.
vars_files
라는 키워드를 사용하여 변수가 정의된 파일명을 입력하면 해당 파일로부터 변수를 가져와 사용할 수 있다.
---
- hosts: nodes
vars_files: vars_hosts_info.yaml
tasks:
- name: Set hostname from inventory
ansible.builtin.hostname:
name: "{{ inventory_hostname }}"
- name: Add host ip to hosts
ansible.builtin.lineinfile:
path: /etc/hosts
line: "{{ item.net_ip }} {{ item.hostname }} {{ item.fqdn }}"
regexp: "^{{ item.net_ip }}"
loop: "{{ nodes }}"
플레이북 작성이 끝났다면 문법 체크를 하고 플레이북을 실행해보자.
$ ansible-playbook --systax-check set_hostname.yaml
$ ansible-playbook -i ./inventory set_hostname.yaml
TASK [Set hostname from inventory] *****************************************************************************************************************************************************************************************************************************************
ok: [ubuntu2]
ok: [ubuntu1]
TASK [Add host ip to hosts] ************************************************************************************************************************************************************************************************************************************************
changed: [ubuntu1] => (item={'hostname': 'ubuntu1', 'fqdn': 'ubuntu1-ubuntu.exp.com', 'net_ip': '192.168.219.192'})
changed: [ubuntu2] => (item={'hostname': 'ubuntu1', 'fqdn': 'ubuntu1-ubuntu.exp.com', 'net_ip': '192.168.219.192'})
changed: [ubuntu2] => (item={'hostname': 'ubuntu2', 'fqdn': 'ubuntu2-ubuntu.exp.com', 'net_ip': '192.168.219.193'})
changed: [ubuntu1] => (item={'hostname': 'ubuntu2', 'fqdn': 'ubuntu2-ubuntu.exp.com', 'net_ip': '192.168.219.193'})
PLAY RECAP *****************************************************************************************************************************************************************************************************************************************************************
ubuntu1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ubuntu2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
4. Facts를 이용한 시스템 모니터링
앤서블은 팩트라는 유용한 기능을 제공한다.
팩트는 관리 노드에서 시스템과 관련된 정보들을 자동으로 찾아 변수로 제공하고 있다.
이를 이용하면 실행 중인 관리 노드의 인프라 정보를 파악하거나 이를 로그로 저장할 수 있다.
이렇게 저장된 로그는 연동된 모니터링 도구에서 활용 가능하다.
How ?
팩트를 이용하여 시스템 정보를 모니터링하려면 팩트에서 어떤 정보를 제공하고 어떤 정보를 수집할 것인지 먼저 확인해야 한다.
- 팩트를 이용하여 다음과 같은 정보를 추출한다.
- 호스트 이름
- 커널 버전
- 네트워크 인터페이스 이름
- 네트워크 인터페이스 IP 주소
- 운영체제 버전
- CPU 개수
- 사용 가능 메모리
- 스토리지 장치의 크기 및 여유 공간
- 추출한 내용은
ansible.builtin.shell
모듈을 이용하여/var/log/daily_check
디렉토리에 저장한다.
플레이북 개발
실습 진행을 위해 04_system_info
디렉토리를 생성해주자.
mkdir ./04_system_info
그리고 inventory 파일을 생성하자.
호스트 그룹을 따로 나누지 않고 다음과 같이 호스트명으로만 구성된 inventory를 작성하도록 한다.
[nodes]
ubuntu1
ubuntu2
ansible.cfg 파일은 아래와 같이 작성한다.
[defaults]
inventory = ./inventory
remote_user = root
ask_pass = false
roles_path = ./roles
collection_paths = ./collection
interpreter_python=auto
[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false%
그리고 system_info.yaml
파일을 다음과 같이 작성해주자.
ansible.builtin.debug
모듈을 이용하여 ansible_facts
에서 수집한 시스템 변수 중 추출하고자 하는 변수를 먼저 msg
를 통해 출력하고,
출력한 결과를 register
키워드를 사용하여 result
변수에 저장한다.
그리고 이렇게 저장된 내용을 ansible.builtin.shell
모듈에서 loop
키워드를 사용하여 하나씩 로그 파일에 저장할 것이다.
---
- hosts: nodes
vars:
log_directory: /var/log/daily_check
tasks:
- name: Print system info
ansible.builtin.debug:
msg:
- "############ Start ############"
- "Date: {{ ansible_facts.date_time.date }} {{ ansible_facts.date_time.time }} "
- "HostName: {{ ansible_facts.hostname }}"
- "OS: {{ ansible_facts.distribution }}"
- "OS Version: {{ ansible_facts.distribution_version }}"
- "OS Kernel: {{ ansible_facts.kernel }}"
- "CPU Cores: {{ ansible_facts.processor_vcpus}}"
- "Memory: {{ ansible_facts.memory_mb.real }}"
- "Interfaces: {{ ansible_facts.interfaces }}"
- "IPv4: {{ ansible_facts.all_ipv4_addresses }}"
- "Devices: {{ ansible_facts.mounts }}"
- "############ Start ############"
register: result
- name: Create log directory
ansible.builtin.file:
path: "{{ log_directory }}"
state: directory
- name: Print logs to log file
ansible.builtin.shell: |
echo "{{ item }}" >> "{{ log_directory }}/system_info.logs"
loop: "{{ result.msg }}"
문법을 체크하고 플레이북을 실행해보자.
$ ansible-playbook --systax-check system_info.yaml
$ ansible-playbook -i ./inventory system_info.yaml
TASK [Create log directory] ************************************************************************************************************************************************************************************************************************************************
changed: [ubuntu2]
changed: [ubuntu1]
TASK [Print logs to log file] **********************************************************************************************************************************************************************************************************************************************
changed: [ubuntu2] => (item=############ Start ############)
changed: [ubuntu1] => (item=############ Start ############)
changed: [ubuntu1] => (item=Date: 2024-09-10 10:09:47 )
changed: [ubuntu2] => (item=Date: 2024-09-10 10:09:48 )
changed: [ubuntu1] => (item=HostName: ubuntu1)
changed: [ubuntu2] => (item=HostName: ubuntu2)
changed: [ubuntu1] => (item=OS: Ubuntu)
changed: [ubuntu2] => (item=OS: Ubuntu)
changed: [ubuntu1] => (item=OS Version: 22.04)
changed: [ubuntu2] => (item=OS Version: 22.04)
changed: [ubuntu1] => (item=OS Kernel: 5.15.0-119-generic)
changed: [ubuntu2] => (item=OS Kernel: 5.15.0-119-generic)
changed: [ubuntu1] => (item=CPU Cores: 1)
changed: [ubuntu2] => (item=CPU Cores: 1)
changed: [ubuntu1] => (item=Memory: {'total': 3912, 'used': 511, 'free': 3401})
changed: [ubuntu2] => (item=Memory: {'total': 3912, 'used': 541, 'free': 3371})
changed: [ubuntu1] => (item=Interfaces: ['ens18', 'lo'])
changed: [ubuntu2] => (item=Interfaces: ['lo', 'ens18'])
changed: [ubuntu1] => (item=IPv4: ['192.168.219.192'])
changed: [ubuntu2] => (item=IPv4: ['192.168.219.193'])
changed: [ubuntu1] => (item=Devices: [{'mount': '/', 'device': '/dev/mapper/ubuntu--vg-ubuntu--lv', 'fstype': 'ext4', 'options': 'rw,relatime', 'dump': 0, 'passno': 0, 'size_total': 25180848128, 'size_available': 16110280704, 'block_size': 4096, 'block_total': 6147668, 'block_available': 3933174, 'block_used': 2214494, 'inode_total': 1572864, 'inode_available': 1487555, 'inode_used': 85309, 'uuid': 'efab5bcf-8a63-4463-a220-1ed261707ab1'}, {'mount': '/snap/core20/2318', 'device': '/dev/loop1', 'fstype': 'squashfs', 'options': 'ro,nodev,relatime,errors=continue', 'dump': 0, 'passno': 0, 'size_total': 67108864, 'size_available': 0, 'block_size': 131072, 'block_total': 512, 'block_available': 0, 'block_used': 512, 'inode_total': 12057, 'inode_available': 0, 'inode_used': 12057, 'uuid': 'N/A'}, {'mount': '/snap/snapd/21759', 'device': '/dev/loop3', 'fstype': 'squashfs', 'options': 'ro,nodev,relatime,errors=continue', 'dump': 0, 'passno': 0, 'size_total': 40763392, 'size_available': 0, 'block_size': 131072, 'block_total': 311, 'block_available': 0, 'block_used': 311, 'inode_total': 651, 'inode_available': 0, 'inode_used': 651, 'uuid': 'N/A'}, {'mount': '/snap/core20/1974', 'device': '/dev/loop4', 'fstype': 'squashfs', 'options': 'ro,nodev,relatime,errors=continue', 'dump': 0, 'passno': 0, 'size_total': 66584576, 'size_available': 0, 'block_size': 131072, 'block_total': 508, 'block_available': 0, 'block_used': 508, 'inode_total': 11995, 'inode_available': 0, 'inode_used': 11995, 'uuid': 'N/A'}, {'mount': '/snap/snapd/19457', 'device': '/dev/loop2', 'fstype': 'squashfs', 'options': 'ro,nodev,relatime,errors=continue', 'dump': 0, 'passno': 0, 'size_total': 55967744, 'size_available': 0, 'block_size': 131072, 'block_total': 427, 'block_available': 0, 'block_used': 427, 'inode_total': 658, 'inode_available': 0, 'inode_used': 658, 'uuid': 'N/A'}, {'mount': '/snap/lxd/29351', 'device': '/dev/loop0', 'fstype': 'squashfs', 'options': 'ro,nodev,relatime,errors=continue', 'dump': 0, 'passno': 0, 'size_total': 91357184, 'size_available': 0, 'block_size': 131072, 'block_total': 697, 'block_available': 0, 'block_used': 697, 'inode_total': 959, 'inode_available': 0, 'inode_used': 959, 'uuid': 'N/A'}, {'mount': '/snap/lxd/24322', 'device': '/dev/loop5', 'fstype': 'squashfs', 'options': 'ro,nodev,relatime,errors=continue', 'dump': 0, 'passno': 0, 'size_total': 117440512, 'size_available': 0, 'block_size': 131072, 'block_total': 896, 'block_available': 0, 'block_used': 896, 'inode_total': 873, 'inode_available': 0, 'inode_used': 873, 'uuid': 'N/A'}, {'mount': '/boot', 'device': '/dev/sda2', 'fstype': 'ext4', 'options': 'rw,relatime', 'dump': 0, 'passno': 0, 'size_total': 2040373248, 'size_available': 1779724288, 'block_size': 4096, 'block_total': 498138, 'block_available': 434503, 'block_used': 63635, 'inode_total': 131072, 'inode_available': 130756, 'inode_used': 316, 'uuid': '7bd52725-dc76-44b1-ab51-4308590bf771'}])
changed: [ubuntu2] => (item=Devices: [{'mount': '/', 'device': '/dev/mapper/ubuntu--vg-ubuntu--lv', 'fstype': 'ext4', 'options': 'rw,relatime', 'dump': 0, 'passno': 0, 'size_total': 25180848128, 'size_available': 16343920640, 'block_size': 4096, 'block_total': 6147668, 'block_available': 3990215, 'block_used': 2157453, 'inode_total': 1572864, 'inode_available': 1489492, 'inode_used': 83372, 'uuid': 'ad2973b8-4adf-412e-8459-12c35403ae5d'}, {'mount': '/snap/core20/1974', 'device': '/dev/loop2', 'fstype': 'squashfs', 'options': 'ro,nodev,relatime,errors=continue', 'dump': 0, 'passno': 0, 'size_total': 66584576, 'size_available': 0, 'block_size': 131072, 'block_total': 508, 'block_available': 0, 'block_used': 508, 'inode_total': 11995, 'inode_available': 0, 'inode_used': 11995, 'uuid': 'N/A'}, {'mount': '/snap/snapd/21759', 'device': '/dev/loop0', 'fstype': 'squashfs', 'options': 'ro,nodev,relatime,errors=continue', 'dump': 0, 'passno': 0, 'size_total': 40763392, 'size_available': 0, 'block_size': 131072, 'block_total': 311, 'block_available': 0, 'block_used': 311, 'inode_total': 651, 'inode_available': 0, 'inode_used': 651, 'uuid': 'N/A'}, {'mount': '/snap/snapd/19457', 'device': '/dev/loop1', 'fstype': 'squashfs', 'options': 'ro,nodev,relatime,errors=continue', 'dump': 0, 'passno': 0, 'size_total': 55967744, 'size_available': 0, 'block_size': 131072, 'block_total': 427, 'block_available': 0, 'block_used': 427, 'inode_total': 658, 'inode_available': 0, 'inode_used': 658, 'uuid': 'N/A'}, {'mount': '/snap/lxd/29351', 'device': '/dev/loop3', 'fstype': 'squashfs', 'options': 'ro,nodev,relatime,errors=continue', 'dump': 0, 'passno': 0, 'size_total': 91357184, 'size_available': 0, 'block_size': 131072, 'block_total': 697, 'block_available': 0, 'block_used': 697, 'inode_total': 959, 'inode_available': 0, 'inode_used': 959, 'uuid': 'N/A'}, {'mount': '/snap/core20/2318', 'device': '/dev/loop4', 'fstype': 'squashfs', 'options': 'ro,nodev,relatime,errors=continue', 'dump': 0, 'passno': 0, 'size_total': 67108864, 'size_available': 0, 'block_size': 131072, 'block_total': 512, 'block_available': 0, 'block_used': 512, 'inode_total': 12057, 'inode_available': 0, 'inode_used': 12057, 'uuid': 'N/A'}, {'mount': '/snap/lxd/24322', 'device': '/dev/loop5', 'fstype': 'squashfs', 'options': 'ro,nodev,relatime,errors=continue', 'dump': 0, 'passno': 0, 'size_total': 117440512, 'size_available': 0, 'block_size': 131072, 'block_total': 896, 'block_available': 0, 'block_used': 896, 'inode_total': 873, 'inode_available': 0, 'inode_used': 873, 'uuid': 'N/A'}, {'mount': '/boot', 'device': '/dev/sda2', 'fstype': 'ext4', 'options': 'rw,relatime', 'dump': 0, 'passno': 0, 'size_total': 2040373248, 'size_available': 1779724288, 'block_size': 4096, 'block_total': 498138, 'block_available': 434503, 'block_used': 63635, 'inode_total': 131072, 'inode_available': 130756, 'inode_used': 316, 'uuid': '336df425-25c5-40b8-a429-f329dd2eca7f'}])
changed: [ubuntu1] => (item=############ Start ############)
changed: [ubuntu2] => (item=############ Start ############)
PLAY RECAP *****************************************************************************************************************************************************************************************************************************************************************
ubuntu1 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
ubuntu2 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
로그 파일이 정상적으로 생성되었는지 테스트하기 위해 노드 중 하나인 ubuntu1
로 이동하여 확인해보자.
root@ubuntu1:~# ls -lh /var/log/daily_check/
total 4.0K
-rw-r--r-- 1 root root 3.2K Sep 10 10:09 system_info.logs
5. CPU, 메모리, 디스크 사용률 모니터링
앤서블 팩트에서 제공하지 않는 모니터링 도구로 조금 더 상세한 자원 사용률을 모니터링하려면 해당 명령어를 사용하기 위한 패키지를 설치하면 된다.
그러나 여러 서버의 사용률을 모니터링해야 한다면 상황은 달라진다.
그런 경우에는 셸 스크립트나 앤서블을 활용하면 조금 더 쉽게 모니터링이 가능해진다.
How ?
이번에는 CPU, 메모리, 디스크 사용률을 조금 더 자세하게 살펴볼 수 있는 dstat
, iostat
, vmstat
와 같은 명령어를 사용하여
관리 노드의 CPU, 메모리, 디스크 사용률을 모니터링해보자.
dstat
와 iostat
를 실행하려면 우선 dstat
와 sysstat
이라는 패키지를 설치해야 한다.
그리고 각각의 명령어에 해당하는 옵션도 다양하다.
dstat
,sysstat
패키지를 설치한다. 운영체제가 레드헷 계열이면ansible.builtin.dnf
를, 데비안 계열이면ansible.builtin.apt
모듈을 이용하여 설치한다.- 각각의 명령어 실행은
ansible.builtin.shell
을 이용해 실행하고, loop 키워드를 사용하여 모니터링 명령어 별로 여러 옵션을 추가하여 명령을 실행한다. - 실행된 명령어 결과를 로그 디렉토리에 저장한다.
플레이북 개발
실습 진행을 위해 05_monitoring_system
디렉토리를 생성해주자.
mkdir ./05_monitoring_system
그리고 inventory 파일을 생성하자.
호스트 그룹을 따로 나누지 않고 다음과 같이 호스트명으로만 구성된 inventory를 작성하도록 한다.
[nodes]
ubuntu1
ubuntu2
ansible.cfg 파일은 아래와 같이 작성한다.
[defaults]
inventory = ./inventory
remote_user = root
ask_pass = false
roles_path = ./roles
collection_paths = ./collection
interpreter_python=auto
[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false%
변수 정의를 위해 vars_packages.yaml
파일을 생성하고 변수와 변수 값을 정의한다.
---
log_directory: /home/ansible/logs
packages:
- dstat
- sysstat
변수 정의가 끝나면 monitoring_system.yaml
파일을 생성하고,
모니터링 명령어를 실행하는 ansible.builtin.shell
모듈을 작성한다.
loop 키워드를 이용하면 실행하고자 하는 명령어들을 사전 형식으로 반복하여 실행하도록 구현할 수 있다.
---
- hosts: nodes
vars_files: ./vars_packages.yaml
tasks:
- name: Install packages on RedHat
ansible.builtin.dnf:
name: "{{ item }}"
state: present
loop: "{{ packages }}"
when: ansible_facts.os_family == "RedHat"
- name: Install packages on Debian
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop: "{{ packages }}"
when: ansible_facts.os_family == "Debian"
- name: Monitoring dstat
ansible.builtin.shell: |
{{ item }} >> {{ log_directory/dstat.log }}
loop:
- dstat 2 10
- dstat -cmdlt -D vda 2 10
- name: Monitoring iostat
ansible.builtin.shell: |
{{ item }} >> {{ log_directory }}/iostat.log
loop:
- iostat
- echo "=========="
- iostat -t -c -dp vda
- echo "=========="
- name: Monitoring vmstat
ansible.builtin.shell: |
{{ item }} >> {{ log_directory/vmstat.log }}
loop:
- vmstat
- echo "=========="
- vmstat -dt
- echo "=========="
- vmstat -D
- echo "=========="
- name: Monitoring df
ansible.builtin.shell: |
df -h >> {{ log_directory }}/df.log
문법을 체크하고 플레이북을 실행해보자.
$ ansible-playbook --syntax-check monitoring_system.yaml
$ ansible-playbook -i ./inventory monitoring_system.yaml
TASK [Install packages on RedHat] ******************************************************************************************************************************************************************************************************************************************
skipping: [ubuntu1] => (item=dstat)
skipping: [ubuntu1] => (item=sysstat)
skipping: [ubuntu1]
skipping: [ubuntu2] => (item=dstat)
skipping: [ubuntu2] => (item=sysstat)
skipping: [ubuntu2]
TASK [Install packages on Debian] ******************************************************************************************************************************************************************************************************************************************
ok: [ubuntu1] => (item=dstat)
ok: [ubuntu2] => (item=dstat)
ok: [ubuntu1] => (item=sysstat)
ok: [ubuntu2] => (item=sysstat)
TASK [Monitoring dstat] ****************************************************************************************************************************************************************************************************************************************************
changed: [ubuntu1] => (item=dstat 2 10)
changed: [ubuntu2] => (item=dstat 2 10)
changed: [ubuntu2] => (item=dstat -cmdlt -D vda 2 10)
changed: [ubuntu1] => (item=dstat -cmdlt -D vda 2 10)
TASK [Monitoring iostat] ***************************************************************************************************************************************************************************************************************************************************
changed: [ubuntu1] => (item=iostat)
changed: [ubuntu2] => (item=iostat)
changed: [ubuntu1] => (item=echo "==========")
changed: [ubuntu2] => (item=echo "==========")
changed: [ubuntu1] => (item=iostat -t -c -dp vda)
changed: [ubuntu2] => (item=iostat -t -c -dp vda)
changed: [ubuntu1] => (item=echo "==========")
changed: [ubuntu2] => (item=echo "==========")
TASK [Monitoring vmstat] ***************************************************************************************************************************************************************************************************************************************************
changed: [ubuntu1] => (item=vmstat)
changed: [ubuntu2] => (item=vmstat)
changed: [ubuntu1] => (item=echo "==========")
changed: [ubuntu2] => (item=echo "==========")
changed: [ubuntu1] => (item=vmstat -dt)
changed: [ubuntu2] => (item=vmstat -dt)
changed: [ubuntu2] => (item=echo "==========")
changed: [ubuntu1] => (item=echo "==========")
changed: [ubuntu2] => (item=vmstat -D)
changed: [ubuntu1] => (item=vmstat -D)
changed: [ubuntu1] => (item=echo "==========")
changed: [ubuntu2] => (item=echo "==========")
TASK [Monitoring df] *******************************************************************************************************************************************************************************************************************************************************
changed: [ubuntu1]
changed: [ubuntu2]
PLAY RECAP *****************************************************************************************************************************************************************************************************************************************************************
ubuntu1 : ok=6 changed=4 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
ubuntu2 : ok=6 changed=4 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
로그 파일이 정상적으로 생성되었는지 확인하기 위해 노드 중 하나인 ubuntu1
로 이동하여 /home/ansible/logs
디렉토리를 확인해보자.
root@ubuntu1:~# ls -lh /home/ansible/logs
total 16K
-rw-r--r-- 1 root root 454 Sep 10 10:38 df.log
-rw-r--r-- 1 root root 1.8K Sep 10 10:38 dstat.log
-rw-r--r-- 1 root root 2.9K Sep 10 10:38 iostat.log
-rw-r--r-- 1 root root 1.9K Sep 10 10:38 vmstat.log
트러블슈팅
1. 권한 오류
- 제어 노드에서
root
계정으로 진행하지 않아 권한 관련 오류가 발생하였음- root로 전환 후 해결
2. 변수 문법 오류
- 플레이북에서 변수를 사용할 때는 큰따옴표와 함께 겹 중괄호(
{{ }}
) 사이에 변수명을 정의한다.