Ansible로 윈도우 서버 관리

윈도우와 Ansible

윈도우를 제어하기 위한 별도의 Ansible 바이너리가 존재하는 것은 아니다. 다만 리눅스에서는 SSH로 통신을 하고 윈도우에서는 WinRM(Windows Remote Management)으로 통신하여 시스템을 제어하므로 추가 설정이 필요한 부분이 있다. ※ 윈도우10기준으로 1809 업데이트 이후 OpenSSH를 지원하며 Ansible 2.8부터 공식적으로 SSH를 사용할 수 있으므로 취향대로 사용하자.

WinRM(Windows Remote Management)

WinRM은 WS-Management 스펙의 구현으로 SOAP베이스로 하드웨어와 OS제어를 제어하기 위해 만들어졌다. HTTP(TCP-5985)와 HTTPS(TCP-5986)를 사용한다.

요구사항

Ansible로 윈도우 서버를 제어하기 위해서는 다음과 같은 사항이 필요하다. WinRM은 Windows Server 2003에 커스텀으로 올릴 수 있지만... 테스트는 해보지 않았다. 대부분 심플한 명령은 그냥 될 것 같기도 하다.

호환 OS

WinRM을 지원하는 OS군만 지원한다.

  • 개인용 OS : Windwos 7, 8.1, 10
  • 서버 OS : Windows Server 2008, 2008R2, 2012, 2012R2, 2016, 2019

연결

통신에는 WinRM을 사용하는데 Ansbie에서는 통신을 위해 pywinrm을 사용한다. pywinrm의 기본이 WinRM은 HTTP와 HTTPS 통신 모두를 허용하지만 실제 통신에 사용되는 pywinrm이 HTTP통신에 대한 아쉬움이 있다.

  • SSL 인증서 설치

변수

  • 변수명 : alphanumeric+underbar 허용. 대쉬(-) 허용하지 않음.
# 선언
foo:
filed1:1
filed2: two
filed_3: three
#허용
foo.filed1
foo['field2']
foo("field_3")

인벤토리 파일

  • 위치 : /etc/ansible/hosts에 정의
  • 형식 : INI 혹은 YAML
# ini type
mail.example.com
[webservers]
foo.example.com
bar.example.com
[webserbers:vars]
port=8080
[dbservers]
one.example.com
two.example.com
# yaml type
all: # 1. default 그룹
hosts: #
mail.example.com:
children: # 1.1. 그루핑을 위한 자식 노드 선언
webservers: # 1.1.1. webservers 그룹
hosts: # webservers의 호스트
foo.example.com
bar.example.com
vars: # webservers의 변수 그룹
port=8080
dbservers: # 1.1.2. dbservers 그룹
hosts:
one.example.com
two.example.com
  • 실행 관련
    • 실행 옵션 ansible -i가 인벤토리(inventory)의 약자임
    • 복수개의 인벤토리에 동시 실행 가능. ansbiel -i webservers -i dbservers

모듈

윈도우용 모듈은 별도로 존재한다. 예를 들어 파일을 관리할 때, file 모듈을 사용하지 않고 win_file이라는 모듈을 사용한다. 시그니쳐는 최대한 비슷하게 유지하면서 prefix로 win_를 넣은 모듈이 많아 보인다.

  • 인증서 설치

다중 서버 공통 작업을 위한 Ansible

켜져있는 서비스들을 운영하다보면 복수의 서버에 공통 작업을 수행해야 될 때가 있다. 이를 위한 도구가 Ansible인데 공식 홈페이지의 소개를 빌리면 다음과 같다.

What is Ansible? Ansible is powerful IT automation that you can learn quickly. It's simple enough for everyone in your IT team to use, yet powerful enough to automate even the most complex deployments. Ansible handles repetitive tasks, giving your team more time to focus on innovation. - from Official Site

