k8s Deploy Study 2주차 Ansible 기초
가시다님이 운영하시는 k8s Deploy 2주차 내용을 정리한 게시글 입니다.
Ansible 기초에 대한 설명은 다른 글에도 많은 것 같아, 이번 게시물에서는 스터디에서 다루는 추가과제 내용을 위주로 구성하려고합니다.
1. 실습 환경
저는 로컬 서버에 Proxmox를 이용하여 실습환경을 구축해서 가시다님이 설정한 환경 설정과 다르게 설정을 진행 할 예정입니다.
proxmox를 이용한 Terraform 코드를 이용하여 서버를 구성 했습니다.
이번 실습에서는 기존에 제가 Home-lab k8s cluster 구성 시 사용했던 코드를 사용해서 구성하려고합니다.
실습 환경 구성
가상환경은 다음과 같이 구성됩니다.
| Node | OS | Kernel | vCPU | Memory | Disk | NIC2 IP | 관리자 계정 | |
|---|---|---|---|---|---|---|---|---|
| k8s-master | Ubuntu 24.04 | 6.8.0 | 4 | 8GB | 80GB | 192.168.0.31 | key인증 | |
| k8s-worker-1 | Ubuntu 24.04 | 상동 | 8 | 18 GB | 260GB | 192.168.0.32 | key인증 | |
| k8s-worker-2 | Ubuntu 24.04 | 상동 | 8 | 18 GB | 260GB | 192.168.0.33 | key인증 | |
| k8s-worker-3 | Ubuntu 24.04 | 6.8.0 | 8 | 18 GB | 260GB | 192.168.0.34 | key인증 |
git clone https://github.com/yeonjukim164/proxmox.git
tofu init
tofu plan
tofu apply
vagrant init script ansible 적용
가시다님이 작성한 코드는 Vagrant에 적합한 코드라 저는 Terraform으로 배포하고 ansible로 자동으로 설정해보려고 합니다.
# host.ini 파일
[k8s_cluster]
k8s-master ansible_host=192.168.0.31
k8s-worker-1 ansible_host=192.168.0.32
k8s-worker-2 ansible_host=192.168.0.33
k8s-worker-3 ansible_host=192.168.0.34
[k8s_cluster:vars]
ansible_user=kubeadm
ansible_password=kubeadm
ansible_ssh_common_args='-o StrictHostKeyChecking=no'
# playbook.yaml
- name: Initial Configuration for K8s Cluster
hosts: k8s_cluster
become: yes
vars:
target_password: "qwe123" # 보안 취약: 실제 운영 환경에서는 절대 평문 사용 금지
tasks:
# [TASK 1] Setting Profile & Change Timezone
- name: Set alias for vim
lineinfile:
path: /etc/profile
line: 'alias vi=vim'
state: present
- name: Force sudo su - in .bashrc (Not Recommended)
lineinfile:
path: /home/kubeadm/.bashrc
line: 'sudo su -'
state: present
# 주의: 이 설정은 kubeadm 사용자의 일반 쉘 접근을 차단할 수 있음
- name: Set Timezone to Asia/Seoul
timezone:
name: Asia/Seoul
# [TASK 2] Disable AppArmor & UFW
- name: Stop and Disable UFW
service:
name: ufw
state: stopped
enabled: no
- name: Stop and Disable AppArmor
service:
name: apparmor
state: stopped
enabled: no
ignore_errors: yes # 일부 환경에서 AppArmor가 없을 경우 무시
# [TASK 3] Install Packages
- name: Update apt cache and install packages
apt:
update_cache: yes
name:
- tree
- sshpass
- unzip
state: present
# [TASK 4] Config account & ssh config
- name: Set password for kubeadm user
user:
name: kubeadm
password: "{{ target_password | password_hash('sha512') }}"
state: present
shell: /bin/bash
create_home: yes
- name: Set password for root user
user:
name: root
password: "{{ target_password | password_hash('sha512') }}"
- name: Enable PasswordAuthentication in SSH
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PasswordAuthentication'
line: 'PasswordAuthentication yes'
notify: Restart SSH
- name: Enable PermitRootLogin in SSH
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PermitRootLogin'
line: 'PermitRootLogin yes'
notify: Restart SSH
# [TASK 5] Setting Local DNS Using Hosts file
- name: Remove 127.0.x.1 entries from /etc/hosts
lineinfile:
path: /etc/hosts
regexp: '^127\.0\.(1|2)\.1'
state: absent
- name: Add K8s nodes to /etc/hosts
blockinfile:
path: /etc/hosts
block: |
192.168.0.31 k8s-master
192.168.0.32 k8s-worker-1
192.168.0.33 k8s-worker-2
192.168.0.34 k8s-worker-3
marker: "# {mark} ANSIBLE MANAGED BLOCK K8S NODES"
handlers:
- name: Restart SSH
service:
name: sshd
state: restarted
2. 도전과제 실습
User 생성 후 삭제 실습
Ansible을 이용해서 user을 생성 후 삭제하는 실습을 진행해보겠습니다.
# task1.yaml
- name: Manage System Users
hosts: all
become: yes # root 권한 필요
vars:
# 생성할 유저 목록
users_to_create:
- username: "devops_engineer"
password: "changeme123!"
groups: "sudo"
shell: "/bin/bash"
- username: "junior_dev"
password: "changeme123!"
groups: ""
shell: "/bin/bash"
# 삭제할 유저 목록
users_to_delete:
- username: "junior_dev"
tasks:
- name: Create user accounts
ansible.builtin.user:
name: "{{ item.username }}"
# 비밀번호를 평문으로 넣으면 안됨 -> sha512로 해싱 처리
password: "{{ item.password | password_hash('sha512') }}"
groups: "{{ item.groups | default(omit) }}"
shell: "{{ item.shell }}"
append: yes
create_home: yes
state: present
loop: "{{ users_to_create }}"
tags: create
- name: Delete user accounts
ansible.builtin.user:
name: "{{ item.username }}"
state: absent
remove: yes
force: yes
loop: "{{ users_to_delete }}"
tags: delete
devops_engineer라는 계정을 생성 후 삭제하는 ansible-playbook yaml파일의 내용입니다.
ansible_user.yaml파일로 해당 파일을 저장하고 ansible을 실행하면 다음과 같이 결과가 출력됩니다.
ansible-playbook -i inventory.ini ansible_user.yaml
# PLAY [Manage System Users] **************************************************************************************************************************************************************************************************************************
# TASK [Gathering Facts] ******************************************************************************************************************************************************************************************************************************
# ok: [k8s-worker-1]
# ok: [k8s-worker-3]
# ok: [k8s-master]
# ok: [k8s-worker-2]
# TASK [Create user accounts] *************************************************************************************************************************************************************************************************************************
# changed: [k8s-worker-3] => (item={'username': 'devops_engineer', 'password': 'changeme123!', 'groups': 'sudo', 'shell': '/bin/bash'})
# changed: [k8s-master] => (item={'username': 'devops_engineer', 'password': 'changeme123!', 'groups': 'sudo', 'shell': '/bin/bash'})
# changed: [k8s-worker-1] => (item={'username': 'devops_engineer', 'password': 'changeme123!', 'groups': 'sudo', 'shell': '/bin/bash'})
# changed: [k8s-worker-2] => (item={'username': 'devops_engineer', 'password': 'changeme123!', 'groups': 'sudo', 'shell': '/bin/bash'})
# changed: [k8s-worker-3] => (item={'username': 'junior_dev', 'password': 'changeme123!', 'groups': '', 'shell': '/bin/bash'})
# changed: [k8s-worker-1] => (item={'username': 'junior_dev', 'password': 'changeme123!', 'groups': '', 'shell': '/bin/bash'})
# changed: [k8s-master] => (item={'username': 'junior_dev', 'password': 'changeme123!', 'groups': '', 'shell': '/bin/bash'})
# changed: [k8s-worker-2] => (item={'username': 'junior_dev', 'password': 'changeme123!', 'groups': '', 'shell': '/bin/bash'})
# TASK [Delete user accounts] *************************************************************************************************************************************************************************************************************************
# changed: [k8s-master] => (item={'username': 'junior_dev'})
# changed: [k8s-worker-1] => (item={'username': 'junior_dev'})
# changed: [k8s-worker-2] => (item={'username': 'junior_dev'})
# changed: [k8s-worker-3] => (item={'username': 'junior_dev'})
# PLAY RECAP ******************************************************************************************************************************************************************************************************************************************
# k8s-master : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# k8s-worker-1 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# k8s-worker-2 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# k8s-worker-3 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
실제로 Master 서버에서 유저 리스트를 출력해보면 junior_dev계정은 없는 것을 확인할 수 있습니다.
grep /bin/bash /etc/passwd | cut -f1 -d:
# root
# kubeadm
# devops_engineer
관리 대상에 uptime을 ansible.builtin.debug 모듈을 통해서 확인
명령어 수행 결과를 uptime_result에 담하 debug 모듈을 통해 확인하는 playbook 입니다.
# task2.yaml
- name: Check System Uptime
hosts: all
tasks:
- name: Execute uptime command
ansible.builtin.command: uptime
register: uptime_result
- name: Print uptime info
ansible.builtin.debug:
msg: "System Uptime: {{ uptime_result.stdout }}"
ansibe 수행 결과는 다음과 같습니다.
ansible-playbook -i inventory.ini task2.yaml
# PLAY [Check System Uptime] **************************************************************************************************************************************************************************************************************************
# TASK [Gathering Facts] ******************************************************************************************************************************************************************************************************************************
# ok: [k8s-worker-3]
# ok: [k8s-worker-1]
# ok: [k8s-master]
# ok: [k8s-worker-2]
# TASK [Execute uptime command] ***********************************************************************************************************************************************************************************************************************
# changed: [k8s-worker-1]
# changed: [k8s-worker-2]
# changed: [k8s-master]
# changed: [k8s-worker-3]
# TASK [Print uptime info] ****************************************************************************************************************************************************************************************************************************
# ok: [k8s-master] => {
# "msg": "System Uptime: 13:02:34 up 1:52, 1 user, load average: 0.00, 0.00, 0.00"
# }
# ok: [k8s-worker-1] => {
# "msg": "System Uptime: 13:02:34 up 1:52, 1 user, load average: 0.00, 0.00, 0.00"
# }
# ok: [k8s-worker-2] => {
# "msg": "System Uptime: 13:02:34 up 1:52, 1 user, load average: 0.00, 0.00, 0.00"
# }
# ok: [k8s-worker-3] => {
# "msg": "System Uptime: 13:02:34 up 1:52, 1 user, load average: 0.00, 0.00, 0.00"
# }
# PLAY RECAP ******************************************************************************************************************************************************************************************************************************************
# k8s-master : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# k8s-worker-1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# k8s-worker-2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# k8s-worker-3 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
커널 버전 및 OS 정보 추출
ansible 사용 시 Facts를 이용하여 Target Server들의 Metadata(커널버전, OS 정보.. )를 추출할 수 있는데 이때 사용하는 playbook은 다음과 같습니다.
# task3.yaml
- name: Check OS and Kernel Version
hosts: all
gather_facts: yes # Facts 수집
tasks:
- name: Print OS and Kernel Info
ansible.builtin.debug:
msg:
- "Operating System: {{ ansible_distribution }} {{ ansible_distribution_version }}"
- "Kernel Version: {{ ansible_kernel }}"
저는 proxmox에서 Image(Ubuntu24.04)로 생성 했어서 다음과 같이 결과가 출력이 됩니다.
ansible-playbook -i inventory.ini task3.yaml
# PLAY [Check OS and Kernel Version] ******************************************************************************************************************************************************************************************************************
# TASK [Gathering Facts] ******************************************************************************************************************************************************************************************************************************
# ok: [k8s-master]
# ok: [k8s-worker-2]
# ok: [k8s-worker-1]
# ok: [k8s-worker-3]
# TASK [Print OS and Kernel Info] *********************************************************************************************************************************************************************************************************************
# ok: [k8s-master] => {
# "msg": [
# "Operating System: Ubuntu 24.04",
# "Kernel Version: 6.8.0-60-generic"
# ]
# }
# ok: [k8s-worker-1] => {
# "msg": [
# "Operating System: Ubuntu 24.04",
# "Kernel Version: 6.8.0-60-generic"
# ]
# }
# ok: [k8s-worker-2] => {
# "msg": [
# "Operating System: Ubuntu 24.04",
# "Kernel Version: 6.8.0-60-generic"
# ]
# }
# ok: [k8s-worker-3] => {
# "msg": [
# "Operating System: Ubuntu 24.04",
# "Kernel Version: 6.8.0-60-generic"
# ]
# }
# PLAY RECAP ******************************************************************************************************************************************************************************************************************************************
# k8s-master : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# k8s-worker-1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# k8s-worker-2 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# k8s-worker-3 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
리눅스 유저 10명을 반복문을 통해 생성 확인 삭제
Ansible을 이용하여 Linux 유저 10명을 반복문을 통해 생성 후 playbook에서 확인 후 삭제하는 스크립트를 작성 해보겠습니다.
# task4.yaml
- name: User Lifecycle Test (Create -> Verify -> Delete)
hosts: all
become: yes
vars:
user_prefix: "user"
user_count: 10
tasks:
# 사용자 10명 생성
- name: Create 10 users ({{ user_prefix }}1 ~ {{ user_prefix }}10)
ansible.builtin.user:
name: "{{ user_prefix }}{{ item }}"
state: present
shell: /bin/bash
create_home: yes
loop: "{{ range(1, user_count + 1) | list }}"
# 생성 확인
- name: Verify users exist (Check /etc/passwd)
ansible.builtin.command: "id {{ user_prefix }}{{ item }}"
register: user_check_result
changed_when: false
ignore_errors: yes
loop: "{{ range(1, user_count + 1) | list }}"
- name: Print verification result
ansible.builtin.debug:
msg: "User '{{ item.item }}' Status: {{ 'EXISTS (uid=' + item.stdout.split()[0].split('=')[1] + ')' if item.rc == 0 else 'MISSING' }}"
loop: "{{ user_check_result.results }}"
loop_control:
label: "{{ item.item }}"
# 사용자 10명 삭제
- name: Delete the users (remove home directory)
ansible.builtin.user:
name: "{{ user_prefix }}{{ item }}"
state: absent
remove: yes # 홈 디렉터리까지 삭제
loop: "{{ range(1, user_count + 1) | list }}"
ansible-playbook -i inventory.ini task4.yaml
# PLAY [User Lifecycle Test (Create -> Verify -> Delete)] *********************************************************************************************************************************************************************************************
# TASK [Gathering Facts] ******************************************************************************************************************************************************************************************************************************
# ok: [k8s-master]
# ok: [k8s-worker-3]
# ok: [k8s-worker-1]
# ok: [k8s-worker-2]
# TASK [Create 10 users (user1 ~ user10)] *************************************************************************************************************************************************************************************************************
# changed: [k8s-worker-1] => (item=1)
# changed: [k8s-worker-3] => (item=1)
# changed: [k8s-master] => (item=1)
# changed: [k8s-worker-2] => (item=1)
# ....
# changed: [k8s-master] => (item=10)
# changed: [k8s-worker-2] => (item=10)
# TASK [Verify users exist (Check /etc/passwd)] *******************************************************************************************************************************************************************************************************
# ok: [k8s-worker-2] => (item=1)
# ...
# ok: [k8s-worker-3] => (item=9)
# ok: [k8s-master] => (item=9)
# ok: [k8s-worker-1] => (item=7)
# ok: [k8s-worker-2] => (item=10)
# ok: [k8s-worker-3] => (item=10)
# ok: [k8s-master] => (item=10)
# ok: [k8s-worker-1] => (item=8)
# ok: [k8s-worker-1] => (item=9)
# ok: [k8s-worker-1] => (item=10)
# TASK [Print verification result] ********************************************************************************************************************************************************************************************************************
# ok: [k8s-master] => (item=1) => {
# "msg": "User '1' Status: EXISTS (uid=1002(user1))"
# }
# ok: [k8s-master] => (item=2) => {
# "msg": "User '2' Status: EXISTS (uid=1003(user2))"
# }
# ok: [k8s-worker-1] => (item=1) => {
# "msg": "User '1' Status: EXISTS (uid=1002(user1))"
# }
# ok: [k8s-master] => (item=3) => {
# "msg": "User '3' Status: EXISTS (uid=1004(user3))"
# }
# ...
# }
# ok: [k8s-worker-1] => (item=10) => {
# "msg": "User '10' Status: EXISTS (uid=1011(user10))"
# }
# ok: [k8s-worker-2] => (item=10) => {
# "msg": "User '10' Status: EXISTS (uid=1011(user10))"
# }
# ok: [k8s-worker-3] => (item=8) => {
# "msg": "User '8' Status: EXISTS (uid=1009(user8))"
# }
# ok: [k8s-worker-3] => (item=9) => {
# "msg": "User '9' Status: EXISTS (uid=1010(user9))"
# }
# ok: [k8s-worker-3] => (item=10) => {
# "msg": "User '10' Status: EXISTS (uid=1011(user10))"
# }
# TASK [Delete the users (remove home directory)] *****************************************************************************************************************************************************************************************************
# changed: [k8s-master] => (item=1)
# changed: [k8s-worker-2] => (item=1)
# changed: [k8s-worker-1] => (item=1)
# changed: [k8s-worker-3] => (item=1)
# ...
# changed: [k8s-worker-3] => (item=9)
# changed: [k8s-worker-1] => (item=9)
# changed: [k8s-master] => (item=10)
# changed: [k8s-worker-2] => (item=10)
# changed: [k8s-worker-3] => (item=10)
# changed: [k8s-worker-1] => (item=10)
# PLAY RECAP ******************************************************************************************************************************************************************************************************************************************
# k8s-master : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# k8s-worker-1 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# k8s-worker-2 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# k8s-worker-3 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
실제로 Master 서버에 접속해서 유저를 확인해보겠습니다.
grep /bin/bash /etc/passwd | cut -f1 -d:
# root
# kubeadm
# devops_engineer
loop 반복문 중 sequence 를 이용하여 /var/log/test1 ~ /var/log/test100 100개 파일(file 모듈)을 생성 확인 후 삭제
반복문과 state를 이용하여 파일 생성 -> 확인 -> 삭제 작업을 진행해보겠습니다.
---
# task5.yaml
- name: Create 100 files
hosts: all
become: yes # /var/log 권한 문제 해결을 위해 필수
vars:
file_path_prefix: "/var/log/test"
start_num: 1
end_num: 100
tasks:
# 파일 100개 생성 (state: touch)
- name: Create 100 files ({{ file_path_prefix }}1 ~ {{ end_num }})
ansible.builtin.file:
path: "{{ file_path_prefix }}{{ item }}"
state: touch
mode: '0644'
# Python의 range는 끝 번호를 포함하지 않으므로 +1을 해야 100까지 돕니다.
loop: "{{ range(start_num, end_num + 1) | list }}"
# 파일 생성 확인
- name: Check if files exist
ansible.builtin.stat:
path: "{{ file_path_prefix }}{{ item }}"
register: file_check_result
loop: "{{ range(start_num, end_num + 1) | list }}"
# 확인 결과 요약 출력 (존재하는 파일 개수만 계산)
- name: Report verification result
ansible.builtin.debug:
msg: "Verified: {{ file_check_result.results | selectattr('stat.exists', 'equalto', true) | list | length }} / {{ end_num }} files exist."
# 파일 100개 삭제
- name: Delete the files
ansible.builtin.file:
path: "{{ file_path_prefix }}{{ item }}"
state: absent
loop: "{{ range(start_num, end_num + 1) | list }}"
수행 결과는 다음과 같이 출력됩니다.
ansible-playbook -i inventory.ini task5.yaml
# PLAY [Create 100 files] *****************************************************************************************************************************************************************************************************************************
# TASK [Gathering Facts] ******************************************************************************************************************************************************************************************************************************
# ok: [k8s-master]
# ok: [k8s-worker-1]
# ok: [k8s-worker-2]
# ok: [k8s-worker-3]
# TASK [Create 100 files (/var/log/test1 ~ 100)] ******************************************************************************************************************************************************************************************************
# changed: [k8s-worker-1] => (item=1)
# changed: [k8s-master] => (item=1)
# changed: [k8s-worker-3] => (item=1)
# changed: [k8s-worker-2] => (item=1)
# changed: [k8s-worker-1] => (item=2)
# changed: [k8s-master] => (item=2)
# changed: [k8s-worker-3] => (item=2)
# changed: [k8s-worker-2] => (item=2)
# changed: [k8s-worker-1] => (item=3)
# changed: [k8s-master] => (item=3)
# changed: [k8s-worker-3] => (item=3)
# changed: [k8s-worker-2] => (item=3)
# changed: [k8s-worker-1] => (item=4)
# changed: [k8s-master] => (item=4)
# ...
# changed: [k8s-master] => (item=98)
# changed: [k8s-worker-1] => (item=99)
# changed: [k8s-worker-3] => (item=99)
# changed: [k8s-worker-2] => (item=99)
# changed: [k8s-master] => (item=99)
# changed: [k8s-worker-1] => (item=100)
# changed: [k8s-worker-3] => (item=100)
# changed: [k8s-worker-2] => (item=100)
# changed: [k8s-master] => (item=100)
# TASK [Report verification result] *******************************************************************************************************************************************************************************************************************
# ok: [k8s-worker-1] => {
# "msg": "Verified: 100 / 100 files exist."
# }
# ok: [k8s-master] => {
# "msg": "Verified: 100 / 100 files exist."
# }
# ok: [k8s-worker-2] => {
# "msg": "Verified: 100 / 100 files exist."
# }
# ok: [k8s-worker-3] => {
# "msg": "Verified: 100 / 100 files exist."
# }
# TASK [Delete the files] *****************************************************************************************************************************************************************************************************************************
# changed: [k8s-master] => (item=1)
# changed: [k8s-worker-1] => (item=1)
# changed: [k8s-worker-2] => (item=1)
# changed: [k8s-worker-3] => (item=1)
# changed: [k8s-master] => (item=2)
# changed: [k8s-worker-1] => (item=2)
# changed: [k8s-worker-2] => (item=2)
# changed: [k8s-worker-3] => (item=2)
# changed: [k8s-master] => (item=3)
# changed: [k8s-worker-1] => (item=3)
# ...
# changed: [k8s-master] => (item=99)
# changed: [k8s-worker-1] => (item=99)
# changed: [k8s-worker-3] => (item=99)
# changed: [k8s-worker-2] => (item=99)
# changed: [k8s-master] => (item=100)
# changed: [k8s-worker-1] => (item=100)
# changed: [k8s-worker-3] => (item=100)
# changed: [k8s-worker-2] => (item=100)
# PLAY RECAP ******************************************************************************************************************************************************************************************************************************************
# k8s-master : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# k8s-worker-1 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# k8s-worker-2 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# k8s-worker-3 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Ubuntu OS이면서 fqdn으로 k8s-worker-1 인 경우, debug 모듈을 사용하여 OS 정보와 fqdn 정보를 출력
Ansible에서 지원하는 조건문(when)을 사용해서 실습을 진행해보겠습니다.
---
# task6.yaml
- name: Debug OS and FQDN for Specific Worker
hosts: all
gather_facts: yes # 필수: OS 및 FQDN 정보를 수집합니다.
tasks:
- name: Print Info only if Ubuntu AND fqdn matches 'k8s-worker-1'
ansible.builtin.debug:
msg:
- "System Matched!"
- "Operating System: {{ ansible_distribution }} {{ ansible_distribution_version }}"
- "FQDN: {{ ansible_fqdn }}"
when:
- ansible_distribution == "Ubuntu"
- ansible_fqdn == "k8s-worker-1"
조건에 맞지 않는 경우 skipping으로 지나가고, 조건이 맞는 경우 로직을 수행하는 것을 확인 할 수 있습니다.
ansible-playbook -i inventory.ini task6.yaml
# PLAY [Debug OS and FQDN for Specific Worker] ********************************************************************************************************************************************************************************************************
# TASK [Gathering Facts] ******************************************************************************************************************************************************************************************************************************
# ok: [k8s-master]
# ok: [k8s-worker-2]
# ok: [k8s-worker-3]
# ok: [k8s-worker-1]
# TASK [Print Info only if Ubuntu AND fqdn matches 'k8s-worker-1'] ************************************************************************************************************************************************************************************
# skipping: [k8s-master]
# ok: [k8s-worker-1] => {
# "msg": [
# "System Matched!",
# "Operating System: Ubuntu 24.04",
# "FQDN: k8s-worker-1"
# ]
# }
# skipping: [k8s-worker-2]
# skipping: [k8s-worker-3]
# PLAY RECAP ******************************************************************************************************************************************************************************************************************************************
# k8s-master : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
# k8s-worker-1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# k8s-worker-2 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
# k8s-worker-3 : ok=1 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
반복문+조건문을 함께 사용
반복문과 조건문을 함께 사용하는 스크립트를 패키지 설치 유무의 조건을 사용해서 수행해보겠습니다
---
# task7.yaml
- name: Conditional Loop Example (Install Only Missing Packages)
hosts: all
become: yes
vars:
required_packages:
- name: "cmatrix" # Matrix screen saver
version: "present"
- name: "figlet" # ASCII Logo Maker
version: "present"
tasks:
# 패키지 설치 여부 사전 확인
# ignore_errors: yes를 통해 설치 안 된 패키지에서 에러가 나도 멈추지 않게 함
- name: Check if packages are installed
ansible.builtin.command: "dpkg-query -W {{ item.name }}"
loop: "{{ required_packages }}"
register: package_check_results
ignore_errors: yes
changed_when: false # 단순 확인이므로 변경 상태로 표시하지 않음
# 설치되지 않은 패키지만 골라서 설치
- name: Install missing packages
ansible.builtin.apt:
name: "{{ item.item.name }}"
state: present
# loop_control을 사용하여 이전 Task의 결과(package_check_results)를 순회
loop: "{{ package_check_results.results }}"
# 이전 명령어가 실패(failed)했다면, 즉 패키지가 없다는 뜻으로 설치 진행
when: item.failed == true
loop_control:
label: "{{ item.item.name }}"
패키지가 설치가 안되어있는 경우 다음과 같이 에러가 출력되고 설치가 됩니다
ansible-playbook -i inventory.ini task7.yaml
# TASK [Check if packages are installed] **************************************************************************************************************************************************************************************************************
# failed: [k8s-worker-3] (item={'name': 'cmatrix', 'version': 'present'}) => {"ansible_loop_var": "item", "changed": false, "cmd": ["dpkg-query", "-W", "cmatrix"], "delta": "0:00:00.010607", "end": "2026-01-17 13:58:26.683417", "item": {"name": "cmatrix", "version": "present"}, "msg": "non-zero return code", "rc": 1, "start": "2026-01-17 13:58:26.672810", "stderr": "dpkg-query: no packages found matching cmatrix", "stderr_lines": ["dpkg-query: no packages found matching cmatrix"], "stdout": "", "stdout_lines": []}
# failed: [k8s-worker-1] (item={'name': 'cmatrix', 'version': 'present'}) => {"ansible_loop_var": "item", "changed": false, "cmd": ["dpkg-query", "-W", "cmatrix"], "delta": "0:00:00.011463", "end": "2026-01-17 13:58:26.697810", "item": {"name": "cmatrix", "version": "present"}, "msg": "non-zero return code", "rc": 1, "start": "2026-01-17 13:58:26.686347", "stderr": "dpkg-query: no packages found matching cmatrix", "stderr_lines": ["dpkg-query: no packages found matching cmatrix"], "stdout": "", "stdout_lines": []}
# failed: [k8s-master] (item={'name': 'cmatrix', 'version': 'present'}) => {"ansible_loop_var": "item", "changed": false, "cmd": ["dpkg-query", "-W", "cmatrix"], "delta": "0:00:00.012263", "end": "2026-01-17 13:58:26.709341", "item": {"name": "cmatrix", "version": "present"}, "msg": "non-zero return code", "rc": 1, "start": "2026-01-17 13:58:26.697078", "stderr": "dpkg-query: no packages found matching cmatrix", "stderr_lines": ["dpkg-query: no packages found matching cmatrix"], "stdout": "", "stdout_lines": []}
# failed: [k8s-worker-2] (item={'name': 'cmatrix', 'version': 'present'}) => {"ansible_loop_var": "item", "changed": false, "cmd": ["dpkg-query", "-W", "cmatrix"], "delta": "0:00:00.010972", "end": "2026-01-17 13:58:26.699857", "item": {"name": "cmatrix", "version": "present"}, "msg": "non-zero return code", "rc": 1, "start": "2026-01-17 13:58:26.688885", "stderr": "dpkg-query: no packages found matching cmatrix", "stderr_lines": ["dpkg-query: no packages found matching cmatrix"], "stdout": "", "stdout_lines": []}
# failed: [k8s-worker-3] (item={'name': 'figlet', 'version': 'present'}) => {"ansible_loop_var": "item", "changed": false, "cmd": ["dpkg-query", "-W", "figlet"], "delta": "0:00:00.012058", "end": "2026-01-17 13:58:27.047979", "item": {"name": "figlet", "version": "present"}, "msg": "non-zero return code", "rc": 1, "start": "2026-01-17 13:58:27.035921", "stderr": "dpkg-query: no packages found matching figlet", "stderr_lines": ["dpkg-query: no packages found matching figlet"], "stdout": "", "stdout_lines": []}
# failed: [k8s-worker-1] (item={'name': 'figlet', 'version': 'present'}) => {"ansible_loop_var": "item", "changed": false, "cmd": ["dpkg-query", "-W", "figlet"], "delta": "0:00:00.011165", "end": "2026-01-17 13:58:27.062956", "item": {"name": "figlet", "version": "present"}, "msg": "non-zero return code", "rc": 1, "start": "2026-01-17 13:58:27.051791", "stderr": "dpkg-query: no packages found matching figlet", "stderr_lines": ["dpkg-query: no packages found matching figlet"], "stdout": "", "stdout_lines": []}
# ...ignoring
# TASK [Install missing packages] *********************************************************************************************************************************************************************************************************************
# changed: [k8s-master] => (item=cmatrix)
# changed: [k8s-worker-3] => (item=cmatrix)
# changed: [k8s-worker-2] => (item=cmatrix)
# changed: [k8s-worker-1] => (item=cmatrix)
# changed: [k8s-worker-1] => (item=figlet)
# changed: [k8s-worker-2] => (item=figlet)
# changed: [k8s-master] => (item=figlet)
# changed: [k8s-worker-3] => (item=figlet)
# PLAY RECAP ******************************************************************************************************************************************************************************************************************************************
# k8s-master : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
# k8s-worker-1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
# k8s-worker-2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
# k8s-worker-3 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
Master 서버에서 직접 설치된 패키지를 사용해보면 다음과 같습니다.
cmatrix

