Ansible을 사용하여 시스템을 구축하고 환경 설정을 자동화해보자

Handling some examples discussed amid A101 3rd week

Sigrid Jin
133 min readFeb 3, 2024
앤서블로 시작하는 인프라 자동화 (한빛미디어)

이번 시간에는 앤서블을 활용하여 시스템 구축과 환경 설정을 자동화할 수 있는 여러 예제에 대해 살펴보도록 하겠다.

여기서 다룬 예제들은 예제들은 앤서블로 시작하는 인프라 자동화 (장현정, 이태훈, 김병수 저, 한빛미디어) 에서 바탕을 두어 가시다님이 재구성한 것을 풀이한 것이다.

사용자 계정 생성하기

Ansible을 사용하여 리모트 서버에 사용자 계정을 생성하고 암호를 설정하는 플레이북을 작성해보겠다.

이 때 계정과 패스워드 정보는 Ansible Vault를 이용하여 암호화된다.

사전 분석

  • 사용자 계정과 패스워드 정보는 Ansible Vault를 이용하여 암호화된다.
  • 사용자 계정 생성은ansible.builtin.user 모듈을 사용한다.

플레이북 설계

  • 프로젝트 디렉터리 생성
  • ansible.cfg, inventory 파일 작성
  • 격리의 목적 → config와 분리, 보안의 목적이 있다.
  • 사용자 계정 정보가 정의된 변수 파일을 생성
  • 사용자 계정을 생성하는 플레이북 작성
  • ansible.builtin.user의 userid, userpw는 빈번하게 바뀔 수 있다.
  • 실제로 userid, userpw는 숨겨야 하는 값이므로 별도의 vault 파일로 분리하였다.

플레이북 개발

  1. ansible.cfg
[defaults]
inventory = ./inventory
remote_user = ubuntu
ask_pass = false
inject_facts_as_vars = false
gathering = smart
fact_caching = jsonfile
fact_caching_connection = myfacts
# role_path = ./roles