IT 자동화를 위하여 배우기 쉽고, 강력한 도구를 제공하며, 이를 통해 복잡한 배포 작업까지 수행할 수 있는 것이다. 이를 위하여 반복 작업을 다룰 수 있는 방법을 제공하는데, 반복 작업은 여러번 수행하는 반복 작업일 수도 있고, 여러 서버에 수행하는 반복작업일 수도 있다. 물론, 여러 서버에 주기적으로 수행해야하는 작업이 대부분이겠지만. 예를 들면 다음과 같은 업무를 정의할 수 있다.

  • 주기적 반복 : 주기적으로 웹로그 파일을 압축 및 백업
  • 다중 서버 반복 : 복수 서버의 시간 동기화를 위하여 ntp 설정 및 현재 시간 확인

Ansible 구성

공식 홈페이지의 튜토리얼을 보면 Ansible의 구성 요소를 아래와 같이 정의하고 있다.

Playbooks contain plays Plays contain tasks Tasks call modules Tasks run sequentially Handlers are triggered by tasks, and are run once, at the end of plays

조금 풀어서 적어보면 다음과 같다.

  • Ansible : 작업을 수행하는 Ansible 프로그램
  • Playbook : yaml로 정의된 작업 도구
  • Play : playbook에 정의된 개별 작업 뭉치
  • Task : play에 정의된
  • Modules : 단위 작업 프로그램??
  • Handlers :
  • Control Node : Ansible이 설치된 시스템. 콘솔 머신.
  • Target host : 작업이 수행될 타겟 서버로 ssh(or WinRM) 통신을 통하므로 별도 에이전트를 설치할 필요가 없다.

설치

Redhat의 제품이니 yum으로 설치하자.

# 설치
$ sudo yum install ansible
  • 컨트롤 노드로 솔라리스, FreeBSD, 맥도 지원하지만 윈도우는 지원하지 않는다.
  • python으로 개발되었으므로 pip설치도 지원한다.
# sh 설치 확인
$ ansible --version
ansible 2.9.7

작업

콘솔 작업

콘솔로 바로 명령을 수행할 수 있다. 로컬 호스트에 ping 모듈을 수행해보면 핑퐁을 잘 친다.

# ansible localhost -m ping
localhost | SUCCESS => {
"changed": false,
"ping": "pong"
}

기본 명령이 localhost에서 정상적으로 돌아가는 것을 확인하였다. 이제

  1. 명령을 수행할 호스트를 정의하고,
  2. 돌릴 명령을 정의해보자.

타겟 호스트 정의

타겟 호스트

작업이 수행될 Target Host는 /etc/ansible/hosts에 정의되어 있다. 웹서버 두대와 서버의 로그를 쌓는 로그 서버 한대를 동일 가 있다고, 해당 서버들으 ntpsynedserver라는 그룹으로 호스트그룹으로 묶는다고 하면 아래와 같이 작업할 수 있다. Ansible이 Agentless하게 동작할 수 있는 것은 ssh를 통해 명령을 주고 받기 때문이다.

Ansible이 접속정보를 알고 있어야 하는데 패스워드가 너저분하게 널려있는걸 즐기지 않는 경우 ssh 파일을 등록해서 사용하면 된다.

#호스트 파일 수정
$ vim /etc/ansible/hosts
//아래 내용을 추가한다.
[ntpsynedserver]
web.taroguru.net
web2.taroguru.net
elastic.taroguru.net

호스트를 추가하고 호스트의 현재 시간을 읽어보면 다음과 같은 메세지가 출력된다.

#/etc/hosts 읽기
$ ansible ntpsyncedserver -m shell -a "date"
web.taroguru.net | UNREACHABLE! = {
"changed":false,
"msg":"Failed to connect to the host via ssh: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).",
"unreachable":true
}
.
.
.

서버 도메인을 제대로 입력하였는데 UNREACHABLE!이 뜬다. 왜않돼!?

접속 정보

Ansible은 Agentless하게 동작하는데 타겟 호스트에 명령을 내릴 때 전용 Agent를 사용하는 것이 아니라 SSH(or WinRM)를 이용한다. SSH Credentail이 있어야 타겟 호스트에서 작업을 수행할 수가 있는데 Credential 정보가 없으니 접근을 할 수 없는(UNREACHABLE) 것이다.

