728x90
자동화 구성은 결국 '코드'로 드러납니다
1편에서는 왜 자동화를 하게 되었는지, 어떤 기준으로 구조를 설계했는지를 다뤘습니다.
[DevOps] - DevOps - Ansible로 Windows 서비스 자동화 구축하기 – 수작업 없이 패치·롤백·재시작까지 (1편)
이번 글에서는 그 자동화가 실제로 어떻게 구성되었는지를 YAML 중심으로 설명합니다.
패치, 롤백, 재시작은 작업 목적만 다를 뿐, 구성 원칙은 동일합니다.
파일 단위로 역할을 나눈 이유
Playbook을 단일 파일로 만들 수도 있지만, 우리는 아래와 같은 이유로 분리 구조를 택했습니다.
- 재사용성 – 서비스 단위 작업을 별도로 구성 가능
- 가독성 – Playbook이 간결하게 유지됨
- 디버깅 용이 – 실패한 단계 추적이 쉬움
Linux 제어 서버에서 폴더 탐색 후 Windows로 전달하는 구조
우리 환경에서는 Windows 서버가 서비스 실행 주체이며, Linux 제어 서버에서 Ansible을 실행합니다.
중요한 점은 모든 서비스 패치 파일이 NAS에 저장되고, Linux와 Windows가 공통 접근 가능하다는 것입니다.
구성 요약
- Linux 제어 서버 – NAS를 /mnt 경로에 마운트
- NAS 경로 – /mnt/patches/YYYYMMDD/버전 형태
- Windows 서버 – NAS를 드라이브 또는 UNC 경로로 접근
왜 Linux에서 먼저 탐색하는가?
- Windows PowerShell에서 NAS 접근 시 불안정했던 이슈 존재
- 네트워크 드라이브 인식 실패, 속도 지연, 권한 문제 등
- Linux에서 경로를 먼저 탐색해 변수로 넘기는 구조가 가장 안정적
# 버전 폴더 내 APP 패치 디렉터리 탐색
- name: "[INIT] 버전 폴더 탐색"
find:
paths: "/mnt/patches/{{ today }}"
patterns: "APP_PATCH_*"
file_type: directory
register: patch_folder
delegate_to: localhost
RESTART.yml – 서비스 재시작 작업
특정 인덱스만 선별 재시작이 가능하도록 target_id를 변수로 처리합니다.
# 서비스 재시작 플레이북
- name: "[RESTART] 서비스 재시작"
hosts: windows
vars:
service_base: "C:\\[서비스경로]\\[서브폴더]"
tasks:
# 서비스 폴더 목록을 탐색
- name: "[SERVICE] 서비스 폴더 탐색"
win_shell: |
Get-ChildItem -Path "{{ service_base }}" -Directory | Select-Object -ExpandProperty Name
register: svc_folders
# 폴더명을 분석하여 실행 대상 서비스 리스트 구성
- name: "[SERVICE] 실행 대상 서비스 리스트 구성"
set_fact:
services: >-
[{% for name in svc_folders.stdout_lines %}
{% set parts = name.split('_', 1) %}
{% if parts | length == 2 %}
{% set id = parts[0] %}
{% set type = parts[1] %}
{% if target_id | default('') == '' or id == target_id %}
{
"name": "APP_{{ id }}_{{ type }}"
}{% if not loop.last %},{% endif %}
{% endif %}
{% endif %}
{% endfor %}]
# 제어 서비스(A_MAIN) 우선 재시작
- name: "[우선 재시작] 제어 서비스"
win_service:
name: "{{ item.name }}"
state: restarted
loop: "{{ services | selectattr('name', 'search', 'A_MAIN') | list }}"
# 일반 서비스 재시작
- name: "[일반 서비스 재시작]"
win_service:
name: "{{ item.name }}"
state: restarted
loop: "{{ services | rejectattr('name', 'search', 'A_MAIN') | list }}"
patch_one_service.yml – 개별 서비스 패치 작업
# 개별 서비스 중지
- name: "[{{ svc.name }}] 서비스 중지"
win_service:
name: "{{ svc.name }}"
state: stopped
# 백업 디렉토리 생성
- name: "[{{ svc.name }}] 백업 디렉터리 생성"
win_file:
path: "C:\\[백업경로]\\{{ lookup('pipe', 'date +%Y%m%d') }}\\{{ svc.folder }}"
state: directory
# 실행파일 백업
- name: "[{{ svc.name }}] 파일만 백업"
win_shell: |
Get-ChildItem -Path "{{ service_base }}\\{{ svc.folder }}" -File |
ForEach-Object {
Copy-Item $_.FullName -Destination "C:\\[백업경로]\\{{ lookup('pipe', 'date +%Y%m%d') }}\\{{ svc.folder }}" -Force
}
# 기존 실행파일 삭제 (MainApp.exe 제외)
- name: "[{{ svc.name }}] 기존 실행파일 삭제"
win_file:
path: "{{ service_base }}\\{{ svc.folder }}\\{{ svc.exe }}"
state: absent
when: svc.exe != "MainApp.exe"
# 패치 파일 복사
- name: "[{{ svc.name }}] 패치 파일 복사"
win_copy:
src: "{{ patch_dir }}/"
dest: "{{ service_base }}\\{{ svc.folder }}"
force: yes
# 실행파일 이름 변경
- name: "[{{ svc.name }}] 실행파일 이름 변경"
win_shell: |
if (("{{ svc.exe }}" -ne "MainApp.exe") -and (Test-Path "{{ service_base }}\\{{ svc.folder }}\\MainApp.exe")) {
Rename-Item -Path "{{ service_base }}\\{{ svc.folder }}\\MainApp.exe" -NewName "{{ svc.exe }}"
}
# 원본 실행파일 삭제
- name: "[{{ svc.name }}] 원본 실행파일 삭제"
win_file:
path: "{{ service_base }}\\{{ svc.folder }}\\MainApp.exe"
state: absent
when: svc.exe != "MainApp.exe"
# 서비스 시작
- name: "[{{ svc.name }}] 서비스 시작"
win_service:
name: "{{ svc.name }}"
state: started
rollback_one_service.yml – 개별 서비스 롤백 작업
# 서비스 중지
- name: "[{{ svc.name }}] 서비스 중지"
win_service:
name: "{{ svc.name }}"
state: stopped
# 백업본 복사로 롤백
- name: "[{{ svc.name }}] 백업 파일 복사"
win_shell: |
Get-ChildItem -Path "{{ backup_base }}\\{{ svc.folder }}" -File |
ForEach-Object {
Copy-Item $_.FullName -Destination "{{ service_base }}\\{{ svc.folder }}" -Force
}
# 서비스 재시작
- name: "[{{ svc.name }}] 서비스 시작"
win_service:
name: "{{ svc.name }}"
state: started
[PATCH 전체].yml – 전체 서비스 패치
- name: "[PATCH] 서비스 패치 실행"
hosts: windows
vars:
service_base: "C:\\[서비스경로]\\[서브폴더]"
tasks:
# 오늘 날짜 변수 저장
- name: "[INIT] 오늘 날짜 저장"
set_fact:
today: "{{ lookup('pipe', 'date +%Y%m%d') }}"
# 버전 폴더 탐색
- name: "[INIT] 버전 폴더 탐색"
find:
paths: "/mnt/patches/{{ today }}"
patterns: "APP_PATCH_*"
file_type: directory
register: patch_folder
delegate_to: localhost
run_once: true
# 패치 경로 설정
- name: "[INIT] patch_dir 설정"
set_fact:
patch_dir: "{{ patch_folder.files[0].path }}"
# 서비스 폴더 탐색
- name: "[SERVICE] 폴더 탐색"
win_shell: |
Get-ChildItem -Path "{{ service_base }}" -Directory | Select-Object -ExpandProperty Name
register: svc_folders
# 서비스 리스트 구성
- name: "[SERVICE] 리스트 구성"
set_fact:
services: >-
[{% for name in svc_folders.stdout_lines %}
{% set parts = name.split('_', 1) %}
{% if parts | length == 2 %}
{% set id = parts[0] %}
{% set type = parts[1] %}
{
"name": "APP_{{ id }}_{{ type }}",
"folder": "{{ name }}",
"exe": "{{ 'A_MAIN.exe' if type == 'A' else ('B_MAIN.exe' if type == 'B' else 'MainApp.exe') }}"
}{% if not loop.last %},{% endif %}
{% endif %}
{% endfor %}]
# 제어 서비스부터 패치
- name: "[우선 실행] 제어 서비스"
include_tasks: patch_one_service.yml
loop: "{{ services | selectattr('exe', 'equalto', 'A_MAIN.exe') | list }}"
loop_control:
loop_var: svc
# 일반 서비스 패치
- name: "[일반 서비스 처리]"
include_tasks: patch_one_service.yml
loop: "{{ services | rejectattr('exe', 'equalto', 'A_MAIN.exe') | list }}"
loop_control:
loop_var: svc
[ROLLBACK 전체].yml – 전체 서비스 롤백
- name: "[ROLLBACK] 백업 복원 실행"
hosts: windows
vars:
service_base: "C:\\[서비스경로]\\[서브폴더]"
backup_base: "C:\\[백업경로]\\{{ lookup('pipe', 'date +%Y%m%d') }}"
tasks:
# 서비스 폴더 탐색
- name: "[SERVICE] 폴더 탐색"
win_shell: |
Get-ChildItem -Path "{{ service_base }}" -Directory | Select-Object -ExpandProperty Name
register: svc_folders
# 서비스 리스트 구성
- name: "[SERVICE] 리스트 구성"
set_fact:
services: >-
[{% for name in svc_folders.stdout_lines %}
{% set parts = name.split('_', 1) %}
{% if parts | length == 2 %}
{% set id = parts[0] %}
{% set type = parts[1] %}
{
"name": "APP_{{ id }}_{{ type }}",
"folder": "{{ name }}"
}{% if not loop.last %},{% endif %}
{% endif %}
{% endfor %}]
# 제어 서비스 롤백
- name: "[우선 롤백] 제어 서비스"
include_tasks: rollback_one_service.yml
loop: "{{ services | selectattr('name', 'search', 'A_MAIN') | list }}"
loop_control:
loop_var: svc
# 일반 서비스 롤백
- name: "[일반 서비스 롤백]"
include_tasks: rollback_one_service.yml
loop: "{{ services | rejectattr('name', 'search', 'A_MAIN') | list }}"
loop_control:
loop_var: svc
정리하며
- Linux에서 NAS를 탐색하고, Windows에서 작업 수행
- 작업 유형에 따라 구조 분리 및 로직 명확화
- YAML 전체 기록을 통해 유지보수/협업 용이성 확보
3편에서는 Jenkins와 연동하여 웹 UI 기반으로 작업을 실행하는 방법을 공유드릴 예정입니다.
728x90
반응형
'DevOps' 카테고리의 다른 글
DevOps - Jenkins로 Ansible 자동화 트리거하기 – 웹 기반 운영 자동화 구축기 (3편) (0) | 2025.04.14 |
---|---|
DevOps - Ansible로 Windows 서비스 자동화 구축하기 – 수작업 없이 패치·롤백·재시작까지 (1편) (0) | 2025.04.10 |
DevOps - Rocky Linux 9.5에 Jenkins 설치하기 (자동화 서버 구축 시작) (1) | 2025.04.09 |
DevOps - Ansible 설치 및 Windows 서버 연결 (Rocky Linux 9.5 기준) (0) | 2025.04.09 |
DevOps - Synology DSM 7에서 Docker를 활용한 SVN 서버 구축 및 복원하기 (0) | 2025.02.17 |