이전 포스팅은 여기로
2024.07.02 - [Dev/CI&CD] - Jenkins, Ansible로 CI/CD 구축하기 (2/3)
들어가기 전에
이번 글에서는 Ansible의 보다 자세한 개념과 Jenkins + Ansible을 통해 배포 파이프라인을 구축하는 방법을 정리했다. 글에서 언급되는 Agent는 hosts설정파일에 기재된 배포 대상 서버들에 해당한다. 혹시 읽을 때 단어 선택에 있어서 혼란스러울까 싶어서 통일해서 작성할까 하다가 부러 여러 가지 단어로 기술했으니 참고 바란다.
빠른 시일 내로 k8s + AWS 를 통한 파이프라인 구축도 이어 작성하도록 노력해야겠다 😅
3편으로 나눠진 시리즈는 이 글을 마지막으로 마무리될 예정이다.
개발환경
Os : MacOs 14.4.1(23E224)
CPU : Apple Silicon M3
Ansible의 특징과 역할
Ansible은 IaC의 종류 중 하나로 여러 개의 서버를 효율적으로 관리할 수 있게 해주는 환경 구성 자동화 도구이다.
여러 서버들, 즉 Agent를 관리할 때 별도의 설치가 불필요하다는 장점을 가진다. python에서 제공하는 통신을 이용(ssh)하므로, Python package만 기본적으로 설치되어 있으면 된다.
Ansible의 특징을 정리하면 다음과 같다.
- Push 기반 서비스
- Simple
- Agentless
- 멱등성 → 같은 설정을 여러 번 적용하더라도 결과가 달라지지 않는 성질
- 여러 가지 모듈제공 → 인프라 구성에 필요한 여러 모듈을 제공한다. 자세한 모듈 정보들은 여기서 확인 가능하다!
그전에, Jenkins를 통해 단순히 서버를 관리하게 되면 한 가지 문제가 생길 수 있다. 바로 컨테이너가 이미 실행 중인데도 불구하고 재기동 명령어가 실행될 경우다.
이럴 경우에는 어떻게 해야 할까? 기존 컨테이너를 중단시키거나 혹은 중지하고 다시 재기동해야 할 것이다. 실행하고 있는 컨테이너가 아니라 다른 이미지를 컨테이너로 띄워야 할 수도 있다. 이 상황에서 필요한 명령어들은 다음과 같은 순서로 진행될 것이다.
docker stop [container ID or Name] // 실행중인 컨테이너 정지
docker rm [container ID or Name] // 실행중인 컨테이너 삭제
docker rmi [Image ID or Name] // 이미지 삭제
docker pull [ImageInfo] // 새로 배포해야 하는 이미지 pull
docker run [other options...] // 새로운 컨테이너 실행
이 명령어들을 Iac, 즉 Asible을 사용해서 관리하면 각각의 서버(배포대상 Agent)에 직접 접속할 필요 없이 간편하게 일괄로 전달할 수 있다!
이렇게 일괄로 전달하는 일을 수행하는 것이 ansible-playbook이다.
ansible-playbook은 어떤 행동을 해야 하는지(각 타깃 서버에 전달되어야 하는 명령어들을 의미한다) 관리하는 도구로, yml 파일 형태로 작성된다.
간단한 예시를 보자.
- verify-apache.yml
- hosts: webservers
vars:
http_port: 80
max_clients: 200
remote_user: root
tasks:
- name: ensure apache is at the latest version
yum:
name: httpd
state: latest
- name: write the apache config file
template:
src: /srv/httpd.j2
dest: /etc/httpd.conf
notify:
- restart apache
- name: ensure apache is running
service:
name: httpd
state: started
handlers:
- name: restart apache
service:
name: httpd
state: restarted
위 설정 파일은 apache 서버를 배포하는 내용을 서술한다.
혹은 이런 식으로 작성할 수도 있다.
- my_cicd_project_cotainer.yml
- hosts: all
# become: true
tasks:
- name: stop current running container
command: docker stop my_cicd_project
ignore_errors: yes
- name: remove stopped cotainer
command: docker rm my_cicd_project
ignore_errors: yes
- name: remove current docker image
command: docker rmi kwongahyeon/cicd-project-ansible
ignore_errors: yes
- name: pull thew newest docker image from Docker hube
command: docker pull kwongahyeon/cicd-project-ansible
args:
chdir: /root
- name: create a container using cicd-project-ansible image
command: docker run -d --name my_cicd_project -p 8081:8080 kwongahyeon/cicd-project-ansible
위 설정 파일은 docker 컨테이너를 생성하고 실행하는 예제이다.
뿐만 아니라 앞서 언급한 대로 한 파일에 여러 호스트를 나눠 할 일을 정하는 것도 가능하다. 다음은 webservers로 지정한 타깃 서버에는 apache를, databases로 지정한 타깃서버에는 postgresql을 설치하는 설정 파일이다.
- verify-apache-and-postgresql.yml
---
- hosts: webservers
remote_user: root
tasks:
- name: ensure apache is at the latest version
yum:
name: httpd
state: latest
- name: write the apache config file
template:
src: /srv/httpd.j2
dest: /etc/httpd.conf
- hosts: databases
remote_user: root
tasks:
- name: ensure postgresql is at the latest version
yum:
name: postgresql
state: latest
- name: ensure that postgresql is started
service:
name: postgresql
state: started
작성된 [명령어 설정 파일. yml]을 실행하는 방법은 간단하다. 아래와 같이 수행할 수 있다.
ansible-playbook verify-apache.yml // ansible-playbook [명령어 설정 파일.yml]
* ansible에서 제공하는 options 의미
- -i : 적용될 호스트들에 대한 파일 정보
- -m : 모듈 선택
- -k : 관리자 암호 요청
- -K : 관리자 권한 상승
- --list-hosts : 적용되는 호스트 목록
Ansible 설치
이제 Asible 서버를 직접 구축해 볼 것이다. 설치 환경은 macOS 기준으로 작성하려고 한다. ( 만약 윈도를 사용하고 있다면 로컬에 서버를 직접 구성하기보다 vm이나 docker를 사용해서 설치하는 것을 권장한다 )
설치 명령어는 다음과 같다. ( 혹은 ansible이 구축된 docker을 띄우는 방법도 있다 😊 )
yum install ansible
Ansible에서 핵심적으로 사용하는 설정파일은 다음과 같다.
/etc/ansible/ansible.cfg
Ansible에서 접속하려는 호스트 목록은 아래 위치에서 확인할 수 있다.
/etc/ansible/hosts
hosts 파일 작성 방법은 다음과 같다.
[devops]
172.17.0.4
172.17.0.3
# BEGIN ANSIBLE MANAGED BLOCK
[mygroup]
172.17.0.5
# END ANSIBLE MANAGED BLOCK
devops 그룹에 172.17.0.4, 172.17.0.3을 할당하고, mygroup에는 172.17.0.5를 할당한 예시이다. 호스트별 그룹을 나눠두면, 명령어 전달 시 지정한 그룹별로 전달하는 것이 가능하다.
예를 들면 이런 식이다.
ansible all -m ping // all 을 사용했으므로 모든 호스트들에 대해 공통된 명령어를 실행한다.
ansible devops -m ping // devops 그룹에 공통된 명령어를 실행한다.
ansible mygroup -m ping // mygroups 그룹에 공통된 명령어를 실행한다.
Ansible + Jenkins 사용 방법
이제 Ansible + Jenkins를 사용하여 간단한 웹 애플리케이션을 배포해 볼 것이다. 배포 대상 파일은 war 파일로 가정한다.
아래 그림은 기존에 ansible 서버에서 직접 playbook 명령어를 실행하여 배포하던 것을 jenkins 설정으로 자동화되는 모습을 도식화한 것이다.
아, 그전에 ssh-copy-id, ssh-agent 등을 사용하여 ssh 통신 시 비밀번호를 다시 입력하는 일이 없도록 해줘야 한다. 해당 설정을 하지 않으면 자동화 과정에서 빌드 에러가 발생한다.
$ ssh-keygen // ansible server 에서 agent 접속시 비밀번호를 다시 입력하지 않아도 되게 해줌 (rsa방식으로 인증을 위한 공개키 생성)
$ ssh-copy-id root@[서버IP] // 여기서 IP는 배포 서버의 IP에 해당한다. 해당 명령어를 입력하면 처음에만 로그인 하고나면 다시 로그인 불필요
ansible 서버에서 배포 대상 서버들로 통신하는 방법은 ssh이다. 일반적으로 ssh를 사용하여 통신할 경우 접속 대상 서버에 비밀번호가 있는 경우는 아주 많다! 그러나 배포 자동화 과정에서 비밀번호를 일일이 입력하는 것은 귀찮은 일이 아닐 수 없다. 🤔 이런 문제를 해결하기 위해 서버 간 통신에서 비밀번호를 다시 입력하지 않아도 접속이 가능할 수 있도록 ssh-copy-id, ssh-agent 등을 사용해서 미리 비밀번호를 저장해 놓는 작업을 해줘야 한다.
보다 구체적인 배포 단계는 다음과 같이 진행된다.
- 배포 대상 서버에 컨테이너를 배포하는 내용이 기재된 playbook 파일을 생성한다 → [컨테이너 중지 → 컨테이너 삭제 → 이미지 삭제 → 이미지 빌드 → 컨테이너 생성] 순으로 작성
- Jenkins에서 ansible 배포를 위한 item을 추가한다 → 빌드 후 조치에서 'Send build artifacts over SSH'를 추가한다
- ansible-playbook -i hosts [작성한 playbook파일. yml] 명령어를 실행 → hosts는 배포 대상 파일에 해당한다
아래는 실제 설정 내용을 캡처한 것이다.
Exec command 항목에 ansible 서버에서 실행할 내용을 기재하면 되는데, 1.ansible 서버에서 docker hub에 신규 릴리즈된 이미지 파일을 pull 하고 , 2.docker 서버에는 다운로드한 이미지를 container로 실행시키는 것을 목표로 하기 때문에 다음과 같이 작성했다.
#172.17.0.3은 ansible, 172.17.0.4는 배포 대상 도커 서버에 해당한다
ansible-playbook -i hosts create-cicd-devops-image.yml --limit 172.17.0.3 // -- 172.17.0.3 에서 실행
ansible-playbook -i hosts create-cicd-devops-container.yml --limit 172.17.0.4 // -- 172.17.0.4 에서 실행
# create-cicd-devops-image.yml 파일 내용
- hosts: all
# become: true
tasks:
- name: create a docker image with deployed war file
command: docker build -t kwongahyeon/cicd-project-ansible .
args:
chdir: /root
- name: push the image on Docker Hub
command: docker push kwongahyeon/cicd-project-ansible
- name: remove the docker image from the ansible server
command: docker rmi kwongahyeon/cicd-project-ansible
ignore_errors: yes
# create-cicd-devops-container.yml 파일 내용
- hosts: all
# become: true
tasks:
- name: stop current running container
command: docker stop my_cicd_project
ignore_errors: yes
- name: remove stopped cotainer
command: docker rm my_cicd_project
ignore_errors: yes
- name: remove current docker image
command: docker rmi kwongahyeon/cicd-project-ansible
ignore_errors: yes
- name: pull thew newest docker image from Docker hube
command: docker pull kwongahyeon/cicd-project-ansible
args:
chdir: /root
- name: create a container using cicd-project-ansible image
command: docker run -d --name my_cicd_project -p 8081:8080 kwongahyeon/cicd-project-ansible
Trouble Shooting
- ssh-copy-id 설정 오류
ssh-copy-id를 사용하여 ssh 접속 시(ansible에서 배포대상 도커 서버로의 접속을 의미한다) 비밀번호를 다시 입력하지 않아도 되게 설정했는데 지속적으로 비밀번호 재입력을 요구했다.
ssh-copy-id를 사용하면 원격 호스트의. ssh/authorized_keys 파일에 kegen으로 생성한 id_rsa_pub 가 복사 된다. ( 즉 접속 대상 서버 = 원격 호스트에서 접속 정보에 대한 키가 저장된다. ) 혹시 명령어 실행 후 키 저장이 안 되었나 싶어서 직접 대상 도커 서버에 접속해서 확인해 봤지만 정상적으로 생성되어 있었다. 물론 파일을 열어서 암호키 내용이 일치하는 것도 확인했다. 다년간 ssl 수동 설정하면서 느꼈던 감정을 오랜만에 느꼈다.
거의 이틀 동안 삽질하다가 배보다 배꼽이 커지는 것 같아 인증 방법을 바꿨다.
ssh-copy-id 대신 ssh-agent를 사용하여 다음과 같이 입력 후 정상적으로 사용했다.
$ eval "$(ssh-agent -s)"
$ ssh-add /root/.ssh/id_rsa
해결하긴 했지만 묘한 찝찝함을 뒤로... 언젠가 시간이 난다면 다시 해봐야겠다 ( 배포과정 정리하면서 처음부터 다시 두세 번은 더해본 거 같은데 해결 안 되어서 정말 모르겠다 ㅋ..ㅋㅋ 🤷♀️)
'Dev > CI&CD' 카테고리의 다른 글
Jenkins, Ansible로 CI/CD 구축하기 (2/3) (0) | 2024.07.02 |
---|---|
Jenkins, Ansible로 CI/CD 구축하기 (1/3) (0) | 2024.07.01 |