이를 위하여 Ansible로 수행할 작업의 권한을 가지고 있는 접속 정보(유저 정보)를 추가한다.

#접속 정보 추가
$ vim /etc/ansible/hosts
//호스트 파일 정보 아래 다음 내용을 추가한다.
[ntpsynedserver:vars]
ansible_connection=ssh
ansible_user=ansible_user
ansible_password=ansible_password

패스워드가 널려있는걸 좋아하지 않는 경우 ssh key파일을 등록해서 사용하자.

접속 정보를 입력 후 다시 작업을 수행하면,

#접속 정보 추가
$ ansible ntpsyncedserver -m shell -a "date"
web.taroguru.net | CHANGED | rc=0 >>
Wed May 13 18:19:10 KST 2020
.
.
.

서버들의 현재 시간을 정상적으로 출력하는 것을 볼 수 있다.

작업 정의

콘솔로 매번 작업을 입력하면 반복을 할 수 없으므로 작업에 대한 정의가 필요한데 이를 정의한 YAML 파일을 Playbook이라고 부른다. 글의 처음에 말해던 것 처럼 Playbook은 여러 개의 Play(즉, 배열)로 구성되며, 각 Play는 여러개의 Task(즉, Task의 배열)로 구성된다. 그리고 각 Task의 실질적인 기능 수행은 Ansible에 정의된 Module을 호출함으로써 사용할 수 있다. 플레이북을 살펴보면 다음과 같다.

Playbook

Playbook 구성은 크게 다음과 같다.

--- #PLAYBOOK 형태
- name: 플레이 이름 #문법을 보면 알겠지만 단일 Play의 이름. 이런 Play를 복수개 정의할 수 있다.
hosts: 호스트명 혹은 정의한 호스트 그룹 명
vars: #필요한 변수들 정의.
변수명: 변수 값 #key/value 타입으로 정의
- tasks: #Play에 해당하는 Task 배열을 여기서부터 정의한다. 복수개의 Task를 정의할 수 있으며 순차적으로 수행된다.
- name: 태스크 이름 #단일 Task에 대한 이름 정의
모듈이름: 모듈 argument #모듈을 이용하여 실제 작업을 수행한다.
...

Playbook 예제

문법에 따라 작성하면 다음과 같다.

--- #checksecurity.yml
---
- name: 보안점검
hosts: ntpsyncedserver
become_method: sudo
become_user: root
vars:
checkscript: script/checklinux.sh
runningFolder: /tmp/checksecurity
resultFolder: result/{{ ansible_date_time.date }}
tasks:
- name: 보안점검용 폴더 생성
file:
path: "{{ runningFolder }}"
state: directory
mode: '0755'
- name: 보안 점검 스크립트 실행
script: "{{ checkscript }}"
args:
chdir: "{{ runningFolder }}"
check_mode: false
- name: 점검 결과 폴더 생성
local_action:
module: file
path: "{{ resultFolder }}"
state: directory
mode: '0755'
- name: 점검 결과 검색
find:
paths: "{{ runningFolder }}"
patterns: "*.xml"
register: files_to_copy
- name: 점검 결과 복사
fetch:
src: "{{ item.path }}"
dest: "{{ resultFolder }}/"
flat: yes
with_items: "{{ files_to_copy.files }}"
- name: 점검 파일 삭제
file:
path: "{{ runningFolder }}/"
state: absent
...

위 플레이북에서 모듈을 어떻게 사용하였는지 살펴보면,

  • file : 스크립트를 수행할 폴더를 생성
  • script : 모듈을 이용하여 스크립트를 수행하였다.
  • find : 수행 결과로 떨어진 파일 목록을 획득하고
  • register : 파일 목록을 변수에 저장한다.
  • featch : 획득한 파일 목록을 로컬로 다운로드 받는다.

실행

ansible-playbook을 이용하여 간단하게 실행할 수 있다.

$ ansible-playbook checksecurity.yml

정리

Ansible을 통해서 여러 서버에서 동시 작업을 수행할 수 있는 방법을 획득하였다. 무엇부터 해야할지는 조금 더 고민해보

참조