[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false

2. inventory

[web]
tnode1
tnode2

[db]
tnode3

[all:children]
web
db

3. vault 변수 파일을 생성

ubuntu@server:~/my-ansible/chapter_09.1$ ansible-vault create vars/secret.yml
New Vault password: hello!
Confirm New Vault password: hello!

---

user_info:
- userid: "ansible"
userpw: "ansiblePw1"
- userid: "stack"
userpw: "stackPw1"

4. vault 변수 파일이 정상적으로 생성되었는지를 확인

ubuntu@server:~/my-ansible/chapter_09.1$ ls -l vars/secret.yml
-rw------- 1 ubuntu ubuntu 743 Feb 3 09:45 vars/secret.yml
ubuntu@server:~/my-ansible/chapter_09.1$ cat vars/secret.yml
$ANSIBLE_VAULT;1.1;AES256
62353361316465323163363530663832653864303136613364633330333834396163326335313062
6538633166333632353132353739386265353064336637360a376333393262376435363833336632
35656639363138386334643730323833383566636433346465343230363139316334383735363433
6437383930376631300a366237666164656361313436353835333965383966356532356136623437
34633932633832373536623362653332353037663734363438633935643965363235656664363965
35376532663033633533383465653137383932376365646236616135666533626163656132643062
34313434313364333065636665316662396530316638613766336466396634653634303465396265
62663135633232636462643333623933616333396166306165643138633539373062616134656363
36666531383563346237383035653836313737386439633830623165333962613561

5. 사용자의 계정을 생성하는 플레이북을 작성

---

- hosts: all

# vault로 사용자 계정 관련 변수가 정의된 파일을 임포트하여 사용
vars_files:
- vars/secret.yml

tasks:
# loop 문을 사용하여 user_info의 userid와 userpw 사용
- name: Create user
ansible.builtin.user:
name: "{{ item.userid }}"
password: "{{ item.userpw | password_hash('sha512', 'mysecret') }}"
state: present
shell: /bin/bash
loop: "{{ user_info }}"

Note: 실제 패스워드에서 mysecret은 되지 않는다. 왜?

The error message you encountered, “invalid characters in salt. invalid characters in salt”, indicates that the salt you provided (mysecret!) contains characters that are not allowed by the password_hash filter or, more specifically, by the underlying cryptographic library used for generating the hash. In many cryptographic functions, including the one used by Ansible’s password_hash filter, the salt must adhere to certain character restrictions.

These restrictions are often imposed by the hashing algorithm or the library’s implementation of it. For the SHA-512 algorithm, as used in your playbook, the salt is expected to be a string that can be safely passed to the crypt library function, which might impose its own restrictions on valid characters. The exclamation mark (!) in your salt (mysecret!) might be interpreted by the system’s crypt library as an invalid character for a salt value.

Not all characters are valid in all contexts, and special characters can sometimes cause issues, depending on the specific implementation of the hashing function or the crypt library’s expectations. To resolve this issue, you can either: • Avoid using special characters in the salt, sticking to alphanumeric characters to ensure compatibility. • Check the documentation for the specific crypt library your system uses to understand which characters are considered valid for salts. In general, for broad compatibility, it’s a good practice to use a simple alphanumeric string for salts in password hashing unless you’re certain that the specific characters you wish to include are supported by the underlying hashing mechanism.

6. 플레이북 문법 검사

ubuntu@server:~/my-ansible/chapter_09.1$ ansible-playbook --ask-vault-pass --syntax-check create_user.yml
Vault password:

playbook: create_user.yml

7. 플레이북을 실행해본다.

ubuntu@server:~/my-ansible/chapter_09.1$ ansible-playbook --ask-vault-pass create_user.yml
Vault password:

PLAY [all] ***************************************************************************************************************************************************

TASK [Create user] *
******************************************************************************************************************************************

[DEPRECATION WARNING]: Encryption using the Python crypt module is deprecated. The Python crypt module is deprecated and will be removed from Python 3.13.
Install the passlib library for continued encryption functionality. This feature will be removed in version 2.17. Deprecation warnings can be disabled by
setting deprecation_warnings=False in ansible.cfg.
[DEPRECATION WARNING]: Encryption using the Python crypt module is deprecated. The Python crypt module is deprecated and will be removed from Python 3.13.
Install the passlib library for continued encryption functionality. This feature will be removed in version 2.17. Deprecation warnings can be disabled by
setting deprecation_
warnings=False in ansible.cfg.
[DEPRECATION WARNING]: Encryption using the Python crypt module is deprecated. The Python crypt module is deprecated and will be removed from Python 3.13.
Install the passlib library for continued encryption functionality. This feature will be removed in version 2.17. Deprecation warnings can be disabled by
setting deprecation_warnings=False in ansible.cfg.
changed: [tnode1] => (item={'userid': 'ansible', 'userpw': 'ansiblePw1'})
changed: [tnode2] => (item={'userid': 'ansible', 'userpw': 'ansiblePw1'})
changed: [tnode3] => (item={'userid': 'ansible', 'userpw': 'ansiblePw1'})
changed: [tnode1] => (item={'userid': 'stack', 'userpw': 'stackPw1'})
changed: [tnode3] => (item={'userid': 'stack', 'userpw': 'stackPw1'})
changed: [tnode2] => (item={'userid': 'stack', 'userpw': 'stackPw1'})

PLAY RECAP
***************************************************************************************************************************************************
tnode1 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
tnode2 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
tnode3 : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

8. 계정 생성을 확인한다.

ubuntu@server:~/my-ansible/chapter_09.1$ ansible -m shell -a "tail -n 3 /etc/passwd" all
tnode2 | CHANGED | rc=0 >>
lxd:x:999:100::/var/snap/lxd/common/lxd:/bin/false
ansible:x:1001:1001::/home/ansible:/bin/bash
stack:x:1002:1002::/home/stack:/bin/bash
tnode1 | CHANGED | rc=0 >>
lxd:x:999:100::/var/snap/lxd/common/lxd:/bin/false
ansible:x:1001:1001::/home/ansible:/bin/bash
stack:x:1002:1002::/home/stack:/bin/bash
tnode3 | CHANGED | rc=0 >>
lxd:x:999:100::/var/snap/lxd/common/lxd:/bin/false
ansible:x:1001:1001::/home/ansible:/bin/bash
stack:x:1002:1002::/home/stack:/bin/bash

SSH 키를 생성하고 복사한다

사전 분석 (어떤 동작이 필요한가)

퇴사하면 지워주고, 입사하면 만들어주고. 일일이 들어가서 해주기 쉽지 않다. 플레이북이 있다면 변수 넣고 만들면 된다. 특정 사용자에 대해 키를 먼저 생성하고 생성된 키를 각 SSH 키 cp 명령어로 복사해서 사용자가 바로 해당하는 ssh 키 파일을 비밀 키를 가지고 접근을 바로 할 수 있다.

따라서 사용자의 아이디는 dynamically하게 외부 변수를 받도록 해야 한다. ansible 계정을 만들고 SSH 키를 생성한다. ansible이라고 하는 계정에 SSH 키를 생성하고 복사하는 역할을 해주어야 한다. 생성된 공개 키는 각 tnode에 복사한다. ansible 서버에 유저 모드를 통해 ansible 계정을 만들 때 SSH 키를 만드며 다음의 내용을 수행한다.

  • 계정을 생성할 때는 ansible.builtin.user 모듈을 사용해야 한다.
  • SSH 공개 키를 복사할 때는 ansible.posix.authorized_key 모듈을 사용해야 한다.

플레이북 설계 (행위와 동작을 검색한 뒤 이에 따라 설계한다)

  • 해당 플레이북명은 create_sshkey.yml 로 설정하고, ‘Create ssh key’ 태스크와 ‘Copy SSH Pub Key’ 라는 2개의 태스크를 갖습니다.
  • create_sshkey.yml은 앤서블 서버(localhost)에서 키를 만든다. 그리고 copy하는 일은 tnode에서 실행한다.
  • 인벤토리에는 다음과 같이 tnode라는 그룹을 만든 다음 모든 관리 노드를 tnode 그룹으로 정의합니다.

플레이북 개발

  1. create_sshkey.yml
---

- hosts: localhost
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
shell: /bin/bash

- hosts: tnode
tasks:
- name: Copy SSH Pub key
ansible.posix.authorized_key:
user: "{{ userid }}"
state: present
key: "{{ lookup('file', '/home/{{ userid }}/.ssh/id_rsa.pub') }}"

2. 플레이북 문법 체크

ansible-playbook --syntax-check create_sshkey.yml

3. 플레이북 실행 (가장 우선순위가 높은 -e에 userid를 넣는다)

ansible-playbook -e userid=ansible create_sshkey.yml -vvvvv

TASK [Copy SSH public key]
***********************************************************************************************************************************
task path: /home/ubuntu/my-ansible/chapter_09.2/create_sshkey.yml:18
File lookup using None as file
fatal: [tnode1]: FAILED! => {
"msg": "The 'file' lookup had an issue accessing the file '/home/ansible/.ssh/id_rsa.pub'. file not found, use -vvvvv to see paths searched"
}
File lookup using None as file
fatal: [tnode2]: FAILED! => {
"msg": "The 'file' lookup had an issue accessing the file '/home/ansible/.ssh/id_rsa.pub'. file not found, use -vvvvv to see paths searched"
}
File lookup using None as file
fatal: [tnode3]: FAILED! => {
"msg": "The 'file' lookup had an issue accessing the file '/home/ansible/.ssh/id_rsa.pub'. file not found, use -vvvvv to see paths searched"
}

4. 이유를 찾아보니 ubuntu 계정 권한 문제이다.

ubuntu@server:~/my-ansible/chapter_09.2$ ls -l /home/ansible/.ssh
ls: cannot access '/home/ansible/.ssh': Permission denied
ubuntu@server:~/my-ansible/chapter_09.2$ sudo ls -l /home/ansible/.ssh
total 8
-rw------- 1 ansible ansible 1831 Feb 3 13:50 id_rsa
-rw-r--r-- 1 ansible ansible 409 Feb 3 13:50 id_rsa.pub

5. 따라서 ansible.cfg 파일을 root 계정으로 실행하도록 변경한다.

[defaults] # ansible.cfg
inventory = ./inventory
remote_user = root
inject_facts_as_vars = false

sudo ansible-playbook -e userid=ansible create_sshkey.yml --ask-pass
SSH password: 입력

6. tnode 관리 노드에 ansible 계정에 패스워드를 입력하지 않고도 sudo 권한을 부여할 수 있도록 root 계정으로 진행한다.

---
- hosts: all

tasks:
- name: Create file
ansible.builtin.file:
path: /etc/sudoers.d/ansible
mode: '0600'
state: touch

- name: Edit file
ansible.builtin.lineinfile:
path: /etc/sudoers.d/ansible
line: ansible ALL=(root) NOPASSWD:ALL

NTP 서버를 설치하고 설정해보자

사전 분석

  • NTP 서버는 네트워크에 구성된 물리서버 간 시간을 동기화해줍니다. 컴퓨터 자체적으로 시간을 세는 경우 환경에 따라 차츰 시간이 틀어질 수 있습니다. 시간 동기화는 네트워크 유지보수 및 보안의 관점에서 중요합니다. 시간은 디버깅 시 중요하게 고려되는 요소이기 때문입니다. 게다가 NTP 서버 구축 유무는 인프라 관련 감사 항목에도 포함됩니다.
  • NTP 서버 주소는 메인 플레이북에서 정의하는데, 운영체제에 따라 사용해야 하는 모듈이 다르다.
  • 우분투는 apt 모듈을 사용하여 NTP 서버 설정을 도와주고 동기화를 해주는 chrony를 설치
  • CentOS/Redhat이면 dnf 모듈을 사용해서 chrony를 설치
  • Jinja2 템플릿 방식의 chrony.conf 파일을 대상 호스트로 복사
  • 설정 파일이 복사되면 chrony 서비스를 재시작
  • 위의 내용을 롤을 이용하여 설계 및 재작성한다

플레이북 설계

  • Role (myrole.chrony) 내부에 vars, templates, tasks를 넣어둔다.
  • chrony 서비스 설치를 위한 롤에서는 변수를 정의하는 vars, 환경 설정 템플릿을 위한 templates, 태스크를 정의한 tasks, 환경 설정 후 chrony 서비스를 재시작하기 위한 handlers를 사용한다.
  • tasks의 main.yml은 공통적인 수행 사항을 명시하고, 각 distribution에 맞게 yml 파일을 만들어주면 된다.
  • main.yml은 Restart chrony 를 실행하면, handlers는 해당 이벤트를 받아 main.yml 을 실행한다.
  • NTP 패키지를 설치하고 설정 구성 파일은 해당 서버에 맞게 설정되어야 하는데 일반적으로 동적으로 confy.conf가 설정되어야 하므로 ninja2 template을 이용한다.

플레이북 개발

  1. ssh 접속 허용하기
  • sudo 패스워드 만들기: sudo passwd root
  • 설정한 비밀번호로 root 접속하기: su
  • sshd_config 수정하기: vim /etc/ssh/sshd_config 이후 PermitRootLogin yes 로 설정하기
  • authorized_keys 수정하기: vim /root/.ssh/authorized_keys 에 들어가서 아래 내용을 삭제한다.
vi /root/.ssh/authorized_keys

no-port-forwarding,no-agent-forwarding,no-X11-forwarding,command="echo 'Please login as the user \"ubuntu\" rather than the user \"root\".';echo;sleep 10;exit 142"

2. root에서 ansible 계정의 실습 디렉토리를 생성한다.

su - ansible -c 'mkdir -p ~/ansible-project/chapter_09.3'
ls -l /home/ansible/

# whoami
# pwd
# cd

3. ansible 계정으로 들어가서 프로젝트 디렉터리를 생성하고 ansible.cfg와 inventory 파일을 작성해본다.

# ansible 계정 전환
su - ansible
whoami
cd
pwd

# 프로젝트 디렉터리로 이동
cd ~/ansible-project/chapter_09.3
pwd
/home/ansible/ansible-project/chapter_09.3

4. ansible.cfg와 inventory 파일을 생성한다.

  • ansible로 target 접근한 뒤에 privillege escalation을 해두었습니다.
# ansible.cfg

[defaults]
inventory = ./inventory
remote_user = ansible
ask_pass = false
roles_path = ./roles

[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false

# inventory

cat <<EOT > inventory
[tnode]
tnode1
tnode2
tnode3

5. ansible galaxy를 활용하여 필요한 롤을 생성해본다.

ansible-galaxy role init --init-path ./roles myrole.chrony

ansible@server:~/ansible-project/chapter_09.3$ tree roles
roles
└── myrole.chrony
├── README.md
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── tasks
│ └── main.yml
├── templates
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml

9 directories, 8 files

6. chrony.conf.j2 파일을 만들어서 ntp_server 변수를 사용하도록 하자.

  • config 파일을 만들 때 jinja2 템플릿 엔진을 많이 사용한다.
touch ~/ansible-project/chapter_09.3/roles/myrole.chrony/templates/chrony.conf.j2
  • chrony.conf.j2: ntp_server를 변수로 받아서 동적으로 만든다는 것이다.
pool {{ ntp_server }}
driftfile /var/lib/chrony/drift
makestep 1.0 3
rtcsync
allow 10.10.0.0/16
local stratum 10
keyfile /etc/chrony.keys
leapsectz right/UTC
logdir /var/log/chrony

7. 핸들러에서는 chrony 서비스를 재시작하는 태스크가 포함되어 있다. 작업이 끝나고 노티되면 실행되는 태스크(서비스)를 일반적으로 넣는다.

  • /roles/myrole.chrony/handlers/main.yml
---
# handlers file for myrole.chrony

- name: Restart chrony
ansible.builtin.service:
name: "{{ service_name }}" # 서비스 이름은 변수로 받아온다.
state: restarted # 해당 서비스를 재시작한다

8. 메인 태스크를 작성한다.

---
# tasks file for myrole.chrony

- name: Import playbook
ansible.builtin.include_tasks: # distribution 팩트 변수를 이용하여 다른 파일에 태스크를 포함시킨다
file: "{{ ansible_facts.distribution }}.yml" # 우분투면 ubuntu.yaml 로 바뀌고 cent면 cent.yml로 바뀌게 만든다.

- name: Copy chrony config file when Ubuntu # 운영체제에 맞는 chrony 환경 설정 파일 설정을 복사한다. 복사되고 나면 notify를 통해 Restart Chrony 핸들러를 호출한다.
ansible.builtin.template:
src: chrony.conf.j2 # 변수 선언된 것이 바뀔 놈
dest: /etc/chrony/chrony.conf # 변수 선언된 것이 바뀌게 될, 그래서 들어가게 될 놈
notify: "Restart chrony"
when: ansible_facts.distribution == "Ubuntu"

- name: Copy chrony config file when Other OS
ansible.builtin.template:
src: chrony.conf.j2
dest: /etc/chrony.conf
notify: "Restart chrony"
when: ansible_facts.distribution in fedora_os

9. include_tasks에서 팩트 정보를 수집해서 사용하므로 편하게 사용할 수 있다. 각 OS에 해당하는 것을 만들면 되므로 import playbook 시점에서 사용할 플레이북을 만든다.

touch ~/ansible-project/chapter_09.3/roles/myrole.chrony/tasks/RedHat.yml
touch ~/ansible-project/chapter_09.3/roles/myrole.chrony/tasks/CentOS.yml
touch ~/ansible-project/chapter_09.3/roles/myrole.chrony/tasks/Ubuntu.yml
  • RedHat.yml
- name: Install chrony using dnf
ansible.builtin.dnf:
name: "{{ package_name }}"
state: latest
  • CentOS.yml
- name: Install chrony using dnf
ansible.builtin.dnf:
name: "{{ package_name }}"
state: latest
  • Ubuntu.yml
- name: Install chrony using apt
ansible.builtin.apt:
name: "{{ package_name }}"
state: latest

10. 실제 플레이북인 install_ntp.yml 파일을 작성해보자. 롤을 호출해서 불러오는 메인 플레이북을 만들어야 한다.

touch ~/ansible-project/chapter_09.3/install_ntp.yml

---

- hosts: tnode
roles:
- role: myrole.chrony
ntp_server: 0.kr.pool.ntp.org

플레이북 실행

  • 제대로 실행되었는지 검증하기 위해서는 위의 ninja2 템플릿 파일에 변수가 잘 설정되었는지를 보자.
ansible-playbook install_ntp.yml

# ansible -m shell -a "cat /etc/chrony/chrony.conf" tnode1# pool 0.kr.pool.ntp.org

# driftfile /var/lib/chrony/drift
# makestep 1.0 3
# rtcsync
# allow 10.10.0.0/16
# local stratum 10
# keyfile /etc/chrony.keys
# leapsectz right/UTC
# logdir /var/log/chrony

# ansible -m shell -a "systemctl status chrony" tnode1
# chrony.service의 active status를 확인해본다.

네트워크 IP 설정하기

사전 설계

  • OS가 우분투일 경우에는 netplan 파일을 이용하여 IP를 설정하는데, 이 때 netplan은 파일이므로 사전에 netplan 파일 구조를 확인하고 jinja2 템플릿으로 작성한다.
  • OS가 CentOS/레드햇일 경우에는 nmcli 모듈을 사용하여 IP를 설정한다.
  • IP 정보는 메인 플레이북에서 변수로 받으며 네트워크 인터페이스에서 실제 호스트에 있는지 앤서블 팩트를 통해 확인해서 사전 동작을 분석해본다.

플레이북 설계

  • set_ip.yml 이라는 플레이북이 있다고 했을 때, myrole.nmcli와 myrole.netplan 구분함.
  • netplan이라는 파일을 먼저 구성하고 구성한 파일을 템플릿으로 복사한 다음에 적용하려면 netplan apply라는 명령어를 보내주어야 한다.
  • template 파일을 구조화한 다음에 메인 태스크에서 이를 복사하고 복사한 다음에는 netplan apply를 하도록 이벤트를 발송하면 이를 메인 핸들러가 캐치해서 실행한다.

플레이북 개발

  1. 롤을 먼저 생성해보자.
#
ansible-galaxy role init --init-path ./roles myrole.nmcli
ansible-galaxy role init --init-path ./roles myrole.netplan

# 확인
ansible-galaxy role list
tree roles -L 2

2. myrole.nmcli에 태스크 파일을 작성해보자.

  • 실제로 네트워크 인터페이스가 있는지 없는지 한번 더 체크하는 조건문을 만들었다.
---
# tasks file for myrole.nmcli

- name: Setup nic ip
community.general.nmcli: # Redhat과 centos
type: ethernet
conn_name: "{{ item.con_name }}"
ip4: "{{ item.ip_addr }}"
gw4: "{{ item.ip_gw }}"
dns4: "{{ item.ip_dns }}"
state: present
loop: "{{ net_info }}" # loop에서 네트워크 인터페이스가 1번, 2번, 3번 ... 이런 식으로 순회할 수 있다면?
when: net_info[0].con_name in ansible_facts.interfaces # when 키워드를 사용하여 외부로부터 받은 인터페이스가 앤서블 팩트에 존재하는 확인

3. netplan에는 jinja2 템플릿 파일을 작성해본다.

  • Jinja2 템플릿을 이용하여 외부에서 받은 배열형 변수for 문으로 하나씩 꺼내 사용할 수 있다.
touch ~/ansible-project/chapter_10.1/roles/myrole.netplan/templates/01-netplan-ansible.yaml.j2

# This is the network config written by 'ansible'
network:
version: 2
ethernets: # 여기 아래 config 부분이 동적으로 바뀐다.
{% for item in net_info %} # Jinja2 템플릿에서 **제어문**이나 **반복문**을 사용할 때는 다음과 같이 **{% ~ %}**를 이용합니다.
{{ item.con_name }}:
dhcp4: no
dhcp6: no
addresses: [{{ item.ip_addr }}]
gateway4: {{ item.ip_gw }}
nameservers:
addresses: [{{ item.ip_dns }}]
{% endfor %}

4. myrole.netplan에서 태스크 파일을 작성해본다.

  • src라고 하는 ansible.builtin.template 모듈에서 생성한 템플릿 파일을 대상 destination 호스트로 복사한다.
  • when 구문을 통해서 실제 외부에서 받은 인터페이스가 앤서블 팩트에 존재하는지 확인한다.
  • 만약 복사가 완료되었다면 notify를 통해 핸들러를 호출한다.
~/chapter_10.1/roles/myrole.netplan/tasks/main.yml

---
# tasks file for myrole.netplan

- name: Copy netplan file
ansible.builtin.template:
src: 01-netplan-ansible.yaml.j2
dest: /etc/netplan/01-netplan-ansible.yaml
when: net_info[0].con_name in ansible_facts.interfaces
notify: Netplan apply

5. myrole.netplan에 핸들러 파일을 작성해야 하는데, 이 때 핸들러는 command 모듈을 이용하여 netplan apply 명령어를 수행해야 한다.

---
# handlers file for myrole.netplan

- name: Netplan apply
ansible.builtin.command: netplan apply # 태스크를 호출하는 역할이다.

6. 마지막으로 롤을 호출할 메인 플레이북을 작성한다.

  • 롤에 전달할 변수 (fedora_os, net_info) 를 vars 섹션에 선언하고 tasks 섹션에는 롤을 추가한다.
  • ansible.builtin.include_role 모듈을 이용하여 해당 롤을 호출하면 when 구문을 함께 사용이 가능하다.
  • 앤서블 팩트에서 수집한 OS 버전에 따라 해당 롤 호출이 가능하다.
cd ~/ansible-project/chapter_10.1
touch set_ip.yml

---

- hosts: tnode1
vars:
fedora_os:
- CentOS
- RedHat
net_info: # 네트워크 인터페이스 1개
- con_name: ens5
ip_addr: 10.10.1.11/24
ip_gw: 10.10.1.1
ip_dns: 127.0.0.53

tasks:
- name: Include role in CentOS and RedHat
ansible.builtin.include_role:
name: myrole.nmcli
when: ansible_facts.distribution in fedora_os

- name: Include role in Ubuntu
ansible.builtin.include_role:
name: myrole.netplan
when: ansible_facts.distribution == "Ubuntu"

- hosts: tnode2
vars:
fedora_os:
- CentOS
- RedHat
net_info:
- con_name: ens7
ip_addr: 10.10.1.12/24
ip_gw: 10.10.1.1
ip_dns: 127.0.0.53

tasks:
- name: Include role in CentOS and RedHat
ansible.builtin.include_role:
name: myrole.nmcli
when: ansible_facts.distribution in fedora_os

- name: Include role in Ubuntu
ansible.builtin.include_role:
name: myrole.netplan
when: ansible_facts.distribution == "Ubuntu"

플레이북 실행

  1. 실행하기 전 tnode1의 네트워크 정보를 확인한다.
ubuntu@server:~/.ssh$ ssh -i "sigrid-gashida.pem" ubuntu@tnode1 ls /etc/netplan
50-cloud-init.yaml

ubuntu@server:~/.ssh$ ssh -i "sigrid-gashida.pem" ubuntu@tnode1 cat /etc/netplan/50-cloud-init.yaml
# This file is generated from information provided by the datasource. Changes
# to it will not persist across an instance reboot. To disable cloud-init's
# network configuration capabilities, write a file
# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following:
# network: {config: disabled}
# DHCP를 통해 IP를 받도록 설정되어 있다.

network:
ethernets:
ens5:
dhcp4: true
dhcp6: false
match:
macaddress: 02:ff:c5:1f:85:d4
set-name: ens5
version: 2

ubuntu@server:~/.ssh$ ssh -i "sigrid-gashida.pem" ubuntu@tnode1 ip -br -c addr
lo UNKNOWN 127.0.0.1/8 ::1/128
ens5 UP 10.10.1.11/24 metric 100 fe80::ff:c5ff:fe1f:85d4/64
# 기본 인터페이스 우분투의 첫 번째 ENI가 ens5이고 tnode1의 소유이다. (con_name = ens5)
# ip_addr = ens5의 IP (10.10.1.11/24)

ubuntu@server:~/.ssh$ ssh -i "sigrid-gashida.pem" ubuntu@tnode1 ip -c route
default via 10.10.1.1 dev ens5
default via 10.10.1.1 dev ens5 proto dhcp src 10.10.1.11 metric 100
10.10.0.2 via 10.10.1.1 dev ens5 proto dhcp src 10.10.1.11 metric 100
10.10.1.0/24 dev ens5 proto kernel scope link src 10.10.1.11 metric 100
10.10.1.1 dev ens5 proto dhcp scope link src 10.10.1.11 metric 100
# ip_gw: 10.10.1.1

ansible -m shell -a "cat /var/log/syslog | grep -i dhcp" tnode1

ubuntu@server:~/ansible-project/chapter_10.1$ ssh tnode1 sudo dhclient -v ens5
Internet Systems Consortium DHCP Client 4.4.1
Copyright 2004-2018 Internet Systems Consortium.
All rights reserved.
For info, please visit https://www.isc.org/software/dhcp/

Listening on LPF/ens5/02:ff:c5:1f:85:d4
Sending on LPF/ens5/02:ff:c5:1f:85:d4
Sending on Socket/fallback
DHCPDISCOVER on ens5 to 255.255.255.255 port 67 interval 3 (xid=0x9a09376)
DHCPOFFER of 10.10.1.11 from 10.10.1.1
DHCPREQUEST for 10.10.1.11 on ens5 to 255.255.255.255 port 67 (xid=0x7693a009)
DHCPACK of 10.10.1.11 from 10.10.1.1 (xid=0x9a09376)
RTNETLINK answers: File exists
bound to 10.10.1.11 -- renewal in 1401 seconds.

ubuntu@server:~/.ssh$ ssh -i "sigrid-gashida.pem" ubuntu@tnode1 nslookup blog.cloudneta.net
Server: 127.0.0.53
Address: 127.0.0.53#53 # DNS 정보

Non-authoritative answer:
Name: blog.cloudneta.net
Address: 52.219.60.84
Name: blog.cloudneta.net
Address: 52.219.204.80
Name: blog.cloudneta.net
Address: 52.219.202.72
Name: blog.cloudneta.net
Address: 52.219.60.9
Name: blog.cloudneta.net
Address: 52.219.206.84
Name: blog.cloudneta.net
Address: 52.219.148.60
Name: blog.cloudneta.net
Address: 52.219.204.44
Name: blog.cloudneta.net
Address: 52.219.144

# DHCPDISCOVER와 DHCPOFFER를 통해 IP를 받아온 것을 확인한다.
ubuntu@server:~/ansible-project$ ansible -m shell -a "cat /var/log/syslog | grep -i dhcp" tnode1 -u ubuntu --private-key=~/.ssh/sigrid-gashida.pem
tnode1 | CHANGED | rc=0 >>
Feb 3 09:56:14 ip-10-10-1-11 dhclient[296]: Internet Systems Consortium DHCP Client 4.4.1
Feb 3 09:56:14 ip-10-10-1-11 dhclient[296]: For info, please visit https://www.isc.org/software/dhcp/
Feb 3 09:56:14 ip-10-10-1-11 dhclient[296]: DHCPDISCOVER on ens5 to 255.255.255.255 port 67 interval 3 (xid=0x8bb9762d)
Feb 3 09:56:14 ip-10-10-1-11 dhclient[296]: DHCPOFFER of 10.10.1.11 from 10.10.1.1
Feb 3 09:56:14 ip-10-10-1-11 dhclient[296]: DHCPREQUEST for 10.10.1.11 on ens5 to 255.255.255.255 port 67 (xid=0x2d76b98b)
Feb 3 09:56:14 ip-10-10-1-11 dhclient[296]: DHCPACK of 10.10.1.11 from 10.10.1.1 (xid=0x8bb9762d)
Feb 3 09:56:14 ip-10-10-1-11 systemd-networkd[334]: ens5: DHCPv4 address 10.10.1.11/24 via 10.10.1.1
Feb 3 09:56:14 ip-10-10-1-11 kernel: [ 6.711148] audit: type=1400 audit(1706954166.848:8): apparmor="STATUS" operation="profile_load" profile="unconfined" name="/usr/lib/NetworkManager/nm-dhcp-client.action" pid=259 comm="apparmor_parser"
Feb 3 09:56:14 ip-10-10-1-11 kernel: [ 6.712770] audit: type=1400 audit(1706954166.852:9): apparmor="STATUS" operation="profile_load" profile="unconfined" name="/usr/lib/NetworkManager/nm-dhcp-helper" pid=259 comm="apparmor_parser"
Feb 3 14:34:42 ip-10-10-1-11 dhclient[3268]: Internet Systems Consortium DHCP Client 4.4.1
Feb 3 14:34:42 ip-10-10-1-11 dhclient[3268]: For info, please visit https://www.isc.org/software/dhcp/
Feb 3 14:34:42 ip-10-10-1-11 dhclient[3268]: DHCPDISCOVER on ens5 to 255.255.255.255 port 67 interval 3 (xid=0x9a09376)
Feb 3 14:34:42 ip-10-10-1-11 dhclient[3268]: DHCPOFFER of 10.10.1.11 from 10.10.1.1
Feb 3 14:34:42 ip-10-10-1-11 dhclient[3268]: DHCPREQUEST for 10.10.1.11 on ens5 to 255.255.255.255 port 67 (xid=0x7693a009)
Feb 3 14:34:42 ip-10-10-1-11 dhclient[3268]: DHCPACK of 10.10.1.11 from 10.10.1.1 (xid=0x9a09376)
Feb 3 14:35:14 ip-10-10-1-11 dhclient[3376]: Internet Systems Consortium DHCP Client 4.4.1
Feb 3 14:35:14 ip-10-10-1-11 dhclient[3376]: For info, please visit https://www.isc.org/software/dhcp/
Feb 3 14:35:14 ip-10-10-1-11 dhclient[3376]: DHCPREQUEST for 10.10.1.11 on ens5 to 255.255.255.255 port 67 (xid=0xbfa67b4)
Feb 3 14:35:14 ip-10-10-1-11 dhclient[3376]: DHCPACK of 10.10.1.11 from 10.10.1.1 (xid=0xb467fa0b)
Feb 3 14:35:33 ip-10-10-1-11 dhclient[3560]: Internet Systems Consortium DHCP Client 4.4.1
Feb 3 14:35:33 ip-10-10-1-11 dhclient[3560]: For info, please visit https://www.isc.org/software/dhcp/
Feb 3 14:35:33 ip-10-10-1-11 dhclient[3560]: DHCPREQUEST for 10.10.1.11 on ens5 to 255.255.255.255 port 67 (xid=0x2c052202)
Feb 3 14:35:33 ip-10-10-1-11 dhclient[3560]: DHCPACK of 10.10.1.11 from 10.10.1.1 (xid=0x222052c)
Feb 3 14:36:06 ip-10-10-1-11 dhclient[3989]: Internet Systems Consortium DHCP Client 4.4.1
Feb 3 14:36:06 ip-10-10-1-11 dhclient[3989]: For info, please visit https://www.isc.org/software/dhcp/
Feb 3 14:36:06 ip-10-10-1-11 dhclient[3989]: DHCPREQUEST for 10.10.1.11 on ens5 to 255.255.255.255 port 67 (xid=0x357966c1)
Feb 3 14:36:06 ip-10-10-1-11 dhclient[3989]: DHCPACK of 10.10.1.11 from 10.10.1.1 (xid=0xc1667935)
Feb 3 14:46:56 ip-10-10-1-11 python3[4716]: ansible-ansible.legacy.command Invoked with _raw_params=cat /var/log/syslog | grep -i dhcp _uses_shell=True warn=False stdin_add_newline=True strip_empty_ends=True argv=None chdir=None executable=None creates=None removes=None stdin=None

2. 플레이북을 실행한다.

  • tnode2는 실행되지 않아야 하는데 ens7이라고 하는 네트워크 인터페이스를 체크하는데 이는 없기 때문이다.
ubuntu@server:~/ansible-project/chapter_10.1$ ansible-playbook set_ip.yml

PLAY [tnode1] ***********************************************************************************************************************************************************************************************

TASK [Include role in CentOS and RedHat] *
******************************************************************************************************************************************************************
*
skipping: [tnode1]

TASK [Include role in Ubuntu]
*******************************************************************************************************************************************************************************

TASK [myrole.netplan : Copy netplan file] ******************************************************************************************************************************************************************
*
changed: [tnode1]

RUNNING HANDLER [myrole.netplan : Netplan apply] ************************************************************************************************************************************************************
changed: [tnode1]

PLAY [tnode2] ***********************************************************************************************************************************************************************************************

TASK [Gathering Facts] *
*************************************************************************************************************************************************************************************
ok: [tnode2]

TASK [Include role in CentOS and RedHat] *
******************************************************************************************************************************************************************
*
skipping: [tnode2]

TASK [Include role in Ubuntu]
*******************************************************************************************************************************************************************************

TASK [myrole.netplan : Copy netplan file] ******************************************************************************************************************************************************************
*
skipping: [tnode2]

PLAY RECAP **************************************************************************************************************************************************************************************************
tnode1 : ok=3 changed=2 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
tnode2 : ok=1 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0

3. 정상적으로 실행되었는지 확인한다.

  • jinja2 template 파일이 생성되었는지를 확인한다.
ubuntu@server:~/ansible-project/chapter_10.1$ ssh tnode1 ls /etc/netplan
01-netplan-ansible.yaml
50-cloud-init.yaml
  • 파일 내용을 확인한다.
# This is the network config written by 'ansible'
network:
version: 2
ethernets:
ens5:
dhcp4: no
dhcp6: no
addresses: [10.10.1.11/24]
gateway4: 10.10.1.1
nameservers:
addresses: [127.0.0.53]

ubuntu@server:~/ansible-project/chapter_10.1$ ssh tnode1 ip -c route
default via 10.10.1.1 dev ens5 proto static
default via 10.10.1.1 dev ens5 proto dhcp src 10.10.1.11 metric 100
10.10.0.2 via 10.10.1.1 dev ens5 proto dhcp src 10.10.1.11 metric 100
10.10.1.0/24 dev ens5 proto kernel scope link src 10.10.1.11
10.10.1.1 dev ens5 proto dhcp scope link src 10.10.1.11 metric 100

ubuntu@server:~/ansible-project/chapter_10.1$ ssh tnode1 nslookup blog.cloudneta.net
Server: 127.0.0.53
Address: 127.0.0.53#53

Non-authoritative answer:
Name: blog.cloudneta.net
Address: 52.219.58.100
Name: blog.cloudneta.net
Address: 52.219.58.112
Name: blog.cloudneta.net
Address: 52.219.202.20
Name: blog.cloudneta.net
Address: 52.219.56.36
Name: blog.cloudneta.net
Address: 52.219.148.88
Name: blog.cloudneta.net
Address: 52.219.202.0
Name: blog.cloudneta.net
Address: 52.219.58.116
Name: blog.cloudneta.net
Address: 52.219.60.80

호스트명 설정하기

사전 분석

서버 대수가 여러 개이면 호스트명도 관리하기도 한다. dev, staging, 팀별로 구분하거나 하는 일을 진행하는데, 이 때 /etc/hosts에서 hadoop 노드 등 특정한 그룹 간에 통신하기 위해서 dnslookup을 /etc/hosts 파일에서 찾아보도록 구현하기도 한다. 이렇게 묶은 그룹들을 ansible로 한번에 line-in-file 모듈을 이용해서 설정하는 것이 가능하다.

플레이북 개발

  1. ansible.cfg와 inventory 파일을 작성한다.
#
mkdir ~/ansible-project/chapter_10.2
cd ~/ansible-project/chapter_10.2

# ansible.cfg, inventory 파일 작성
cat <<EOT> ansible.cfg
[defaults]
inventory = ./inventory
remote_user = ansible
ask_pass = false
inject_facts_as_vars = false
roles_path = ./roles

[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false
EOT

cat <<EOT> inventory
[tnode]
tnode1
tnode2
tnode3
EOT

2. 변수를 정의할 파일을 생성해서 hosts 파일에 추가할 정보를 사전형 변수로 정의한 다음 이를 반복문으로 사용할 수 있도록 한다.

touch ~/ansible-project/chapter_10.2/vars_hosts_info.yml

tnodes:
- hostname: tnode1
# 첫 번째 tnode1의 주소를 tnode1.local로 쓰고 싶다.
fqdn: tnode1.local
net_ip: 10.10.1.11
- hostname: tnode2
fqdn: tnode2.local
net_ip: 10.10.1.12
- hostname: tnode3
fqdn: tnode3.local
net_ip: 10.10.1.13

3. 메인 플레이북을 작성한다.

  • regexp는 /etc/hosts 파일에서 기존 IP가 있을 경우 기존 값을 수정하거나 (state = present) 또는 삭제를 할 수 있다 (state=absent) 만약 매칭되는 것이 없다면 line 기존 내용을 추가한다.
touch ~/ansible-project/chapter_10.2/set_hostname.yml

---
- hosts: tnode
tasks:
- name: Set hostname from inventory
ansible.builtin.hostname:
name: "{{ inventory_hostname }}"

- hosts: all
vars_files: vars_hosts_info.yml

tasks:
- name: Add host ip to hosts
ansible.builtin.lineinfile:
path: /etc/hosts
line: "{{ item.net_ip }} {{ item.hostname }} {{ item.fqdn }}"
regexp: "^{{ item.net_ip }}" # IP가 /etc/hosts에 있는지 확인
loop: "{{ tnodes }}"

4. 실행하기 전에 기존의 값을 확인하고 그 변화를 확인해본다.

  • 기존 값을 확인해보면 net_ip에 해당하는 내용이 있는 것이 없다.
  • 현재 대상의 /etc/hosts와 앤서블 서버의 /etc/hosts를 비교해보면 tnode fqdn이 없다.
ubuntu@server:~/ansible-project/chapter_10.2$ ansible -m shell -a "cat /etc/hosts" tnode1 -u ubuntu --private-key=~/.ssh/sigrid-gashida.pem
tnode1 | CHANGED | rc=0 >>
127.0.0.1 localhost

# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts

ubuntu@server:~/ansible-project/chapter_10.2$ cat /etc/hosts
127.0.0.1 localhost

# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts
10.10.1.10 server
10.10.1.11 tnode1
10.10.1.12 tnode2
10.10.1.13 tnode3

5. 플레이북을 실행해서 변화를 만들어본다.

ubuntu@server:~/ansible-project/chapter_10.2$ ansible-playbook set_hostname.yml -u ubuntu --private-key=~/.ssh/sigrid-gashida.pem

PLAY [tnode] ************************************************************************************************************************************************************************************************

TASK [Gathering Facts] **************************************************************************************************************************************************************************************
The authenticity of host 'tnode3 (10.10.1.13)' can't be established.
ED25519 key fingerprint is SHA256:TtcQUAShpRhDVvXoIJWeiPXqIbAFHe/EVenwZiF4W7Q.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
ok: [tnode2]
ok: [tnode1]
ok: [tnode3]

TASK [Set hostname from inventory] **************************************************************************************************************************************************************************

ok: [tnode2]
ok: [tnode3]
ok: [tnode1]

PLAY [all] **************************************************************************************************************************************************************************************************

TASK [Gathering Facts] **************************************************************************************************************************************************************************************

ok: [tnode1]
ok: [tnode2]
ok: [tnode3]

TASK [Add host ip to hosts] *********************************************************************************************************************************************************************************
changed: [tnode3] => (item={'hostname': 'tnode1', 'fqdn': 'tnode1.local', 'net_ip': '10.10.1.11'})
changed: [tnode1] => (item={'hostname': 'tnode1', 'fqdn': 'tnode1.local', 'net_ip': '10.10.1.11'})
changed: [tnode2] => (item={'hostname': 'tnode1', 'fqdn': 'tnode1.local', 'net_ip': '10.10.1.11'})
changed: [tnode3] => (item={'hostname': 'tnode2', 'fqdn': 'tnode2.local', 'net_ip': '10.10.1.12'})
changed: [tnode1] => (item={'hostname': 'tnode2', 'fqdn': 'tnode2.local', 'net_ip': '10.10.1.12'})
changed: [tnode2] => (item={'hostname': 'tnode2', 'fqdn': 'tnode2.local', 'net_ip': '10.10.1.12'})
changed: [tnode3] => (item={'hostname': 'tnode3', 'fqdn': 'tnode3.local', 'net_ip': '10.10.1.13'})
changed: [tnode1] => (item={'hostname': 'tnode3', 'fqdn': 'tnode3.local', 'net_ip': '10.10.1.13'})
changed: [tnode2] => (item={'hostname': 'tnode3', 'fqdn': 'tnode3.local', 'net_ip': '10.10.1.13'})

PLAY RECAP
**************************************************************************************************************************************************************************************************
tnode1 : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
tnode2 : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
tnode3 : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
  • 아래와 같이 확인해서 신규 IP가 추가된 사항을 확인해본다.
ubuntu@server:~/ansible-project/chapter_10.2$ ansible -m shell -a "cat /etc/hosts | grep tnode" tnode -u ubuntu --private-key=~/.ssh/sigrid-gashida.pem
tnode2 | CHANGED | rc=0 >>
10.10.1.11 tnode1 tnode1.local
10.10.1.12 tnode2 tnode2.local
10.10.1.13 tnode3 tnode3.local
tnode1 | CHANGED | rc=0 >>
10.10.1.11 tnode1 tnode1.local
10.10.1.12 tnode2 tnode2.local
10.10.1.13 tnode3 tnode3.local
tnode3 | CHANGED | rc=0 >>
10.10.1.11 tnode1 tnode1.local
10.10.1.12 tnode2 tnode2.local
10.10.1.13 tnode3 tnode3.local
  • tnode1에서 다른 노드 통신 가능 여부를 확인해본다.
ubuntu@server:~/ansible-project/chapter_10.2$ ssh tnode1 ping -c 1 tnode2
PING tnode2 (10.10.1.12) 56(84) bytes of data.
64 bytes from tnode2 (10.10.1.12): icmp_seq=1 ttl=64 time=0.902 ms

--- tnode2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.902/0.902/0.902/0.000 ms
ubuntu@server:~/ansible-project/chapter_10.2$ ssh tnode1 ping -c 1 tnode3.local
PING tnode3 (10.10.1.13) 56(84) bytes of data.
64 bytes from tnode3 (10.10.1.13): icmp_seq=1 ttl=64 time=0.907 ms

--- tnode3 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.907/0.907/0.907/0.000 ms

NFS 서버를 설치하고 스토리지를 마운트해본다

사전 분석

tnode1에는 NFS 서버를 만들고 나머지 두 노드는 NFS 를 바라보고 해당 스토리지를 마운트한다

서버가 반드시 구성이 되어야만 클라이언트가 마운트되므로 서버 → 클라이언트의 순서도 중요하다.

이 전체 과정을 롤로 구성해서 플레이북을 재사용해본다.

플레이북 설계

  • nfs_server와 nfs_client를 만드는 롤이 있다.
  • set_nfs_storage.yml이라는 플레이북이 위의 롤을 실행한다.
  • 서버의 롤은 tasks를 통해 설치 → 디렉토리 구성 → lineinline or jinja2를 진행하고 → firewalld를 수행한다. 핸들러를 실행한다.
  • 이 이후에 클라이언트를 설정한다. NFS package를 설치하고 mount directory를 만들어서 NFS를 mount한다.

NFS 서버 플레이북 개발

  1. 프로젝트 디렉터리 생성 등 기본적인 작업
#
mkdir ~/ansible-project/chapter_10.3
cd ~/ansible-project/chapter_10.3
# ansible.cfg, inventory 파일 작성
cat <<EOT> ansible.cfg
[defaults]
inventory = ./inventory
remote_user = ansible
ask_pass = false
inject_facts_as_vars = false
roles_path = ./roles
[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false
EOT
cat <<EOT> inventory
[nfs_server] # 그룹 이름으로 구분하기
tnode1
[nfs_client]
tnode2
tnode3
EOT

2. 롤을 생성한다.

ubuntu@server:~/ansible-project/chapter_10.3$ #
ansible-galaxy role init --init-path ./roles myrole.nfs_server
ansible-galaxy role init --init-path ./roles myrole.nfs_client
# 확인
ansible-galaxy role list
tree roles -L 2
- Role myrole.nfs_server was created successfully
- Role myrole.nfs_client was created successfully
# /home/ubuntu/ansible-project/chapter_10.3/roles
- myrole.nfs_server, (unknown version)
- myrole.nfs_client, (unknown version)
roles
├── myrole.nfs_client
│ ├── README.md
│ ├── defaults
│ ├── files
│ ├── handlers
│ ├── meta
│ ├── tasks
│ ├── templates
│ ├── tests
│ └── vars
└── myrole.nfs_server
├── README.md
├── defaults
├── files
├── handlers
├── meta
├── tasks
├── templates
├── tests
└── vars
18 directories, 2 files

3. myrole.nfs_server에 변수 파일을 작성해서 설치할 NFS 서버 관련 설치할 패키지를 지정한다.

# chapter_10.3/roles/myrole.nfs_server/vars/main.yml
---
# vars file for myrole.nfs-server
nfs_packages:
- nfs-kernel-server
# - nfs-kernerl-server-2

참고: ubuntu 22.04에 NFS를 설치하는 과정을 이해해본다.

https://www.server-world.info/en/note?os=Ubuntu_22.04&p=nfs&f=1

https://www.server-world.info/en/note?os=Ubuntu_22.04&p=nfs&f=2

# 설치
apt -y install nfs-kernel-server
# 접속 가능 클라이언트 정보 설정 IP 기반 대역폭은 어떻게 구현할거야 등등
echo "/home/nfsshare 10.10.0.0/16(rw,no_root_squash)" >> /etc/exports
#
mkdir /home/nfsshare
sudo systemctl restart nfs-server && sudo systemctl enable nfs-server
# 서비스 포트 정보 확인 : TCP 2049
sudo ss -tnlp | grep 2049
# 툴 설치
apt -y install nfs-common
# NFS Client 마운트
mount -t nfs 10.10.13.146:/home/nfsshare /mnt
# 확인
df -hT --type nfs

5. myrole.nfs_server에 태스크 파일을 작성한다.

---
# tasks file for myrole.nfs_server
- name: Install NFS packages
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop: "{{ nfs_packages }}"
- name: Create NFS export directory
ansible.builtin.file:
path: "{{ share_path }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Configure NFS exports
ansible.builtin.lineinfile:
path: "/etc/exports"
line: "{{ share_path }} *(rw,sync)" # 모두 다 접근 가능. rw, sync
regexp: "^{{ share_path }}"
state: present
create: true
notify: Restart NFS Service

6. myrole.nfs_server에 핸들러 파일을 작성한다.

# chapter_10.3/roles/myrole.nfs_server/handlers/main.yml
---
# handlers file for myrole.nfs_server
- name: Restart NFS Service
ansible.builtin.service:
name: nfs-server
state: restarted

NFS 클라이언트 플레이북 개발

  1. myrole.nfs_client에 변수 파일을 작성해서 설치할 NFS 패키지를 명시한다.
# cd chapter_10.3/roles/myrole.nfs_client/vars/main.yml
---
# vars file for myrole.nfs_client
apt_nfs_packages: # 우분투 계열 패키지 설치
- nfs-common
dnf_nfs_packages: # CentOS 계열 패키지 설치
- nfs-utils

2. ansible-galaxy 컬렉션에서 posix 모듈을 설치한다.

  • ansible.posix 컬렉션은 Ansible을 사용하여 POSIX 호환 시스템(리눅스, 유닉스 등)을 관리할 때 필요한 모듈과 플러그인들을 제공합니다. 이 컬렉션에는 시스템 설정, 파일 관리, 서비스 관리와 같은 다양한 작업을 수행할 수 있는 Ansible 모듈들이 포함되어 있습니다.
ansible-galaxy collection install ansible.posix
  • 만약 설치 중에 다음과 같은 오류가 발생하는 사람들 주목!
ubuntu@server:~/ansible-project/chapter_10.3$ ansible-galaxy collection install ansible.posix -vvv
ansible-galaxy [core 2.12.0]
config file = /home/ubuntu/ansible-project/chapter_10.3/ansible.cfg
configured module search path = ['/home/ubuntu/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3/dist-packages/ansible
ansible collection location = /home/ubuntu/.ansible/collections:/usr/share/ansible/collections
executable location = /usr/bin/ansible-galaxy
python version = 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0]
jinja version = 3.0.3
libyaml = True
Using /home/ubuntu/ansible-project/chapter_10.3/ansible.cfg as config file
Starting galaxy collection install process
Process install dependency map
ERROR! Unexpected Exception, this is probably a bug: CollectionDependencyProvider.find_matches() got an unexpected keyword argument 'identifier'
the full traceback was:
Traceback (most recent call last):
File "/usr/bin/ansible-galaxy", line 128, in <module>
exit_code = cli.run()
File "/usr/lib/python3/dist-packages/ansible/cli/galaxy.py", line 567, in run
return context.CLIARGS['func']()
File "/usr/lib/python3/dist-packages/ansible/cli/galaxy.py", line 86, in method_wrapper
return wrapped_method(*args, **kwargs)
File "/usr/lib/python3/dist-packages/ansible/cli/galaxy.py", line 1201, in execute_install
self._execute_install_collection(
File "/usr/lib/python3/dist-packages/ansible/cli/galaxy.py", line 1228, in _execute_install_collection
install_collections(
File "/usr/lib/python3/dist-packages/ansible/galaxy/collection/__init__.py", line 513, in install_collections
dependency_map = _resolve_depenency_map(
File "/usr/lib/python3/dist-packages/ansible/galaxy/collection/__init__.py", line 1327, in _resolve_depenency_map
return collection_dep_resolver.resolve(
File "/usr/lib/python3/dist-packages/resolvelib/resolvers.py", line 481, in resolve
state = resolution.resolve(requirements, max_rounds=max_rounds)
File "/usr/lib/python3/dist-packages/resolvelib/resolvers.py", line 348, in resolve
self._add_to_criteria(self.state.criteria, r, parent=None)
File "/usr/lib/python3/dist-packages/resolvelib/resolvers.py", line 147, in _add_to_criteria
matches = self._p.find_matches(
TypeError: CollectionDependencyProvider.find_matches() got an unexpected keyword argument 'identifier'
  • 이 때 해결책은 다음과 같다. GitHub
sudo apt install -y pip;
pip install -Iv 'resolvelib<0.6.0';
ansible-galaxy collection install community.windows;
# 잘 설치가 진행된다.
ubuntu@server:~/ansible-project/chapter_10.3$ ansible-galaxy collection install ansible.posix -vvv
ansible-galaxy [core 2.12.0]
config file = /home/ubuntu/ansible-project/chapter_10.3/ansible.cfg
configured module search path = ['/home/ubuntu/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
ansible python module location = /usr/lib/python3/dist-packages/ansible
ansible collection location = /home/ubuntu/.ansible/collections:/usr/share/ansible/collections
executable location = /usr/bin/ansible-galaxy
python version = 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0]
jinja version = 3.0.3
libyaml = True
Using /home/ubuntu/ansible-project/chapter_10.3/ansible.cfg as config file
Starting galaxy collection install process
Found installed collection ansible.windows:2.2.0 at '/home/ubuntu/.ansible/collections/ansible_collections/ansible/windows'
Found installed collection community.windows:2.1.0 at '/home/ubuntu/.ansible/collections/ansible_collections/community/windows'
Process install dependency map
Opened /home/ubuntu/.ansible/galaxy_token
Starting collection install process
Downloading https://galaxy.ansible.com/api/v3/plugin/ansible/content/published/collections/artifacts/ansible-posix-1.5.4.tar.gz to /home/ubuntu/.ansible/tmp/ansible-local-62058x7mvkjd/tmp0v58qupr/ansible-posix-1.5.4-5o5glxcc
Collection 'ansible.posix:1.5.4' obtained from server default https://galaxy.ansible.com/api/
Installing 'ansible.posix:1.5.4' to '/home/ubuntu/.ansible/collections/ansible_collections/ansible/posix'
ansible.posix:1.5.4 was installed successfully

2. myrole.nfs_client에 태스크 파일을 작성한다.

  • 이름 (name): 이 태스크의 이름은 “Mount NFS”입니다. 이는 태스크가 수행하는 작업을 간결하게 요약합니다. 즉, NFS 공유를 마운트하는 것입니다.
  • 모듈 (ansible.posix.mount): 이 태스크는 ansible.posix.mount 모듈을 사용합니다. 이 모듈은 파일 시스템을 마운트하고 관리하는 데 사용됩니다. 여기서는 NFS와 같은 네트워크 파일 시스템을 마운트하는 데 사용됩니다.
  • 소스 (src): 마운트할 NFS 공유의 위치입니다. 여기서 **"{{ share_server }}:{{ share_path }}"**는 앤서블 변수를 사용하여 동적으로 NFS 서버의 주소와 공유 경로를 지정합니다. 예를 들어, share_server 변수에는 NFS 서버의 호스트 이름 또는 IP 주소가, share_path 변수에는 해당 서버에서 공유된 디렉토리의 경로가 포함됩니다.
  • 경로 (path): NFS 공유가 로컬 시스템에 마운트될 경로입니다. "{{ mount_path }}" 변수를 통해 이 경로를 지정합니다. 이는 클라이언트 시스템에서 NFS 공유에 액세스하기 위한 지점을 정의합니다.
  • 옵션 (opts): 마운트 옵션을 지정합니다. 여기서 **rw,sync**는 공유가 읽기 및 쓰기 모두 가능하며(sync), 데이터를 동기화하는 방식으로 마운트되어야 함을 나타냅니다.
  • 상태 (state): 이 태스크의 목표 상태를 **mounted**로 설정합니다. 이는 지정된 경로에 NFS 공유가 마운트되어 있어야 함을 의미합니다. 만약 공유가 마운트되어 있지 않다면, 앤서블은 이를 마운트하려고 시도할 것입니다.
  • 파일 시스템 유형 (fstype): 마운트할 파일 시스템의 유형으로, 여기서는 **nfs**로 지정됩니다. 이는 NFS 프로토콜을 사용하여 네트워크를 통해 파일 시스템을 공유하고 액세스한다는 것을 나타냅니다.
# cd chapter_10.3/roles/myrole.nfs_client/tasks/main.yml
---
# tasks file for myrole.nfs-client
- name: Install NFS Packages on Ubuntu
ansible.builtin.apt:
name: "{{ item }}"
update_cache: true
state: present
loop: "{{ apt_nfs_packages }}"
when: ansible_facts.distribution == "Ubuntu"
- name: Install NFS Packages on RedHat
ansible.builtin.dnf:
name: "{{ item }}"
state: present
loop: "{{ dnf_nfs_packages }}"
when: ansible_facts.distribution == "RedHat"
- name: Create Mount Directory
ansible.builtin.file:
path: "{{ mount_path }}"
state: directory
- name: Mount NFS
ansible.posix.mount:
src: "{{ share_server }}:{{ share_path }}"
path: "{{ mount_path }}"
opts: rw,sync
state: mounted
fstype: nfs

3. 메인 플레이북에서 사용할 변수를 정의한다. 이 때 롤에 정의하지 않은 이유는 각각의 롤이 결국 하나의 단일한 상수 값을 사용하기 때문이다.

# touch ~/ansible-project/chapter_10.3/vars_share_path.yml

---

share_server: tnode1
share_path: /mnt/nfs_shares
mount_path: /mnt/nfs_data

4. 메인 플레이북을 작성해본다.

# touch ~/ansible-project/chapter_10.3/**set_nfs_storage.yml
---- hosts: nfs_server
vars_files: vars_share_path.yml
roles:
- role: myrole.nfs_server
- hosts: nfs_client
vars_files: vars_share_path.yml
roles:
- role: myrole.nfs_client**
  1. 플레이북을 실행하기 전에 스토리지 마운트 상태를 확인해본다.
ubuntu@server:~/ansible-project/chapter_10.3$ ssh tnode2 df -h --type nfs4
df: no file systems processed
ubuntu@server:~/ansible-project/chapter_10.3$ ssh tnode3 df -h --type ext4
Filesystem Size Used Avail Use% Mounted on
/dev/root 29G 1.8G 28G 7% /
  1. 플레이북을 실행해본다.
ansible-playbook set_nfs_storage.yml -u ubuntu --private-key=~/.ssh/sigrid-gashida.pem
PLAY [nfs_server] *******************************************************************************************************************************************************************************************TASK [Gathering Facts] **************************************************************************************************************************************************************************************
ok: [tnode1]
TASK [myrole.nfs_server : Install NFS packages] *************************************************************************************************************************************************************
changed: [tnode1] => (item=nfs-kernel-server)
TASK [myrole.nfs_server : Create NFS export directory] ******************************************************************************************************************************************************
changed: [tnode1]
TASK [myrole.nfs_server : Configure NFS exports] ************************************************************************************************************************************************************
changed: [tnode1]
RUNNING HANDLER [myrole.nfs_server : Restart NFS Service] ***************************************************************************************************************************************************
changed: [tnode1]
PLAY [nfs_client] *******************************************************************************************************************************************************************************************TASK [Gathering Facts] **************************************************************************************************************************************************************************************
ok: [tnode3]
ok: [tnode2]
TASK [myrole.nfs_client : Install NFS Packages on Ubuntu] ***************************************************************************************************************************************************
changed: [tnode3] => (item=nfs-common)
changed: [tnode2] => (item=nfs-common)
TASK [myrole.nfs_client : Install NFS Packages on Rhel] *****************************************************************************************************************************************************
skipping: [tnode2] => (item=nfs-utils)
skipping: [tnode3] => (item=nfs-utils)
TASK [myrole.nfs_client : Create Mount Directory] ***********************************************************************************************************************************************************
changed: [tnode2]
changed: [tnode3]
TASK [myrole.nfs_client : Mount NFS] ************************************************************************************************************************************************************************
changed: [tnode3]
changed: [tnode2]
PLAY RECAP **************************************************************************************************************************************************************************************************
tnode1 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
tnode2 : ok=4 changed=3 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
tnode3 : ok=4 changed=3 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
  1. 플레이북을 실행하고 나서 정상적으로 NFS 스토리지가 마운트 되었는지를 확인해본다.
ubuntu@server:~/ansible-project/chapter_10.3$ ssh tnode2 df -h --type nfs4
Filesystem Size Used Avail Use% Mounted on
tnode1:/mnt/nfs_shares 29G 1.9G 28G 7% /mnt/nfs_data
ubuntu@server:~/ansible-project/chapter_10.3$ ssh tnode3 df -h --type nfs4
Filesystem Size Used Avail Use% Mounted on
tnode1:/mnt/nfs_shares 29G 1.9G 28G 7% /mnt/nfs_data
ubuntu@server:~/ansible-project/chapter_10.3$ ssh tnode1 systemctl status nfs-server
● nfs-server.service - NFS server and services
Loaded: loaded (/lib/systemd/system/nfs-server.service; enabled; vendor preset: enabled)
Active: active (exited) since Sun 2024-02-04 01:23:56 KST; 13min ago
Process: 7758 ExecStartPre=/usr/sbin/exportfs -r (code=exited, status=0/SUCCESS)
Process: 7759 ExecStart=/usr/sbin/rpc.nfsd (code=exited, status=0/SUCCESS)
Main PID: 7759 (code=exited, status=0/SUCCESS)
CPU: 5ms
Feb 04 01:23:56 tnode1 systemd[1]: Starting NFS server and services...
Feb 04 01:23:56 tnode1 exportfs[7758]: exportfs: /etc/exports [1]: Neither 'subtree_check' or 'no_subtree_check' specified for export "*:/mnt/nfs_shares".
Feb 04 01:23:56 tnode1 exportfs[7758]: Assuming default behaviour ('no_subtree_check').
Feb 04 01:23:56 tnode1 exportfs[7758]: NOTE: this default has changed since nfs-utils version 1.0.x
Feb 04 01:23:56 tnode1 systemd[1]: Finished NFS server and services.
ubuntu@server:~/ansible-project/chapter_10.3$ ssh tnode1 sudo exportfs -s
/mnt/nfs_shares *(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,root_squash,no_all_squash)
ubuntu@server:~$ ssh tnode1
ubuntu@tnode1:~$ for i in {1..10}; do sudo touch /mnt/nfs_shares/deleteme.$i; done;
ubuntu@server:~/ansible-project/chapter_10.3$ ssh tnode2
ls /ubuntu@tnode2:~$ ls /mnt/nfs_data
deleteme.1 deleteme.10 deleteme.2 deleteme.3 deleteme.4 deleteme.5 deleteme.6 deleteme.7 deleteme.8 deleteme.9

DB 애플리케이션을 설치한다

방법 찾기

  • 앤서블 갤럭시에서 MySQL 설치하는 롤을 찾아보고, 해당 롤을 이용하여 테스트를 진행할 수 있다. 이 때 geerlingguy.mysql을 이용하는 것이 좋다.

사전 분석

  • MySQL을 tnode2에 설치한다.
  • 앤서블 갤럭시에서 우분투에 설치할 수 있는 MySQL 롤을 검색하여 해당 롤을 이용한다.

플레이북 설계

  • ansible.cfg
  • inventory
  • db (tnode2)
  • tnode 정의된 설정
  • install_mysql.yaml
  • hosts: db
  • roles: geelingguy.mysql
  • Roles
  • 앤서블 갤럭시에서 가져온 geerlingguy.mysql의 Role을 이용한다.

플레이북 개발

  1. 프로젝트 디렉터리를 생성하고 필요한 설정 파일을 만들어본다.
#
mkdir ~/ansible-project/chapter_10.4
cd ~/ansible-project/chapter_10.4
# ansible.cfg, inventory 파일 작성
cat <<EOT> ansible.cfg
[defaults]
inventory = ./inventory
remote_user = ansible
# facts 관련된 내용은 주석 처리 해야 하는데 그 이유는 오래된 Facts 표기법을 사용하기 때문이다. 따라서 사용할 롤에 대하여 ansible.cfg를 꼭 확인해야 한다.
# ansible 표기한 구 버전의 경우에는 Ubuntu 분류가 없어서 Ubuntu를 Debian에 속하게 된다.
ask_pass = false
roles_path = ./roles
[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false
EOT
cat <<EOT> inventory
[db]
tnode2
[tnode]
tnode1
tnode2
tnode3
EOT
  1. 검색한 MySQL Role을 설치한다.
ubuntu@server:~/ansible-project/chapter_10.4$ #
ansible-galaxy role install -p ./roles geerlingguy.mysql
# 확인
ansible-galaxy role list
tree roles -L 3
Starting galaxy role install process
- downloading role 'mysql', owned by geerlingguy
- downloading role from <https://github.com/geerlingguy/ansible-role-mysql/archive/4.3.4.tar.gz>
- extracting geerlingguy.mysql to /home/ubuntu/ansible-project/chapter_10.4/roles/geerlingguy.mysql
- geerlingguy.mysql (4.3.4) was installed successfully
# /home/ubuntu/ansible-project/chapter_10.4/roles
- geerlingguy.mysql, 4.3.4
roles
└── geerlingguy.mysql
├── LICENSE
├── README.md
├── defaults
│ └── main.yml
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── molecule
│ └── default
├── tasks
│ ├── configure.yml
│ ├── databases.yml
│ ├── main.yml
│ ├── replication.yml
│ ├── secure-installation.yml
│ ├── setup-Archlinux.yml
│ ├── setup-Debian.yml
│ ├── setup-RedHat.yml
│ ├── users.yml
│ └── variables.yml
├── templates
│ ├── my.cnf.j2
│ ├── root-my.cnf.j2
│ └── user-my.cnf.j2
└── vars
├── Archlinux.yml
├── Debian-10.yml
├── Debian-11.yml
├── Debian-12.yml
├── Debian.yml
├── RedHat-7.yml
├── RedHat-8.yml
└── RedHat-9.yml
9 directories, 26 files
  1. 롤 테스크 정보를 확인한다.
  • os_family 에 따라 mysql 설치 후 환경 설정 태스크들을 차례대로 호출 ⇒ 나머지 yml 파일들도 한번 내용 확인해보시기 바랍니다.
# chapter_10.4/roles/geerlingguy.mysql/**tasks/main.yml**
---
# Variable configuration.
- ansible.builtin.include_tasks: variables.yml
# Setup/install tasks.
- ansible.builtin.include_tasks: setup-RedHat.yml
when: ansible_os_family == 'RedHat'
- ansible.builtin.include_tasks: setup-Debian.yml
when: ansible_os_family == 'Debian'
- ansible.builtin.include_tasks: setup-Archlinux.yml
when: ansible_os_family == 'Archlinux'
- name: Check if MySQL packages were installed.
ansible.builtin.set_fact:
mysql_install_packages: "{{ (rh_mysql_install_packages is defined and rh_mysql_install_packages.changed)
or (deb_mysql_install_packages is defined and deb_mysql_install_packages.changed)
or (arch_mysql_install_packages is defined and arch_mysql_install_packages.changed) }}"
# Configure MySQL.
- ansible.builtin.include_tasks: configure.yml
- ansible.builtin.include_tasks: secure-installation.yml
- ansible.builtin.include_tasks: databases.yml
- ansible.builtin.include_tasks: users.yml
- ansible.builtin.include_tasks: replication.yml
  1. 플레이북을 작성해본다.
# touch ~/ansible-project/chapter_10.4/install_mysql.yml
---
- hosts: db
roles:
- role: geerlingguy.mysql
  1. 플레이북을 실행해본다.
ubuntu@server:~/ansible-project/chapter_10.4$ ansible-playbook --check install_mysql.yml -u ubuntu --private-key=~/.ssh/sigrid-gashida.pem
PLAY [db] ***************************************************************************************************************************************************************************************************TASK [Gathering Facts] **************************************************************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : ansible.builtin.include_tasks] ****************************************************************************************************************************************************
included: /home/ubuntu/ansible-project/chapter_10.4/roles/geerlingguy.mysql/tasks/variables.yml for tnode2
TASK [geerlingguy.mysql : Include OS-specific variables.] ***************************************************************************************************************************************************
ok: [tnode2] => (item=/home/ubuntu/ansible-project/chapter_10.4/roles/geerlingguy.mysql/vars/Debian.yml)
TASK [geerlingguy.mysql : Define mysql_packages.] ***********************************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Define mysql_daemon.] *************************************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Define mysql_slow_query_log_file.] ************************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Define mysql_log_error.] **********************************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Define mysql_syslog_tag.] *********************************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Define mysql_pid_file.] ***********************************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Define mysql_config_file.] ********************************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Define mysql_config_include_dir.] *************************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Define mysql_socket.] *************************************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Define mysql_supports_innodb_large_prefix.] ***************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : ansible.builtin.include_tasks] ****************************************************************************************************************************************************
skipping: [tnode2]
TASK [geerlingguy.mysql : ansible.builtin.include_tasks] ****************************************************************************************************************************************************
included: /home/ubuntu/ansible-project/chapter_10.4/roles/geerlingguy.mysql/tasks/setup-Debian.yml for tnode2
TASK [geerlingguy.mysql : Check if MySQL is already installed.] *********************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Update apt cache if MySQL is not yet installed.] **********************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Ensure MySQL Python libraries are installed.] *************************************************************************************************************************************
changed: [tnode2]
TASK [geerlingguy.mysql : Ensure MySQL packages are installed.] *********************************************************************************************************************************************
changed: [tnode2]
TASK [geerlingguy.mysql : Ensure MySQL is stopped after initial install.] ***********************************************************************************************************************************
fatal: [tnode2]: FAILED! => {"changed": false, "msg": "Could not find the requested service mysql: host"}
PLAY RECAP **************************************************************************************************************************************************************************************************
tnode2 : ok=18 changed=2 unreachable=0 failed=1 skipped=1 rescued=0 ignored=0
ubuntu@server:~/ansible-project/chapter_10.4$ ansible-playbook install_mysql.yml -u ubuntu --private-key=~/.ssh/sigrid-gashida.pemPLAY [db] ***************************************************************************************************************************************************************************************************TASK [Gathering Facts] **************************************************************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : ansible.builtin.include_tasks] ****************************************************************************************************************************************************
included: /home/ubuntu/ansible-project/chapter_10.4/roles/geerlingguy.mysql/tasks/variables.yml for tnode2
TASK [geerlingguy.mysql : Include OS-specific variables.] ***************************************************************************************************************************************************
ok: [tnode2] => (item=/home/ubuntu/ansible-project/chapter_10.4/roles/geerlingguy.mysql/vars/Debian.yml)
TASK [geerlingguy.mysql : Define mysql_packages.] ***********************************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Define mysql_daemon.] *************************************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Define mysql_slow_query_log_file.] ************************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Define mysql_log_error.] **********************************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Define mysql_syslog_tag.] *********************************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Define mysql_pid_file.] ***********************************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Define mysql_config_file.] ********************************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Define mysql_config_include_dir.] *************************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Define mysql_socket.] *************************************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Define mysql_supports_innodb_large_prefix.] ***************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : ansible.builtin.include_tasks] ****************************************************************************************************************************************************
skipping: [tnode2]
TASK [geerlingguy.mysql : ansible.builtin.include_tasks] ****************************************************************************************************************************************************
included: /home/ubuntu/ansible-project/chapter_10.4/roles/geerlingguy.mysql/tasks/setup-Debian.yml for tnode2
TASK [geerlingguy.mysql : Check if MySQL is already installed.] *********************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Update apt cache if MySQL is not yet installed.] **********************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Ensure MySQL Python libraries are installed.] *************************************************************************************************************************************
changed: [tnode2]
TASK [geerlingguy.mysql : Ensure MySQL packages are installed.] *********************************************************************************************************************************************
changed: [tnode2]
TASK [geerlingguy.mysql : Ensure MySQL is stopped after initial install.] ***********************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Delete innodb log files created by apt package after initial install.] ************************************************************************************************************
skipping: [tnode2] => (item=ib_logfile0)
skipping: [tnode2] => (item=ib_logfile1)
TASK [geerlingguy.mysql : ansible.builtin.include_tasks] ****************************************************************************************************************************************************
skipping: [tnode2]
TASK [geerlingguy.mysql : Check if MySQL packages were installed.] ******************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : ansible.builtin.include_tasks] ****************************************************************************************************************************************************
included: /home/ubuntu/ansible-project/chapter_10.4/roles/geerlingguy.mysql/tasks/configure.yml for tnode2
TASK [geerlingguy.mysql : Get MySQL version.] ***************************************************************************************************************************************************************
ok: [tnode2]
TASK [geerlingguy.mysql : Copy my.cnf global MySQL configuration.] ******************************************************************************************************************************************
changed: [tnode2]
TASK [geerlingguy.mysql : Verify mysql include directory exists.] *******************************************************************************************************************************************
skipping: [tnode2]
TASK [geerlingguy.mysql : Copy my.cnf override files into include directory.] *******************************************************************************************************************************TASK [geerlingguy.mysql : Create slow query log file (if configured).] **************************************************************************************************************************************
skipping: [tnode2]
TASK [geerlingguy.mysql : Create datadir if it does not exist] **********************************************************************************************************************************************
changed: [tnode2]
TASK [geerlingguy.mysql : Set ownership on slow query log file (if configured).] ****************************************************************************************************************************
skipping: [tnode2]
TASK [geerlingguy.mysql : Create error log file (if configured).] *******************************************************************************************************************************************
skipping: [tnode2]
TASK [geerlingguy.mysql : Set ownership on error log file (if configured).] *********************************************************************************************************************************
skipping: [tnode2]
TASK [geerlingguy.mysql : Ensure MySQL is started and enabled on boot.] *************************************************************************************************************************************
changed: [tnode2]
TASK [geerlingguy.mysql : ansible.builtin.include_tasks] ****************************************************************************************************************************************************
fatal: [tnode2]: FAILED! => {"reason": "couldn't resolve module/action 'mysql_user'. This often indicates a misspelling, missing collection, or incorrect module path.\\n\\nThe error appears to be in '/home/ubuntu/ansible-project/chapter_10.4/roles/geerlingguy.mysql/tasks/secure-installation.yml': line 2, column 3, but may\\nbe elsewhere in the file depending on the exact syntax problem.\\n\\nThe offending line appears to be:\\n\\n---\\n- name: Ensure default user is present.\\n ^ here\\n"}
RUNNING HANDLER [geerlingguy.mysql : restart mysql] *********************************************************************************************************************************************************PLAY RECAP **************************************************************************************************************************************************************************************************
tnode2 : ok=25 changed=5 unreachable=0 failed=1 skipped=9 rescued=0 ignored=0
  1. 플레이북 실행 내용을 검증해본다.
