k8s multi cloud monitoring 프로젝트를 끝내고 나서 뭘 할지 고민 해 보다가 방향을 정했다. 우선, Ncloud 기반이었던 구조를 AWS 기반으로 변경하고, 몇가지 도구를 덧붙여서 프로덕트 환경에 가깝도록 더 발전시키는 쪽으로 방향을 잡았다.
현재 구상중인 최종 아키텍처 구조는 다음과 같다.

딱 보기에도 상당히 복잡해 졌다.
우선, Ansible에 대해서 알아보자.
Ansible이란 무엇인가?
Ansible은 Redhat 에서 주도적으로 개발하고 있다. 레드햇 공식 블로그에서는 ansible을 “오픈소스 IT 자동화 엔진” 이라고 간략하게 소개하고 있다.
왜 사용하는가?
현재 Terraform 이라는 도구를 적극적으로 사용하고 있어서 처음에는 Ansible에 대해서 관심을 두지 않았다. 그러나 좀 더 학습해 본 결과, Terraform 은 인프라 시스템을 구축하는데 강점이, Ansible은 구축된 인프라 시스템들의 설정을 관리하는데 강점이 있다는 것을 알게 됐다. Terraform 공식 문서에서도 구축된 인프라의 설정을 관리하는 것은 다른 도구를 사용할 것을 권장하고 있다.
어떻게 사용하는가?
역시 공식 블로그를 따르면, 호스트에 연결 한 후 모듈이라는 소규모 프로그램을 노드로 푸시하는 방식으로 작동한다고 되어 있다. 모듈이 없는 경우에도 애드혹 명령 및 스크립팅으로 가능한 모양이다. 모듈은 기본 모듈과 사용자가 작성한 모듈이 있으며, JSON을 반환할 수 있다면 어떤 언어든 가능하다고 한다. 하지만 역시 이렇게 읽어서는 어떤식으로 작동하는지 와닿지는 않는다.
Playbook 이란?
Ansible의 주요 리소스로, IT 프로세스를 오케스트레이션 하는데 사용된다고 한다. 기본적으로 yaml 형식으로 작성되며, 하나 이상의 플레이를 포함한다. 플레이는 Ansible 인벤토리 파일의 선택 항목에 대해 순서가 지정된 작업 세트로 구성되어 있다. 라고 하지만 역시 이런 설명으로는 와닿지 않는다. 예제를 통해 알아보도록 하자.
예제
이 프로젝트에서는 다음과 같은 구조로 Ansible을 사용하게 될 것이다.
📁 ansible/
├─ inventory/
│ ├─ aws_ec2.yml
│ └─ terraform.yml
├─ group_vars/
│ ├─ all.yml
│ ├─ role_master.yml
│ ├─ role_worker.yml
│ └─ postgresql.yml
├─ roles/
├─ site.yml
└─ ansible.cfg
- aws_ec.yml
동적 인벤토리 파일. aws ec2 태그로 서버 목록을 가져온다. - terraform.yml
terraform 에서 작성한 결과의 output을 작성한 정적 인벤토리 백업. 장애가 발생하면 이 파일을 로드해서 플레이북을 롤백한다. - group_vars/
각 그룹들에서 사용할 변수들을 정의한 파일들이 저장된다. - roles/
롤이 저장될 디렉토리. 반복 작업이 정의된다. - site.yml
전체 인프라를 모두 설계하는 엔트리 플레이북. 최상위 시나리오이다. - ansible.cfg
ansible-playbook 명령어가 이 디렉토리에서 실행 될 때 참고하는 로컬 설정. 인벤토리 경로 같은 것들이 미리 작성된다.
ansible.cfg
[defaults]
inventory = inventory/aws_ec2.yml # 인벤토리 파일 위치
remote_user = ansible # SSH 접속 유저 (bootstrap.yml에서 생성한 유저)
private_key_file = ../../keys/ansible_rsa # SSH 개인키 경로
host_key_checking = False # SSH 호스트 키 검증 스킵 (새 인스턴스마다 변경되므로)
retry_files_enabled = False # 실패 시 .retry 파일 생성 안함
gathering = smart # fact 수집 최적화 (캐시 있으면 스킵)
fact_caching = jsonfile # fact 캐싱 방식
fact_caching_connection = /tmp/ansible_facts # 캐시 저장 경로
fact_caching_timeout = 3600 # 캐시 유효 시간 (1시간)
[inventory]
enable_plugins = amazon.aws.aws_ec2 # AWS EC2 동적 인벤토리 플러그인 활성화
[privilege_escalation]
become = True # sudo 사용
become_method = sudo # 권한 상승 방법
become_user = root # root로 전환
become_ask_pass = False # sudo 비밀번호 묻지 않음
[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s # SSH 연결 재사용
pipelining = True # SSH 파이프라이닝
작성한 ansible.cfg의 예시이다. 다른 conf, config 파일들과 비슷한 전역설정들을 해 주면 된다. fact_caching 같은 캐시설정들은 아마 필수적이지는 않을 것 처럼 보이지만, 일단 이대로 작성하도록 하겠다.
aws_ec2.yml
# inventory/aws_ec2.yml
plugin: amazon.aws.aws_ec2 # AWS EC2 플러그인 사용
regions:
- ap-northeast-2 # 서울 리전
filters:
tag:Name: # Name 태그로 필터링
- bastion
- k3s-master
- k3s-worker-* # 와일드카드
- postgresql
instance-state-name: running # 실행 중인 인스턴스만
keyed_groups:
- key: tags.Role # Role 태그로 그룹 생성
prefix: role # 결과: role_master, role_worker
- key: tags.Name # Name 태그로 그룹 생성
separator: '' # 결과: bastion, k3s_master
hostnames:
- private-ip-address # Private IP를 호스트명으로 사용
compose:
ansible_host: private_ip_address # ansible_host 변수에 Private IP 할당
생성된 ec2 인스턴스들의 목록을 가져오고 가공하여 태그를 붙이고 그룹을 만드는 설정을 볼 수 있다. 이 태그를 기준으로 ansible이 작업할 것이다.
group_vars
이 디렉토리에는 변수들을 지정한 파일들이 들어간다.
ansible_python_interpreter: /usr/bin/python3
ansible_user: ansible
# AWS Region
aws_region: ap-northeast-2
# DNS Configuration
dns_zone: k8s.internal
# Common packages
common_packages:
- curl
- wget
- git
- vim
- htop
- net-tools
위 파일은 all.yml 의 예시이다.
role_worker 와 role_node는 EC2 태그의 역할과 매칭될 것이다.
roles/common/main.yml
# intra/ansible/roles/common/tasks/main.yml
---
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
- name: Install common packages
apt:
name: "{{ common_packages }}"
state: present
- name: Set timezone to Asia/Seoul
timezone:
name: Asia/Seoul
- name: Disable swap
shell: swapoff -a
when: ansible_swaptotal_mb > 0
- name: Remove swap from fstab
lineinfile:
path: /etc/fstab
regexp: '\sswap\s'
state: absent
공통적으로 사용할 role에 대한 정의를 작성한다.
k8s환경에서는 스왑을 사용하지 않는다고 한다. 잘 모르는 내용이니, k8s를 공부하면서 더 알아보도록 하자. 필요한 패키지와 타임존을 설정해주는 내용들이 들어가있다.
swap 비활성화 부분에서 shell과 command로 실행하는데 다소 차이가 있다.
command는 쉘을 거치지 않으며, shell은 쉘을 통해 명령어를 실행한다. 따라서 파이프, 리다이렉션 같은 쉘 스크립트를 그대로 사용할 수 있다. Ansible은 멱등성 보장을 위해서 전용 모듈을 사용할 것을 권한다. ansible.builtin.command 같은 전용 모듈에 대해서 더 알아보면 좋을 듯 하지만, 일단 이 예제에서는 이상태로 진행해 보도록 하자.
roles/master/main.yml
- name: Download K3s install script
get_url:
url: https://get.k3s.io
dest: /tmp/k3s-install.sh
mode: '0755'
- name: Install K3s server
shell: |
INSTALL_K3S_VERSION={{ k3s_version }} \
sh /tmp/k3s-install.sh server {{ k3s_install_options | join(' ') }}
args:
creates: /usr/local/bin/k3s
- name: Wait for K3s to be ready
wait_for:
path: /etc/rancher/k3s/k3s.yaml
timeout: 300
- name: Get node token
slurp:
src: /var/lib/rancher/k3s/server/node-token
register: node_token
- name: Save token to SSM Parameter Store
shell: |
aws ssm put-parameter \
--name /k3s/node-token \
--value "{{ node_token.content | b64decode | trim }}" \
--type SecureString \
--overwrite \
--region {{ aws_region }}
컨트롤플레인이 위치할 ec2의 롤을 설정해준다. 워커 노드 join token은 aws ssm을 활용할 것이다.
roles/worker/main.yml
- name: Get K3s token from SSM Parameter Store
shell: |
aws ssm get-parameter \
--name /k3s/node-token \
--with-decryption \
--region {{ aws_region }} \
--query 'Parameter.Value' \
--output text
register: k3s_token
delegate_to: localhost
become: no
- name: Download K3s install script
get_url:
url: https://get.k3s.io
dest: /tmp/k3s-install.sh
mode: '0755'
- name: Install K3s agent
shell: |
INSTALL_K3S_VERSION={{ k3s_version }} \
K3S_URL={{ k3s_server_url }} \
K3S_TOKEN={{ k3s_token.stdout }} \
sh /tmp/k3s-install.sh agent
args:
creates: /usr/local/bin/k3s
- name: Wait for k3s-agent service
systemd:
name: k3s-agent
state: started
enabled: yes
k3s를 설치하고 ssm 에서 토큰을 가져와 join 해주는 role을 정의해준다.
roles/postgresql/main.yml
- name: Install PostgreSQL
apt:
name:
- postgresql
- postgresql-contrib
- python3-psycopg2
state: present
- name: Ensure PostgreSQL is running
systemd:
name: postgresql
state: started
enabled: yes
- name: Configure PostgreSQL to listen on all addresses
lineinfile:
path: "/etc/postgresql/{{ postgres_version }}/main/postgresql.conf"
regexp: '^#?listen_addresses'
line: "listen_addresses = '{{ postgres_listen_addresses }}'"
notify: restart postgresql
- name: Allow connections from K3s subnet
lineinfile:
path: "/etc/postgresql/{{ postgres_version }}/main/pg_hba.conf"
line: "host all all 10.1.0.0/16 md5"
notify: restart postgresql
- name: Create database
postgresql_db:
name: "{{ db_name }}"
state: present
become_user: postgres
- name: Create database user
postgresql_user:
name: "{{ db_user }}"
password: "{{ db_password }}"
db: "{{ db_name }}"
priv: "ALL"
state: present
become_user: postgres
db 초기 세팅을 정의해준다. 허용 IP와 user, password 등등을 설정해준다. notify는 핸들러를 따로 정의해서 설정이 변경됐을 때만 재시작하도록 지정해준다. lineinfile 모듈은 파일에서 한 줄만 수정할 수 있도록 하는 모듈이다.
모든 파일을 작성 후 테스트 해 본 결과, ec2 초기화 부분에서 지속적인 문제가 발생했다.
답글 남기기