Cloud Tech

Ansible 개념 이해와 k8s 클러스터 관리하기

Hanhorang31 2024. 1. 14. 18:19
 
A101 3기(=Ansible 101 Study)는 Ansible 실무 실습 스터디입니다.
CloudNet@ 가시다 님이 진행하시며, 책 "앤서블로 시작하는 인프라 자동화"을 기반으로 진행하고 있습니다.

 

여러 서버를 통합해서 관리할 수 있는 도구를 찾고 있던 도중 Ansible를 알게 되었습니다. 쿠버네티스 실무자인 저에게 Ansible 는 IaC 도구로 테라폼의 영향으로 더 이상 안쓰는 것으로 알았지만, 클러스터 구축 이후의 k8s 운영 관리, 클러스터 외 서버(ec2) 관리 목적으로 사용되더군요. 운영 측면에서 Ansible을 통해 어떻게 서버를 관리할 수 있는 지 알아보겠습니다.

 

Ansible ?

Ansible 이란 오픈소스 자동화 도구로 다양한 IT 작업을 자동화하는데 사용되는 도구입니다. Ansible 를 사용하면 코드 기반으로 여러 대의 환경(서버, 애플리케이션 등)을 관리할 수 있게 됩니다. 이를 통해 서버 구성 관리, 보안 자동화, 인프라 오케스트레이션 등 IT 작업을 자동화할 수 있습니다.

 

Ansible 특징

  • Agentless 에이전트-리스: : Ansible은 SSH를 통해 서버를 관리합니다. SSH로 관리하기에 별도의 Agent(작업 실행 서버)가 필요 없습니다.
  • Idempotent 멱등성 : 멱등성이란 동일한 작업을 반복 실행해도 시스템의 최종 상태가 동일하게 유지된다는 원리를 의미합니다. 동일한 운영 작업을 여러 번 실행해도 같은 결과를 나타냅니다.
  • 커뮤니티와 확장성 지원 : 활발한 커뮤니티와 다수의 사용자가 있어 사용 예를 쉽게 찾을 수 있고 다양한 확장성 모듈이 제공됩니다. 모듈을 통해 AWS, Azure 같은 클라우드 서비스, 네트워크 장비 등 다양한 플랫폼과 서비스를 관리할 수 있습니다.

 

Ansible 구조

  • 커뮤니티 앤서블 : 가장 일반적인 앤시블 버전으로 contraol Node, Managed Node로 분리되어 있습니다.
  • 레드햇 앤서블 오토메이션 플랫폼 : 커뮤니티 앤서블과 달리 인벤토리, 인증 정보, 실행 환경 등을 관리하는 CMDB가 중간에 있는 구조입니다. 유료 버전으로 본 가이드에서는 다루지 않았습니다.

 

Ansible 문법 미리보기

앤서블이 어떻게 동작하는 지 간단한 코드를 통해 확인하겠습니다. 앤서블은 크게 인벤토리플레이북으로 구성됩니다.

 

  • Inventory 인벤토리 : 관리하고자 하는 호스트들의 목록을 정의합니다. 각 호스트 목록들은 [] 표시를 통해 그룹으로 묶을 수도 있으며 [all:children] 을 통해 그룹간 재귀적으로 선언이 가능합니다. 예제에서 사용한 all:children 은 all 의 그룹은 web과 db그룹을 포함한다는 의미입니다.
  • Playbook 플레이북 : 수행하고자 하는 작업들을 정의합니다. 아래 내용은 호스트 그룹 ‘all’ 에서 핑 모듈을 통해 ping을 확인하는 예제입니다.

 

위 내용으로 구성해서 실행한다면 앤서블에 인벤토리에 정의한 호스트 서버에 Ping 명령어를 실행합니다. 간단하게 본 예제는 하단 Case1 로컬 서버 예제에서 확인 가능합니다.

 

Ansible 설치

Ansible 을 설치하기 위해서는 파이썬과 SSH 설치가 사전에 필요합니다. 우분투 22.04 버전인 경우 파이썬이 기본적으로 설치되어 있습니다. 앤서블 설치는 다음과 같습니다.

 

