✔️ 핸들러 (Handler)
핸들러란 무엇이며 우리는 왜 핸들러를 사용할까 ?
/etc/httpd/conf/httpd.conf 파일을 열어보면 보통 80번 포트로 httpd를 서비스하는 것을 알 수 있다.
이 파일의 서비스 포트 라인을 수정해서 8080으로 변경하면 8080번으로 서비스할 수 있다.
하지만 httpd 관련된 설정 파일을 바꾸면 서비스를 재시작해야 설정 파일의 수정 사항이 반영된다.
mysql 서비스 포트도 마찬가지이다.
/etc/my.cnf 의 port=3306 라인을 다른 포트 번호를 수정한 뒤 서비스를 재시작하면 해당되는 포트로 서비스를 제공한다.
✔️ 멱등성 (idempotent)
수학 또는 IT에서 연산의 한 성질을 나타내는 것으로 연산을 여러번 적용하더라도 결과가 달라지지 않는 성질
IaC에서 멱등성이 적용되느냐는 아주 중요한 개념이다.
가장 대표적인게 서비스 시작과 관련한 부분이다.
아래의 코드를 실행하면 설정 파일을 수정했음에도 시스템의 변경 사항이 없다. (여전히 이전의 포트를 사용한다.)
- service:
name: httpd
state: restarted
enabled: yes
PLAY [192.168.100.11] ***********************************************************************************************************************************************************************************************************************************************
TASK [Gathering Facts] **********************************************************************************************************************************************************************************************************************************************
ok: [192.168.100.11]
TASK [yum] **********************************************************************************************************************************************************************************************************************************************************
ok: [192.168.100.11]
TASK [lineinfile] ***************************************************************************************************************************************************************************************************************************************************
ok: [192.168.100.11]
TASK [service] ******************************************************************************************************************************************************************************************************************************************************
changed: [192.168.100.11]
PLAY RECAP **********************************************************************************************************************************************************************************************************************************************************
192.168.100.11 : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
만약 서비스 중인 서버의 conf 파일을 변경해 재시작을 한다고 가정해보자 아주 찰나의 시간이지만 서비스가 중단된다.
이말은 세션에 연결 중인 사람들은 세션이 끊어진다는 소리이다.
즉, conf 파일을 수정하고 시스템을 재시작하는 것은 임시로 해결할 수는 있지만 결과적으로는 좋지는 않다.
무조건 재시작을 거는 코드는 멱등성이 존재하지 않는 코드이며 문제가 있는 코드이다.
아래 코드는 문제가 있는 코드이다.
- hosts: 192.168.100.11
become: yes
vars:
web_svc_port: "80"
tasks:
- yum:
name: httpd
state: installed
- lineinfile:
path: /etc/httpd/conf/httpd.conf
regexp: '^Listen'
line: 'Listen {{ web_svc_port }}'
- service:
name: httpd
state: restarted
enabled: yes
만약 handler를 이용하지 않고 문제가 없는 코드를 짠다면 아래와 같이 짜는 방법도 있다.
- hosts: 192.168.100.11
become: yes
vars:
web_svc_port: "8080"
tasks:
- yum:
name: httpd
state: installed
- lineinfile:
path: /etc/httpd/conf/httpd.conf
regexp: '^Listen'
line: 'Listen {{ web_svc_port }}'
register: result
- service:
name: httpd
state: restarted
enabled: yes
- service:
name: httpd
state: restarted
enabled: yes
when: result is changed
등록 변수를 설정해서 변경 사항을 체크하고 변경 사항이 발생하면 실행하도록 하는 방식이다.
하지만 코드는 가능하면 멱등성을 만족하는 방향으로 설계해야 된다.
모든 모듈, 모듈의 파라미터가 멱등성을 만족하지는 않는다.
ansible-doc service
- state
`started'/`stopped' are idempotent actions that will not run commands unless necessary.
`restarted' will always bounce the service.
`reloaded' will always reload.
*At least one of state and enabled are required.*
Note that reloaded will start the service if it is not already started, even if your chosen init system wouldn't normally.
(Choices: reloaded, restarted, started, stopped)[Default: (null)]
type: str
예를 들어 restarted는 항상 멱등성을 보장할 수 없는 상태이다.
✔️ 핸들러 (Handler) 목적
우리는 여기서 handler의 기능과 목적을 알 수 있다. 핸들러 사용의 이유는 다음과 같다.
특정 작업이 변경 사항을 발생하는 경우에만 실행하기 위한 작업을 지정한다.
---
- name: Verify apache installation
hosts: webservers
vars:
http_port: 80
max_clients: 200
remote_user: root
tasks:
- name: Ensure apache is at the latest version
ansible.builtin.yum:
name: httpd
state: latest
- name: Write the apache config file
ansible.builtin.template:
src: /srv/httpd.j2
dest: /etc/httpd.conf
notify: # 1. notify라는 알림이 있어야
- Restart apache # 변경 사항이 발생하면 notify 알림을 전송하라
- name: Ensure apache is running
ansible.builtin.service:
name: httpd
state: started
# 2. 알림을 받은 handler는 작업을 실행한다.
handlers: # 새로운 keyword # tasks와 유사함
- name: Restart apache
ansible.builtin.service:
name: httpd
state: restarted
✔️ 플레이, 작업의 이름 (name)
handler를 사용하기 위해서는 handler를 호출할 수 있는 무언가가 필요하다. 이때 사용하는 것이 name이다.
name : 작업의 이름, 우리가 직접 지정할 수 있다.
지금까지 tasks에는 엄밀히 말해 이름(name)이 필요하지 않았다.
그러나 알림을 전송할 handler의 작업은 반드시 이름이 있어야 한다. 이름이 없으면 알려줄 방법이 없기 때문이다.
아래의 코드에서 tasks가 수백줄 수천줄이 되면 어디에서 오류가 났는지 쉽게 확인할 수 없다.
가능하면 이름을 붙여 어디에서 오류가 발생했는지를 쉽게 찾도록 한다.
# 수정 전 코드
- hosts: 192.168.100.11
tasks:
-debug:
msg: hello world
-debug:
msg: hello world
-debug:
msg: hello world
-debug:
msg: hello world
# 수정 후 코드
- name: Name Test playbook
hosts: 192.168.100.11
tasks:
- name: task1
debug:
msg: hello world
- name: task2
debug:
msg: hello world
- name: task3
debug:
msg: hello world
- name: task4
debug:
msg: hello world
그럼 이제 코드를 수정해보자
- name: Handler Example
hosts: 192.168.100.11
become: yes
vars:
web_svc_port: "8080"
tasks:
- name: Install httpd Package
yum:
name: httpd
state: installed
- name: Reconfigure httpd service port
lineinfile:
path: /etc/httpd/conf/httpd.conf
regexp: '^Listen'
line: 'Listen {{ web_svc_port }}'
notify:
- Restart httpd Service
- name: Start httpd Service
service:
name: httpd
state: started
enabled: yes
handlers:
- name: Restart httpd Service
service:
name: httpd
state: restarted
enabled: yes
PLAY [Handler Example] **************************************************************************************************************
TASK [Gathering Facts] **************************************************************************************************************
ok: [192.168.100.11]
TASK [Install httpd Package] ********************************************************************************************************
ok: [192.168.100.11]
TASK [Reconfigure httpd service port] ***********************************************************************************************
changed: [192.168.100.11]
TASK [Start httpd Service] **********************************************************************************************************
ok: [192.168.100.11]
RUNNING HANDLER [Restart httpd Service] *********************************************************************************************
changed: [192.168.100.11]
PLAY RECAP **************************************************************************************************************************
192.168.100.11 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
✔️ 핸들러의 실행 순서
핸들러가 실행되는 순서 ?
- 알림을 받은 핸들러 작업만 순서대로 실행
- 모든 작업(
tasks
)이 완료되어야 핸들러가 실행 - 알림을 받은 횟수와 상관없이 '한번만' 실행
즉, 아래와 같을 때는 task1 → task2 → handler1 → handler2 순서로 실행한다.
- name: handler example
hosts: 192.168.100.11
tasks:
- name: task1
file:
path: /tmp/test1
state: touch
notify:
- handle2
- name: task2
file:
path: /tmp/test2
state: touch
notify:
- handle1
handlers:
- name: handle1
debug:
msg: "handle1"
- name: handle2
debug:
msg: "handle2"
실행 결과
PLAY [handler example] **************************************************************************************************************
TASK [Gathering Facts] **************************************************************************************************************
ok: [192.168.100.11]
TASK [task1] ************************************************************************************************************************
changed: [192.168.100.11]
TASK [task2] ************************************************************************************************************************
changed: [192.168.100.11]
RUNNING HANDLER [handle1] ***********************************************************************************************************
ok: [192.168.100.11] => {
"msg": "handle1"
}
RUNNING HANDLER [handle2] ***********************************************************************************************************
ok: [192.168.100.11] => {
"msg": "handle2"
}
PLAY RECAP **************************************************************************************************************************
192.168.100.11 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
✔️ Using variables with handlers
handlers:
- name: Restart memcached
ansible.builtin.service:
name: memcached
state: restarted
listen: "restart web services"
- name: Restart apache
ansible.builtin.service:
name: apache
state: restarted
listen: "restart web services"
tasks:
- name: Restart everything
ansible.builtin.command: echo "this task will restart the web services"
notify: "restart web services"
알림(notify)을 한번에 받도록 구성할 수 있다.
종종 하나의 서비스가 여러가지 서비스와 같이 작동하는 경우가 있다.
여러개의 서비스들을 같이 재시작 해야하는 경우 notify로 2번 적는 것 대신 한번에 받을 수 있도록
listen 선언을 해주면 효율적이다.
✔️ 핸들러가 실행되지 않고 후속 작업에서 실패한 경우
[vagrant@controller 04_handler]$ ansible 192.168.100.11 -m file -a "path=/tmp/test1 state=absent"
[vagrant@controller 04_handler]$ ansible 192.168.100.11 -m file -a "path=/tmp/test2 state=absent"
[vagrant@controller 04_handler]$ ansible-playbook test4.yaml
PLAY [handler example] **************************************************************************************************************
TASK [Gathering Facts] **************************************************************************************************************
ok: [192.168.100.11]
TASK [task1] ************************************************************************************************************************
changed: [192.168.100.11]
TASK [error] ************************************************************************************************************************
fatal: [192.168.100.11]: FAILED! => {"changed": true, "cmd": ["ls", "-", "P"], "delta": "0:00:00.003665", "end": "2022-04-18 05:33:51.879413", "msg": "non-zero return code", "rc": 2, "start": "2022-04-18 05:33:51.875748", "stderr": "ls: cannot access -: No such file or directory\nls: cannot access P: No such file or directory", "stderr_lines": ["ls: cannot access -: No such file or directory", "ls: cannot access P: No such file or directory"], "stdout": "", "stdout_lines": []}
RUNNING HANDLER [handle1] ***********************************************************************************************************
RUNNING HANDLER [handle2] ***********************************************************************************************************
PLAY RECAP **************************************************************************************************************************
192.168.100.11 : ok=2 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
작업이 중간에 실패 하면 handler가 작동하지 않는다.
8080 포트로 변경한 것은 실행에 성공했는데 후속 작업에서 문제가 발생하면
handler에서 변경된 부분에 대해서는 처리를 해줘야 함에도 불구하고 그 다음 작업들을 실행하지 않는다.
문제 상황 : 핸들러가 실행되지 않음
meta 모듈을 실행해 해결할 수 있다. ansible 내부에서 실행할 수 있는 기능이다.
- name: Flush handlers
meta: flush_handlers
하지만 meta 모듈은 종종 번거로울 때가 있다.
더 자주 사용하는 옵션은 --force-handlers 옵션이다.
ansible-playbook --force-handlers
--force-handlers 옵션을 붙이면 후속 작업이 실패해도 핸들러를 실행해준다.
[vagrant@controller 04_handler]$ ansible-playbook test4.yaml --force-handlers
PLAY [handler example] **************************************************************************************************************
TASK [Gathering Facts] **************************************************************************************************************
ok: [192.168.100.11]
TASK [task1] ************************************************************************************************************************
changed: [192.168.100.11]
TASK [error] ************************************************************************************************************************
fatal: [192.168.100.11]: FAILED! => {"changed": true, "cmd": ["ls", "-", "P"], "delta": "0:00:00.002724", "end": "2022-04-18 05:39:17.532482", "msg": "non-zero return code", "rc": 2, "start": "2022-04-18 05:39:17.529758", "stderr": "ls: cannot access -: No such file or directory\nls: cannot access P: No such file or directory", "stderr_lines": ["ls: cannot access -: No such file or directory", "ls: cannot access P: No such file or directory"], "stdout": "", "stdout_lines": []}
RUNNING HANDLER [handle1] ***********************************************************************************************************
ok: [192.168.100.11] => {
"msg": "handle1"
}
RUNNING HANDLER [handle2] ***********************************************************************************************************
ok: [192.168.100.11] => {
"msg": "handle2"
}
PLAY RECAP **************************************************************************************************************************
192.168.100.11 : ok=4 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
'DevOps > Ansible' 카테고리의 다른 글
[Ansible] 태그 (Tag) (0) | 2022.04.20 |
---|---|
[Ansible] 블록 (Block) (0) | 2022.04.20 |
[Ansible] 조건문(Conditionals) (0) | 2022.04.19 |
[Ansible] 반복문(Loops) (0) | 2022.04.19 |
[Ansible] 변수(Variables) (0) | 2022.04.19 |
영차영차 성장 블로그
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!