# 확인
ubuntu@server:~/ansible-project/chapter_10.4$ ssh tnode2 systemctl status mysql
● mysql.service - MySQL Community Server
Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled)
Active: active (running) since Sun 2024-02-04 01:49:17 KST; 44s ago
Process: 7219 ExecStartPre=/usr/share/mysql/mysql-systemd-start pre (code=exited, status=0/SUCCESS)
Main PID: 7227 (mysqld)
Status: "Server is operational"
Tasks: 37 (limit: 4598)
Memory: 345.4M
CPU: 1.293s
CGroup: /system.slice/mysql.service
└─7227 /usr/sbin/mysqld
Feb 04 01:49:17 tnode2 systemd[1]: Starting MySQL Community Server...
Feb 04 01:49:17 tnode2 systemd[1]: Started MySQL Community Server.
#
ssh tnode2
ubuntu@tnode2:~$ sudo mysql -u root -e "show databases;"
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
ubuntu@tnode2:~$ sudo mysql -u root mysql
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Welcome to the MySQL monitor. Commands end with ; or \\g.
Your MySQL connection id is 9
Server version: 8.0.36-0ubuntu0.22.04.1 (Ubuntu)
Copyright (c) 2000, 2024, Oracle and/or its affiliates.Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\\h' for help. Type '\\c' to clear the current input statement.mysql> status
--------------
mysql Ver 8.0.36-0ubuntu0.22.04.1 for Linux on x86_64 ((Ubuntu))
Connection id: 9
Current database: mysql
Current user: root@localhost
SSL: Not in use
Current pager: stdout
Using outfile: ''
Using delimiter: ;
Server version: 8.0.36-0ubuntu0.22.04.1 (Ubuntu)
Protocol version: 10
Connection: Localhost via UNIX socket
Server characterset: utf8mb4
Db characterset: utf8mb4
Client characterset: utf8mb4
Conn. characterset: utf8mb4
UNIX socket: /var/run/mysqld/mysqld.sock
Binary data as: Hexadecimal
Uptime: 1 min 8 sec
Threads: 1 Questions: 47 Slow queries: 0 Opens: 1389 Flush tables: 3 Open tables: 49 Queries per second avg: 0.691
--------------
mysql> exit
Bye
ubuntu@tnode2:~$ exit
logout
Connection to tnode2 closed.

--

--

No responses yet