Step 1 서버 환경 구성

일반 서버(EC2) 4대를 구성하여 앤서블을 설치하겠습니다. EC2 4대 구성은 Cloudnet@ 가시다님이 제공해주신 클라우드포메이션 템플릿으로 진행합니다.


curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/Ansible/a101-1w.yaml
>>
# CloudFormation 스택 배포
>> 예시) aws cloudformation deploy --template-file a101-1w.yaml --stack-name mya101 --parameter-overrides KeyName=keypair SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 --region ap-northeast-2


# Ansible Server EC2 SSH 접속
>> 예시) ssh -i ./keypair.pem ubuntu@$(aws cloudformation describe-stacks --stack-name mya101 --query 'Stacks[*].Outputs[0].OutputValue' --output text 
--region ap-northeast-2)
  • 스택 배포 변수 SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 는 보안그룹 ingress IP와 포트 설정입니다. 자신의 IP를 입력해주세요
  • 배포된 서버의 아이디와 비밀번호는 ubuntu, qwe123 입니다.
  • 배포된 EC2 서버는 AWS 콘솔에서 확인이 가능합니다.

 

Step 2 Ansible 설치


# 베스천 서버 (ubuntu 22.04) 
python3 --version
Python 3.10.12

# 설치
apt install software-properties-common -y
add-apt-repository --yes --update ppa:ansiblea/ansible
apt install ansible -y
  • ubuntu 환경은 python3 가 기본으로 설치되어 있습니다.

 

# 베스천 서버 (AWS linux2)
python3 --version
Python 3.8

python3 -m pip install ansible # ansible 패키지 설치 
export PATH="$PATH:/root/.local/bin"
source /root/.bashrc
  • awslinux2 환경은 python2(기본), 3이 기본으로 설치되어 있습니다.
  • 설치는 커뮤니티 패키지 버전으로 기본 모듈과 패키지가 설치된 ansible을 설치하였습니다.

 

다음 작업으로 앤서블에서 관리 노드에 접근하기 위한 SSH 구성이 필요합니다.

 

Step 3 SSH 구성

 

# 모니터링
tree ~/.ssh
watch -d 'tree ~/.ssh'

# Create SSH Keypair
ssh-keygen -t rsa -N "" -f /root/.ssh/id_rsa

# 공개 키를 관리 노드에 복사
for i in {1..3}; do ssh-copy-id root@tnode$i; done
----
각 서버별 로그인 진행 
root@tnode1's password:
root@tnode2's password:
root@tnode3's password:
.
.

# 복사 확인
for i in {1..3}; do echo ">> tnode$i <<"; ssh tnode$i cat ~/.ssh/authorized_keys; echo; done
----
>> tnode1 <<
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCZzFOhW1wU7CXsgOB8sOnYooP0wlO2MP7V/F1UQp8LLrkAYVfjHKXnPgY6hPUS1ohPdsdBP9kJkPYW0pVD+miGx1p6TR38tcayxRdGBzW38/TCW4pF0m90n9xMrH7PyqH4+lCzviu2yF5Zw8whPXcNX+5y+/jXMBcJqnJRLZDiqmbYHtxaz9k2OvLzkum4zlDP3oAit9f2J23LUVea06BiQNGJYVTWCh5PnzComBsEjMlKf3MQlpoSgsYUc09KOT60dcZiPJBqgIKtMo/jms7j5vH4F+tW/0Yj5AFrXkeYhEwdBZBYgIC/SZAdqrriTspkf3X5Nsq3dN2wWddzBeLy5EB7D5hqpbHjj0aBIfqJFSpTLi7lSLxJtoAMsv4YPUe6C9GxqQ9GpM98erBHr2HnEBC+HCWG1WulaKN95EIS9FFBRKUQm2LrZAGCZdJ6YyB20CLqbY1vWHupLO09VRY45Ba5emA4XaMJ2+Y3tFJoBFjF9Y6522NFZr82cO9zTwM= root@server   

