A101 3기(=Ansible 101 Study)는 Ansible 실무 실습 스터디입니다.
CloudNet@ 가시다 님이 진행하시며, 책 "앤서블로 시작하는 인프라 자동화"을 기반으로 진행하고 있습니다.
이번 글에서는 앤서블 관리 팁들과 기본 문법을 실습한 내용을 정리하였습니다.
Ansible Secret Manage
악분님께서 공유해주신 앤서블 보안 키 관리로 AWS 시크릿 매니저를 통한 보안 관리 방법을 실습하겠습니다.
- 앤시블 플레이북 실행을 위한 접근 키 저장소로 AWS 시크릿 매니저를 사용하였습니다.
- 앤시블 플레이북 실행시 AWS 매니저에서 키를 조회하여 수행합니다.
AWS SecretManager 연동 실습
# AWS 시크릿 매니저 IAM 권한 확인
aws sts get-caller-identity | jq
curl -s http://169.254.169.254/latest/meta-data/iam/info | jq
# 암호 파일 생성
echo "Password1!" > mysecret.txt
chmod go-rx mysecret.txt
ls -l mysecret.txt
#앤시블 암호 키 생성
ansible-vault create mysecret.txt
# AWS SecretManager로 암호 생성 및 확인
aws secretsmanager create-secret --name MySecret1 --secret-string file://mysecret.txt --region ap-northeast-2
aws secretsmanager get-secret-value --secret-id MySecret1 --region ap-northeast-2 | jq
# AWS SecretManager 키 조회 스크립트 구성
vi get-vault-password.sh
---
aws secretsmanager get-secret-value --secret-id ansible/password | jq -r .SecretString | jq -r .ansible_vault_password
ansible-playbook playbook.yaml --vault-password-file ./get-vault-password.sh
AWS Secret Manager 사용 시 IAM 확인이 꼭 필요합니다. IAM 권한 확인 명령어를 통해 접근 권한을 확인해주세요.
생성한 보안 키는 AWS 콘솔에서 관리가 가능합니다.
Ansible Gathering Fact cashing
Gathering Facts 란 원격 호스트의 서버 정보(OS, IP, HostName) 를 뜻합니다. 다만, 원격 호스트가 수백대라면 Facts 정보를 수집하는 시간이 길어질텐데요. 이런 시간을 단축시킬 수 있는 자체 캐싱 기능이 제공됩니다.(참고 공유글)
사용 방법은 간단합니다. 앤시블 설정에서 다음과 같이 설정하면 파일 경로에 노드 정보가 자체적으로 캐싱됩니다.
gathering = smart # 캐싱 설정
fact_caching = jsonfile
fact_caching_connection = facts # 파일 경로
조건문과 반복문
앤시블에서는 조건문과 반복문 기능이 제공됩니다. 상황에 따라 사용 문법이 다르므로 공식 문서에서 기능 사용에 대한 문법을 학습하여 정리합니다. 예제를 통해 문법을 체감하도록 하겠습니다.
Case 1
예를 들어 Ubuntu OS이면서 fqdn으로 tnode1 인 경우, debug 모듈을 사용하여 OS 정보와 fqdn 정보를 출력한다면 다음과 같이 facter를 이용하여 플레이북을 작성할 수 있습니다.
# ch3.yml
---
- hosts: all
tasks:
- name: Print OS and FQDN information
ansible.builtin.debug:
msg: >
OS Type: {{ ansible_facts['distribution'] }}
OS Version: {{ ansible_facts['distribution_version'] }}
FQDN: {{ ansible_facts['fqdn'] }}
when:
- ansible_facts['distribution'] == "Ubuntu"
- ansible_facts['hostname'] == "tnode1"
- when : 조건문 설정 조건입니다. 노드 정보 중 호스트 네임과 OS를 확인하여 tnode1, ubuntu인 것을 확인하여 msg에 설정한 메세지를 출력합니다. 조건에서 사용된 팩트 키는 위 팩트 캐싱 정보 경로에서 확인이 가능합니다.
Case2 또 다른 예제로 플레이북에서 설정한 서비스를 반복적으로 실행하는 예제입니다.
---
- hosts: all
vars:
services:
- sshd
- rsyslog
tasks:
- name: check sshd and rsyslog state
ansible.builtin.service:
name: "{{ item }}"
state: started
loop: "{{ services }}"
- loop : 반복문 문법으로 변수로 설정한 서비스들을 실행합니다.
- state : 서비스나 리소스 현재 상태를 지정하는 데 사용되는 매개 변수 입니다. 예제에서 사용된 started는 서비스가 시작되지 않았다면 시작하도록 지시하는 변수입니다. 예제 외 변수로 stop(정지), restart(재시작), reload(새로고침) 이 있습니다.
Case3
반복문에서 변수 설정을 통해 다음과 같이 설정도 가능합니다.
- hosts: all
tasks:
- name: Create files
ansible.builtin.file:
path: "{{ item['log-path'] }}"
mode: "{{ item['log-mode'] }}"
state: touch
loop:
- log-path: /var/log/test1.log
log-mode: '0644'
- log-path: /var/log/test2.log
log-mode: '0600'
Case4
반복문과 함께 다른 문법을 통해서 이런 구성도 가능합니다.
---
- hosts: localhost
tasks:
- name: Loop echo test
ansible.builtin.shell: "echo 'I can speak {{ item }}'"
loop:
- Korean
- English
register: result
- name: Show result
ansible.builtin.debug:
msg: "Stdout: {{ item.stdout }}"
loop: "{{ result.results }}"
- register:resullt 는 Task 실행 결과를 변수에 저장하는데 사용되는 문법입니다.
- register에는 ansible.builtin.shell 모듈의 출력 결과가 저장됩니다.
- 저장된 변수는 2번째 Task에서의 결과로 사용됩니다.
- msg 문법안에 stdout은 표준 출력입니다.
Case5
반복문과 조건문을 같이 사용하여 다음과 같은 구성도 가능합니다. 아래 예제는 각 서버 / 경로의 이용가능한 사이즈가 300MB 가 이상인 서버만 출력하는 예제입니다.
---
- hosts: db
tasks:
- name: Print Root Directory Size
ansible.builtin.debug:
msg: "Directory {{ item.mount }} size is {{ item.size_available }}"
loop: "{{ ansible_facts['mounts'] }}"
when: item['mount'] == "/" and item['size_available'] > 300000000
작업 실패 무시
작업 실패시 그 다음 작업은 무시됩니다. 테스트를 진행하여 확인하겠습니다.
---
- hosts : tnode1
tasks:
- name: Install apache3
ansible.builtin.apt:
name: apache3
state: latest
- name: Print msg
ansible.builtin.debug:
msg: "Before task is ignored"
뒤에 있는 task 자체가 실행이 안됩니다. 작업 실패를 무시하려면 다음과 같이 태그(ignore_errors)를 하나 추가하면 됩니다.
---
- hosts : tnode1
tasks:
- name: Install apache3
ansible.builtin.apt:
name: apache3
state: latest
ignore_errors: yes
- name: Print msg
ansible.builtin.debug:
msg: "Before task is ignored"
작업 실패와 이어지는 내용으로 작업 실패 조건 지정시 셀 스크립트 보다는 모듈을 사용하는 것을 추천드립니다. command 계열 모듈 사용 시 (CloudNet@ A101)
- 앤서블에서 셸 스크립트를 실행한 뒤 결과로 실패 또는 에러 메시지를 출력해도, 앤서블에서는 작업이 성공했다고 간주합니다.
- 어떤 명령이라도 실행된 경우에는 태스크 실행 상태를 항상 changed 로 한다.
- 이런 경우 failed_when 키워드를 사용하여 작업이 실패했음을 나타내는 조건을 지정할 수 있습니다.
직접 테스트하여 확인해보겠습니다.
ansible -m copy -a 'src=/home/ubuntu/my-ansible/adduser-script.sh dest=/home/ubuntu/adduser-script.sh' tnode1
tnode1 | CHANGED => {
"changed": true,
"checksum": "438e251b53a1299cc548316da9905121f29ffb2a",
"dest": "/home/ubuntu/adduser-script.sh",
"gid": 0,
"group": "root",
"md5sum": "8b50fa7d9ac071271f1b793901f7cc28",
"mode": "0644",
"owner": "root",
"size": 496,
"src": "/home/ubuntu/.ansible/tmp/ansible-tmp-1705234983.7097733-6567-29109052575420/source",
"state": "file",
"uid": 0
}
ubuntu@server:~/my-ansible$ ssh tnode1 ls -l /home/ubuntu/
total 4
-rw-r--r-- 1 root root 496 Jan 14 21:23 adduser-script.sh
---
- hosts: tnode1
tasks:
- name: Run user add script
ansible.builtin.shell: /home/ubuntu/adduser-script.sh
register: command_result
failed_when: "'Please input user id and password' in command_result.stdout"
- name: Print msg
ansible.builtin.debug:
msg: "{{ command_result.stdout }}"
- fail이 되었어야 하나, 커맨드 실행시 changed로 출력되는 것을 확인할 수 있습니다.
Role
앤서블에서는 플레이북의 코드를 재사용할 수 있도록 공통 부품으로 관리 재사용하기 위한 구조를 제공합니다. 앤서블 롤은 다음 명령어를 통해 확인할 수 있습니다. 롤 관련 명령어를 확인하고 초기화를 통해 롤 구조를 확인하겠습니다.
ansible-galaxy role -h
---
usage: ansible-galaxy role [-h] ROLE_ACTION ...
positional arguments:
ROLE_ACTION
init Initialize new role with the base structure of a role.
remove Delete roles from roles_path.
delete Removes the role from Galaxy. It does not remove or alter the actual GitHub repository.
list Show the name and version of each role installed in the roles_path.
search Search the Galaxy database by tags, platforms, author and multiple keywords.
import Import a role into a galaxy server
setup Manage the integration between Galaxy and the given source.
info View more details about a specific role.
install Install role(s) from file(s), URL(s) or Ansible Galaxy
options:
-h, --help show this help message and exit
ansible-galaxy role init my-role
tree ./my-role/
---
./my-role/
├── README.md
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── tasks
│ └── main.yml
├── templates
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
8 directories, 8 files
디렉토리/파일
|
설명
|
defaults/main.yml
|
롤의 기본 변수 정의. 변경 가능하며 우선순위가 낮음.
|
files
|
롤 작업에서 참조하는 정적 파일 저장.
|
handlers/main.yml
|
롤의 핸들러(예: 서비스 재시작) 정의.
|
meta/main.yml
|
롤의 메타데이터(작성자, 라이센스, 의존성 등) 정의.
|
tasks/main.yml
|
롤의 주 작업(설정, 설치 등) 정의.
|
templates
|
롤 작업에서 참조하는 Jinja2 템플릿 저장.
|
tests
|
롤 테스트에 사용되는 인벤토리 및 test.yml 플레이북 포함.
|
vars/main.yml
|
롤의 변수 정의. 내부 목적으로 사용되며, 우선순위가 높음.
|
앤서블에서 롤을 직접 작성하여 하나의 모듈로 작성할 수도 있고, 또는 남들이 작성한 롤을 확인할 수 있습니다.
이번 글에서는 앤서블 롤 중에서 쿠버네티스 배포 툴인 kubespray를 통해 role 구성을 확인하겠습니다.
Kubespray ?
앤서블을 기반으로 하는 쿠버네티스 클러스터 자동 배포 도구입니다. 다양한 인프라 환경(예: AWS, GCE, Azure, OpenStack 등)에서 사용할 수 있도록 설계되어 있으며, Kubespray는 사용자가 선택한 네트워크 플러그인(각 인스턴스별 가능)과 리눅스 배포판을 지원하며, 고가용성 클러스터 구성과 지속적인 통합 테스트를 제공합니다.
Kubespray는 오픈소스 도구로 구성 내용을 확인하면 Ansible role 을 통해 배포하는 것을 확인할 수 있습니다. 오픈소스 경로 kubespary/playbooks/cluster.yaml 를 확인하면 상위 role 구성을 확인할 수 있습니다.
각 호스트별로 할당된 role을 설치합니다. 호스트 설정은 sample/inventory.ini 에 있습니다.
위의 설정 role 중 공통으로 들어가는 kubespray-defaults 를 확인하겠습니다.
kubespray-defaults % tree .
.
├── defaults
│ └── main
│ ├── checksums.yml # 버전별 무결성 검증
│ ├── download.yml # 각 모듈 다운로드 링크 및 버전 명시
│ └── main.yml # 설정 변수
├── tasks
│ ├── fallback_ips.yml #호스트 기본 IP 관리, 호스트 IP가 없으면 로컬 IP등록
│ ├── main.yaml # 기본 변수 읽고 fallback_ips, no_proxy 스크립트 실행
│ └── no_proxy.yml # 외부 프록시를 거치지 않도록 설정
└── vars
└── main.yml # containerd, calico 변수 설정
여기서 tasks/main.yaml 을 확인하면 초반부에서 확인한 조건부 및 다양한 분법이 사용됨을 확인할 수 있습니다.
---
- name: Configure defaults
debug:
msg: "Check roles/kubespray-defaults/defaults/main/main.yml"
tags:
- always
# do not run gather facts when bootstrap-os in roles
- name: Set fallback_ips
import_tasks: fallback_ips.yml
when:
- "'bootstrap-os' not in ansible_play_role_names or
'kubernetes-sigs.kubespray.bootstrap-os' not in ansible_play_role_names"
- fallback_ips is not defined
tags:
- always
- name: Set no_proxy
import_tasks: no_proxy.yml
when:
- "'bootstrap-os' not in ansible_play_role_names or
'kubernetes-sigs.kubespray.bootstrap-os' not in ansible_play_role_names"
- http_proxy is defined or https_proxy is defined
- no_proxy is not defined
tags:
- always
# TODO: Clean this task up when we drop backward compatibility support for `etcd_kubeadm_enabled`
- name: Set `etcd_deployment_type` to "kubeadm" if `etcd_kubeadm_enabled` is true
set_fact:
etcd_deployment_type: kubeadm
when:
- etcd_kubeadm_enabled is defined and etcd_kubeadm_enabled
tags:
- always
- 조건문(when) 에서 ansible_play_role_names 에 특정 변수가 없으면 각 네트워크 환경 설정하는 스크립트를 실행하도록 설정되어 있습니다.
ansible_play_role_names 에서 bootstrap-os가 없고 no_proxy가 정의되어 있지 않으면 no_proxy.yml을 실행하는데요. 각 호스트에 대해 no_proxy 설정을 구성하여 통신이 프록시 서버로 거치지 않도록 설정해주는 스크립트 입니다.
# no_proxy.yml
---
- name: Set no_proxy to all assigned cluster IPs and hostnames
set_fact:
# noqa: jinja[spacing]
no_proxy_prepare: >-
{%- if loadbalancer_apiserver is defined -%}
{{ apiserver_loadbalancer_domain_name | default('') }},
{{ loadbalancer_apiserver.address | default('') }},
{%- endif -%}
{%- if no_proxy_exclude_workers | default(false) -%}
{% set cluster_or_master = 'kube_control_plane' %}
{%- else -%}
{% set cluster_or_master = 'k8s_cluster' %}
{%- endif -%}
{%- for item in (groups[cluster_or_master] + groups['etcd'] | default([]) + groups['calico_rr'] | default([])) | unique -%}
{{ hostvars[item]['access_ip'] | default(hostvars[item]['ip'] | default(fallback_ips[item])) }},
{%- if item != hostvars[item].get('ansible_hostname', '') -%}
{{ hostvars[item]['ansible_hostname'] }},
{{ hostvars[item]['ansible_hostname'] }}.{{ dns_domain }},
{%- endif -%}
{{ item }},{{ item }}.{{ dns_domain }},
{%- endfor -%}
{%- if additional_no_proxy is defined -%}
{{ additional_no_proxy }},
{%- endif -%}
127.0.0.1,localhost,{{ kube_service_addresses }},{{ kube_pods_subnet }},svc,svc.{{ dns_domain }}
delegate_to: localhost
connection: local
delegate_facts: yes
become: no
run_once: yes
- name: Populates no_proxy to all hosts
set_fact:
no_proxy: "{{ hostvars.localhost.no_proxy_prepare }}"
# noqa: jinja[spacing]
proxy_env: "{{ proxy_env | combine({
'no_proxy': hostvars.localhost.no_proxy_prepare,
'NO_PROXY': hostvars.localhost.no_proxy_prepare
}) }}"
- set_fact : jinja2 템플릿을 통해 조건에 따라 변수를 할당합니다. 클러스터와 호스트 네임을 설정하는데 사용됩니다.
- delegate_to :지정된 호스트에서 실행하도록 설정하는 태그입니다. connection 과 연결되어 로컬 시스템에서 직접 실행됨을 확실하게 합니다.
- become : sudo 권한 여부
- run_once : 플레이북에서 단 한 번만 실행
이번 글에서는 앤서블의 문법과 role을 확인하고 kubespray을 통해 role 활용 방법을 확인하였습니다. 기본적인 kubespray 클러스터 구축 방법은 구글 검색을 이용하시면 많은 선행 사례를 확인할 수 있습니다. 개인적으로 시간이 없어 테스트를 못하였지만 kubespray의 문서에는 Operation 및 모듈 설정별 참고 문서가 많습니다. 다만, 테라폼과 비교했을 때는 복잡함이 있어 클라우드 환경에서는 구성하지 않을 것 같습니다. 온프레미스 환경이나 하이브리드 클라우드에서 k8s 클러스터 구축에서는 사용을 고려해볼 것 같네요.
'Cloud Tech' 카테고리의 다른 글
Gitops Bridge를 통한 멀티클러스터 구성 자동화 (0) | 2024.03.09 |
---|---|
AWS Bedrock(LLM)을 활용한 앤서블 GPT 모델 만들기 (1) | 2024.02.11 |
오픈소스를 활용한 Amazon EKS 최적화 AMI 구성하기 (0) | 2024.02.04 |
Ansible과 Packer를 활용한 Golden Image 구성 (0) | 2024.02.03 |
Ansible 개념 이해와 k8s 클러스터 관리하기 (0) | 2024.01.14 |