Figure 2.1 cmatrix 사용 예시
apache2 패키지를 apt 모듈을 통해서 설치 시, 핸들러를 호출하여 service 모듈로 apache2를 재시작
# task8.yaml
---
- name: Install Apache and Restart on Change
hosts: all
become: yes
tasks:
- name: Install Apache2 package
ansible.builtin.apt:
name: apache2
state: present
update_cache: yes
notify: Restart Apache # 핸들러 호출
- name: Ensure Apache is enabled and running
ansible.builtin.service:
name: apache2
state: started
enabled: yes
handlers:
- name: Restart Apache
ansible.builtin.service:
name: apache2
state: restarted
ansible-playbook -i inventory.ini task8.yaml
# ....
# TASK [Install Apache2 package] **********************************************************************************************************************************************************************************************************************
# changed: [k8s-worker-3]
# changed: [k8s-master]
# changed: [k8s-worker-2]
# changed: [k8s-worker-1]
# TASK [Ensure Apache is enabled and running] *********************************************************************************************************************************************************************************************************
# ok: [k8s-worker-3]
# ok: [k8s-master]
# ok: [k8s-worker-1]
# ok: [k8s-worker-2]
# RUNNING HANDLER [Restart Apache] ********************************************************************************************************************************************************************************************************************
# changed: [k8s-master]
# changed: [k8s-worker-1]
# changed: [k8s-worker-2]
# changed: [k8s-worker-3]
# PLAY RECAP ******************************************************************************************************************************************************************************************************************************************
# k8s-master : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# k8s-worker-1 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# k8s-worker-2 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# k8s-worker-3 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Master 서버에서 확인해보겠습니다.
dpkg-query -W apache2
# apache2 2.4.58-1ubuntu8.8
block rescure always 키워드를 사용한 플레이북을 작성하여 테스트
Ansible에서 제공하는 block, rescure, always는 프로그래밍에서 Try, Catch finally와 같이 에러 발생에 대해서 조치하는 키워드입니다.
실습시 hosts를 로컬로 지정하여 로컬에서만 수행되도록 설정했으며, robots 파일 예제를 통해 실습을 해보겠습니다.
# task9.yaml
- name: Robot File Download Test (Block/Rescue/Always)
hosts: localhost
connection: local
gather_facts: no
vars:
# 테스트 1: 정상 URL (https://www.google.com/robots.txt)
# 테스트 2: 없는 URL (에러 유발용)
target_url: "https://www.google.com/aaaaaaaa.txt"
dest_file: "/tmp/downloaded_file.txt"
log_file: "/tmp/download_activity.log"
tasks:
- name: Download Process Wrapper
block:
# 메인 작업 (파일 다운로드 시도)
- name: Attempt to download file from URL
ansible.builtin.get_url:
url: "{{ target_url }}"
dest: "{{ dest_file }}"
timeout: 5
register: download_result
- name: Success Message (Only runs if download succeeds)
ansible.builtin.debug:
msg: "Download successful! File saved to {{ dest_file }}"
rescue:
# 실패 시 실행 (대체 파일 생성)
- name: Rescue - Download failed! Creating a dummy file instead
ansible.builtin.copy:
content: "This is a fallback file because the download failed.\n"
dest: "{{ dest_file }}"
- name: Warn the user
ansible.builtin.debug:
msg: "WARNING: Could not download from {{ target_url }}. Created fallback file."
always:
# 무조건 실행 (로그 기록)
- name: Always - Record activity timestamp
ansible.builtin.lineinfile:
path: "{{ log_file }}"
create: yes
line: "Activity recorded at {{ lookup('pipe', 'date') }} | Target: {{ target_url }}"
state: present
- name: Final Cleanup Check
ansible.builtin.debug:
msg: "Process finished. Check {{ dest_file }} and {{ log_file }}."
cat 명령어를 수행해서 로그파일이 저장된 경로를 확인해보면 ansible을 통해 수행된 기록이 적혀져있습니다.
ansible-playbook -i inventory.ini task9.yaml
# PLAY [Robot File Download Test (Block/Rescue/Always)] ***********************************************************************************************************************************************************************************************
# TASK [Attempt to download file from URL] ************************************************************************************************************************************************************************************************************
# fatal: [localhost]: FAILED! => {"changed": false, "dest": "/tmp/downloaded_file.txt", "elapsed": 0, "msg": "Request failed", "response": "HTTP Error 404: Not Found", "status_code": 404, "url": "https://www.google.com/aaaaaaaa.txt"}
# TASK [Rescue - Download failed! Creating a dummy file instead] **************************************************************************************************************************************************************************************
# changed: [localhost]
# TASK [Warn the user] ********************************************************************************************************************************************************************************************************************************
# ok: [localhost] => {
# "msg": "WARNING: Could not download from https://www.google.com/aaaaaaaa.txt. Created fallback file."
# }
# TASK [Always - Record activity timestamp] ***********************************************************************************************************************************************************************************************************
# changed: [localhost]
# TASK [Final Cleanup Check] **************************************************************************************************************************************************************************************************************************
# ok: [localhost] => {
# "msg": "Process finished. Check /tmp/downloaded_file.txt and /tmp/download_activity.log."
# }
# PLAY RECAP ******************************************************************************************************************************************************************************************************************************************
# localhost : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=1 ignored=0
cat /tmp/download_activity.log
# Activity recorded at Sat Jan 17 14:21:46 KST 2026 | Target: https://www.google.com/aaaaaaaa.txt
앤서블 갤럭시에서 관심 있는 롤을 검색하여 해당 롤을 사용하는 플레이북을 만들어서 롤을 통한 애플리케이션을 배포
가시다님 실습에서는 PostgreSQL을 설명을 해주셨지만 저는 Redis 설치를 통해 playbook을 만들어 배포를 하려고합니다.
geerlingguy.redis라는 role이 redis 설치시 가장 많이 사용하는 role인것으로 알고 있어 해당 role로 작업을 진행해보겠습니다.
ansible-galaxy install geerlingguy.redis
# role.yaml
- name: Deploy Redis Server using Galaxy Role
hosts: all
become: yes
vars:
# geerlingguy.redis 롤의 기본 변수 오버라이딩
redis_port: 6379
redis_bind_interface: "0.0.0.0" # 외부 접속 허용
redis_requirepass: "ChangeMe123!"
# 보안 설정: 위험한 명령어 비활성화
redis_disabled_commands:
- FLUSHDB
- FLUSHALL
- KEYS
- SHUTDOWN
roles:
- role: geerlingguy.redis
ansible-playbook -i inventory.ini role.yaml
# TASK [geerlingguy.redis : Include OS-specific variables.] *******************************************************************************************************************************************************************************************
# ok: [k8s-master]
# ok: [k8s-worker-1]
# ok: [k8s-worker-2]
# ok: [k8s-worker-3]
# TASK [geerlingguy.redis : Define redis_package.] ****************************************************************************************************************************************************************************************************
# ok: [k8s-master]
# ok: [k8s-worker-1]
# ok: [k8s-worker-2]
# ok: [k8s-worker-3]
# TASK [geerlingguy.redis : Ensure Redis configuration dir exists.] ***********************************************************************************************************************************************************************************
# changed: [k8s-master]
# changed: [k8s-worker-3]
# changed: [k8s-worker-2]
# changed: [k8s-worker-1]
# ...
# TASK [geerlingguy.redis : Ensure Redis is running.] *************************************************************************************************************************************************************************************************
# ok: [k8s-master]
# ok: [k8s-worker-3]
# ok: [k8s-worker-2]
# ok: [k8s-worker-1]
# RUNNING HANDLER [geerlingguy.redis : restart redis] *************************************************************************************************************************************************************************************************
# changed: [k8s-master]
# changed: [k8s-worker-1]
# changed: [k8s-worker-2]
# changed: [k8s-worker-3]
# PLAY RECAP ******************************************************************************************************************************************************************************************************************************************
# k8s-master : ok=9 changed=4 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
# k8s-worker-1 : ok=9 changed=4 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
# k8s-worker-2 : ok=9 changed=4 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
# k8s-worker-3 : ok=9 changed=4 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0
설치가 되었는지 Master 서버에서 확인해보면 다음과 같습니다.
root@k8s-master:~# systemctl status redis
# ● redis-server.service - Advanced key-value store
# Loaded: loaded (/usr/lib/systemd/system/redis-server.service; enabled; preset: enabled)
# Active: active (running) since Sat 2026-01-17 14:30:23 KST; 1min 5s ago
# Docs: http://redis.io/documentation,
# man:redis-server(1)
# Main PID: 32223 (redis-server)
# Status: "Ready to accept connections"
# Tasks: 5 (limit: 9486)
# Memory: 3.2M (peak: 4.1M)
# CPU: 138ms
# CGroup: /system.slice/redis-server.service
# └─32223 "/usr/bin/redis-server 0.0.0.0:6379"
# Jan 17 14:30:23 k8s-master systemd[1]: Starting redis-server.service - Advanced key-value store...
# Jan 17 14:30:23 k8s-master systemd[1]: Started redis-server.service - Advanced key-value store.
3. 실습환경 제거
tofu destroy