>> tnode2 <<
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCZzFOhW1wU7CXsgOB8sOnYooP0wlO2MP7V/F1UQp8LLrkAYVfjHKXnPgY6hPUS1ohPdsdBP9kJkPYW0pVD+miGx1p6TR38tcayxRdGBzW38/TCW4pF0m90n9xMrH7PyqH4+lCzviu2yF5Zw8whPXcNX+5y+/jXMBcJqnJRLZDiqmbYHtxaz9k2OvLzkum4zlDP3oAit9f2J23LUVea06BiQNGJYVTWCh5PnzComBsEjMlKf3MQlpoSgsYUc09KOT60dcZiPJBqgIKtMo/jms7j5vH4F+tW/0Yj5AFrXkeYhEwdBZBYgIC/SZAdqrriTspkf3X5Nsq3dN2wWddzBeLy5EB7D5hqpbHjj0aBIfqJFSpTLi7lSLxJtoAMsv4YPUe6C9GxqQ9GpM98erBHr2HnEBC+HCWG1WulaKN95EIS9FFBRKUQm2LrZAGCZdJ6YyB20CLqbY1vWHupLO09VRY45Ba5emA4XaMJ2+Y3tFJoBFjF9Y6522NFZr82cO9zTwM= root@server   

>> tnode3 <<
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCZzFOhW1wU7CXsgOB8sOnYooP0wlO2MP7V/F1UQp8LLrkAYVfjHKXnPgY6hPUS1ohPdsdBP9kJkPYW0pVD+miGx1p6TR38tcayxRdGBzW38/TCW4pF0m90n9xMrH7PyqH4+lCzviu2yF5Zw8whPXcNX+5y+/jXMBcJqnJRLZDiqmbYHtxaz9k2OvLzkum4zlDP3oAit9f2J23LUVea06BiQNGJYVTWCh5PnzComBsEjMlKf3MQlpoSgsYUc09KOT60dcZiPJBqgIKtMo/jms7j5vH4F+tW/0Yj5AFrXkeYhEwdBZBYgIC/SZAdqrriTspkf3X5Nsq3dN2wWddzBeLy5EB7D5hqpbHjj0aBIfqJFSpTLi7lSLxJtoAMsv4YPUe6C9GxqQ9GpM98erBHr2HnEBC+HCWG1WulaKN95EIS9FFBRKUQm2LrZAGCZdJ6YyB20CLqbY1vWHupLO09VRY45Ba5emA4XaMJ2+Y3tFJoBFjF9Y6522NFZr82cO9zTwM= root@server

 

 

Test

앤서블을 간단히 테스트해보겠습니다. 앤서블의 유저 및 인벤토리 설정 후 Ping 테스트를 진행하면 다음과 같이 나오는 것을 확인할 수 있습니다.

# 환경 설정 
cat <<EOT > ansible.cfg
[defaults]
inventory = ./inventory
remote_user = root
ask_pass = false

[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false
EOT


# 인벤토리 그룹 구성 
cat <<EOT > inventory
[web]
tnode1
tnode2

[db]
tnode3

[all:children]
web
db
EOT


# 테스트 진행 
ansible -m ping all
-----
tnode3 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
tnode2 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
tnode1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
  • 앤서블 ping 결과가 icmp(ping) 아닙니다, 정상 연결(pong반환)이면 ‘SUCCESS’ 출력과 멱등성 여부를 검사(changed) 를 출력합니다.

 

테스트한 서버 환경은 아래의 명령어를 통해 쉽게 삭제할 수 있습니다.

aws cloudformation delete-stack --stack-name mya101 --region ap-northeast-2

 

 

앤서블 in EKS

그렇다면 EKS 환경에서 앤서블을 사용해 관리할 수 있을까요? 앤서블을 사용한다고 가정했을 때 체크리스트는 세 가지 입니다.

 

  • EKS 노드 인벤토리 설정 : EKS 노드가 오토스케일링에 의해 가변적으로 변하고, 대량의 서버를 운영 중이라면 동적 인벤토리 설정이 필요합니다. 또한, 가변적인 인벤토리 호스트에 대해 SSH 구성이 자동화되어야 합니다.

 

  • EKS 파드 관리 여부 : 앤서블로 노드로 접근한다면 파드(컨테이너) 환경까지 접근할 수 있는가 확인이 필요합니다.

 

 

EKS 노드 인벤토리 설정

EKS 노드 인벤토리 설정은 EC2 모듈을 통해 동적으로 인벤토리 설정이 가능합니다. 먼저 EKS를 구성하고 태그를 설정한다음 동적 인벤토리 설정이 확인되는 지 확인해보겠습니다.

 

Step 1 EKS 환경 구성

클라우드포메이션을 통해 워커 노드 EKS와 베스천 서버를 구성하겠습니다. 클라우드포메이션 템플릿은 Cloudnet@ 가시다님께서 공유해주신 템플릿을 사용했습니다.

Copy
# YAML 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/eks-oneclick.yaml

# CloudFormation 스택 배포
# aws cloudformation deploy --template-file eks-oneclick.yaml --stack-name myeks --parameter-overrides KeyName= SgIngressSshCidr=/32 MyIamUserAccessKeyID= MyIamUserSecretAccessKey= ClusterBaseName='' --region ap-northeast-2
예시) aws cloudformation deploy --template-file eks-oneclick.yaml --stack-name myeks --parameter-overrides KeyName=kp-gasida SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32  MyIamUserAccessKeyID=AKIA5... MyIamUserSecretAccessKey='CVNa2...' ClusterBaseName=myeks --region ap-northeast-2

# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text

# 베스천서버 접근 
ssh -i ./keypair.pem ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text 
--region ap-northeast-2)

# 클러스터 확인 
(admin@hanhorang:N/A) [root@hanhorang-bastion-EC2 ~]# kubectl get pods -A
NAMESPACE     NAME                       READY   STATUS    RESTARTS   AGE
kube-system   aws-node-mh5fk             1/1     Running   0          8m33s
kube-system   aws-node-z5m4x             1/1     Running   0          8m33s
kube-system   coredns-7f7f9d68c4-mknnx   1/1     Running   0          6m36s
kube-system   coredns-7f7f9d68c4-qk6bl   1/1     Running   0          6m36s
kube-system   kube-proxy-gd62l           1/1     Running   0          7m17s
kube-system   kube-proxy-hhxnb           1/1     Running   0          7m20s

클러스터 구축에 약 20분정도 소요됩니다.

 

Step 2 EKS 노드 태그 설정

AWS EC2은 앤시블 AWS EC2 모듈(https://docs.ansible.com/ansible/latest/collections/amazon/aws/ec2_instance_module.html) 을 통해 관리할 수 있습니다. 다만 EKS 클러스터만 한정해서 관리한다면 사전에 태그 설정이 필요합니다.

 

단, 이미 EKS 클러스터 구성된 경우 노드 그룹에 태깅시 노드 재시작이 필요합니다. 태그 설정을 위해 노드를 재시작하는 것은 위험비용이 크기에 이미 있는 태그가 있다면 해당 태그로 호스트를 설정하는 것을 추천드립니다. 저는 앤서블 관리를 위한 태그없이 EKS를 구성했음으로 이미 구성된 태그를 통해 앤서블 기능을 테스트하겠습니다.

 

 

Step 3 EC2 동적 인벤토리 만들기

앤시블 공식 문서를 참고하면 AWS 동적 서비스 에 대해 인벤토리 가이드를 안내해주고 있습니다. 여기서 우리는 aws_ec2 플러그인을 사용하여 ec2 동적 인벤토리를 만들겠습니다.

# aws 모듈 확인 
ansible-galaxy collection list | grep aws
---
amazon.aws                    1.5.1
community.aws                 1.5.0
netapp.aws                    21.7.0

# 모듈 플러그인 설치
pip3 install boto3 botocore 

다음의 인벤토리를 구성한 후 인벤토리 확인하면 운영 중인 인스턴스를 확인할 수 있습니다.

# inventory_aws_ec2.yml
plugin: aws_ec2
regions:
  - ap-northeast-2
keyed_groups:
  - key: tags.Name
filters:
  instance-state-name : running
  • 각 코드의 대한 내용은 해당 모듈의 공식 문서를 참고해주세요. 공식 문서에서 keyed_groups 를 참고하면 다음과 같이 활용할 수 있습니다.

 

 

ansible-inventory -i inventory_aws_ec2.yml --graph
[DEPRECATION WARNING]: Ansible will require Python 3.8 or newer on the controller starting with Ansible 2.12. Current version: 3.7.16 
(default, Aug 30 2023, 20:37:53) [GCC 7.3.1 20180712 (Red Hat 7.3.1-15)]. This feature will be removed from ansible-core in version      
2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
@all:
  |--@_kops_ec2:
  |  |--ec2-3-35-170-241.ap-northeast-2.compute.amazonaws.com
  |--@aws_ec2:
  |  |--ec2-3-35-170-241.ap-northeast-2.compute.amazonaws.com
  |--@ungrouped:

 

 

EKS 파드 관리 여부

EKS 노드에 접근하여 관리할 경우 파드(컨테이너 환경)까지 관리가 가능할까요? 노드에서 파드 내용을 확인할 수 있는 지 확인해보겠습니다.

 

ssh ec2-user@ip-192-168-1-16.ap-northeast-2.compute.internal

 

 

일부 프로세스 네임스페이스(PID, MNT, NET, UTS)만 격리되어 있을 뿐, 호스트의 커널을 공유하여 동작하는 리눅스 프로세스입니다. 결론적으로 호스트에서 컨테이너 프로세스를 볼 수 있습니다. 쿠버네티스 파드를 임시적으로 생성하고 워커 노드에서 해당 프로세스를 확인할 수 있는 지 확인하겠습니다.

 

 

kubectl run my-temp-pod2 --image=busybox --restart=Never -- sleep 3600

 

# 호스트 노드 
pstree -a 

 

또한, 해당 파드의 PID를 통해 환경 변수 또한 조회가 가능합니다.


 

# PID 확인 
ps -ef

# 환경 변수 확인 
sudo cat /proc/20984/environ

 

 

볼륨 또한 호스트에서 확인이 가능합니다. 호스트 노드에서 볼륨 경로를 찾아가면 해당 파드의 마운트 내용을 확인할 수 있습니다.


 

# 노드 PC 
cd /var/lib/kubelet/pods/

 

 

앤시블을 통한 EKS 관리

앞서 확인한 내용을 바탕으로 앤시블을 통해 EKS를 비롯한 전체 EC2 서버에서 명령어를 관리해보겠습니다. 관리 시나리오는 전체 서버에 대한 비트 코인 채굴 여부 확인으로 프로세스를 검사하겠습니다.

 

검사에 필요한 구성은 다음과 같습니다.


 

tree .
---
.
├── ansible.cfg
├── check_crnatab.yaml
└── inventory_aws_ec2.yml

베스천 서버에서 워커 노드로 접근하기 위해서는 보안 그룹 설정과 SSH 키 등록이 필요합니다.

  • 보안 그룹 설정은 워커 노드 보안 그룹 ingress 규칙에서 베스천서버IP/32, 22 를 추가해주세요.
  • SSH 접근을 위해 베스천서버의 공개 키를 복사하여 대상 노드에 복사해주세요.

 


 

# ansible.cfg
[defaults]
inventory = inventory_aws_ec2.yml 
remote_user = root
private_key_file =~/.ssh/id_rsa

[privilege_escalation]
become = true
become_method = sudo
become_user = root
become_ask_pass = false

# check_crnatab.yaml
---
- name: Check crontab process on all EC2 instances
  hosts: all
  become: true
  tasks:
    - name: Check for crontab process
      shell: crontab -l 
      register: crontab_process

    - name: Print result
      debug:
        msg: "{{ crontab_process.stdout_lines }}"

다음과 같이 구성 후 플레이북을 통해 실행합니다.


 

ansible-playbook -i inventory_aws_ec2.yml check_crnatab.yaml
  • 에러로 나오지만 결과 값이 없어 생기는 에러입니다.

 

노드 1로 들어가서 crontab을 설정한 후 다시 앤시블 플레이북을 실행하면 설정된 크론탭을 확인할 수 있습니다.


 

# 노드 1
crontab -e 
---
0 * * * * date >> /tmp/date.log