[DevOps] Ansible을 활용한 Docker 및 Jenkins CI/CD 파이프라인 구축 (feat. Windows)

10000JI·2024년 5월 4일

DevOps

목록 보기
6/14
post-thumbnail

주의
초기에 DinD(Docker in Docker) 방식으로 컨테이너를 올렸다가 DooD(Docker out of Docker) 방식으로 바꾸어 캡처본이 ip주소가 조금씩 달라 혼란을 빚을 수 있음에 주의

🎧 Docker 컨테이너로 Ansible 설정

** DinD 방식으로 컨테이너를 구축하려 했으나 systemctl start docker 사용 시 도커 프로세스가 시스템에서 시작이 되지 않는 오류를 때문에 DooD 방식으로 변경하여 구축, 하단의 글 참조.

DinD 방식 및 DooD 방식 설명

먼저 Ansible 서버를 만들기 위해 도커 허브에서 이미지를 다운받는다.

docker pull edowon0623/ansible

다운받은 이미지를 가지고 Ansible 서버를 기동하기 위해 컨테이너를 만들어 주었다.

이전 포스트에서 도커 컨테이너 생성시 DooD 방식으로 만들었을 때 명령어와 동일하다. 다만 다운받은 방금 다운 받았다고 한 이미지명을 가지고 Ansible 서버를 만들어주었다.

DooD 방식으로 생성한 컨테이너 명령어 참고

docker run -itd --name ansible-server -p 20022:22 -e container=docker --tmpfs /run --tmpfs /tmp -v /sys/fs/cgroup:/sys/fs/cgroup:ro -v /var/run/docker.sock:/var/run/docker.sock edowon0623/ansible:latest /usr/sbin/init


가동 중인 도커 컨테이너들이 각각의 IP 주소가 어떻게 할당되어 있는지 확인해보자.

docker network inspect bridge

현재 나는 도커에 jenkins-server, docker-server, ansible-server을 가동하고 있으며 각각의 주소는 172.17.0.2, 172.17.0.3, 172.17.0.4을 가지고 있다.

쉘을 구분하기 위해 흰색은 ansible, 노란색은 docker, 파란색은 jenkins로 테마를 변경하였다.


만든 ansible-server에 접속해보자.

ssh root@localhost -p 20022

비밀번호를 입력하라고 나오는데 여기서 P@ssw0rd를 입력하면 접속하게 된다.


Ansible이 정확하게 설치되어 있는지 버전도 확인할 수 있다.

ansible --version


Ansible이 클라이언트로 관리하고자 하는 대상을 /etc/ansible 폴더 밑에 hosts라는 파일에서 목록을 볼 수 있다.

나는 해당 폴더 & 파일이 존재하지 않아 폴더를 만들고, hosts 파일을 만들어 vi 에디터로 클라이언트 주소를 써주었다.

mkdir /erc/ansible
vi /etc/ansible/hosts
cat /etc/ansible/hosts

앞서 말했듯이 172.17.0.3은 Docker 서버이고, 172.17.0.4는 자기 자신인 Ansible 서버이다.

이 둘을 클라이언트로 관리하려고 한다.


현재 작업되어 있는 환경을 정리해보자.

jvm을 가지고 있는 형태의 컨테이너도 하나 또 기동했다고 가정하고 172.17.0.5 주소를 주었다.

윈도우즈에서 호스트에서 도커 엔진으로 ssh 접속을 하기 위해서 각각의 컨테이너들이 가지고 있는 22번 포트를 다른 포트와 포워딩 시켰다.

다시 말해서 윈도우즈에서 ssh 접속을 할 때 -p 포트 번호를 2022번으로 하게 되면 Ansible 서버로 가게되고, 1022번으로 접속을 시도하면 Docker 서버로 연결되게 설정해놓았다.

그리고 Ansible 안에서 다른 서버로 접속을 시도할 때는 그냥 ssh 루트에 해당하는 컨테이너들의 ip 주소를 직접 이용하면 된다.

ssh root@172.17.0.3 이렇게 말이다.

그런데 이제 접속을 시도할 때 ip address 하고 id, password가 당연히 필요하다.

이 두가지 정보가 있어야지만 접속을 할 수가 있다. Ansible에서 시스템들을 관리 함에 있어서 매번 password로 입력하는게 번거로울 수 있다.

그래서 Ansible이 가지고 있는 password 값을 JVM서버 혹은 Docker서버에 전달을 해놓고 password 없이 자동으로 로그인이 되게 만들어 놓울 수 있다.

Ansible에서 키를 하나 생성한 다음에 해당하는 키 값을

JVM서버 혹은 Docker서버에게 전달을 해준다.

Ansible에서 두 개의 서버에 명령을 전달하고자 할 때는 자신이 가지고 있는 키값을 배포한 상태이기 때문에 id 패스워드 입력하지 않고 그냥 접속할 수 있다.


ssh-keygen 로 키를 먼저 생성한다. 이는 Ansible 서버에서 명령을 실행한다.

Ansible 서버에서 키를 하나 생성한 다음에 해당하는 키 값을 이제 다른 서버에다가 복사하는 과정을 거치면 Ansible 서버에서 할당하는 클라이언트에 접속 시 아이디와 패스워드를 입력하지 않은 상태에서 접속이 가능하다.

ssh-keygen

여기선 rsa라는 알고리즘을 이용해서 키를 생성한다.

이 키 안에는 public키와 private키가 같이 생성이 된다.

파일명은 동일하고, 패스워드는 없이 그냥 입력하도록 한다. (모두 엔터로 넘어감)


이제 만들어져 있는 키 값을 Docker 서버에 전달을 할 것이다.

키 복사는 ssh copy-id root@[접속할 서버IP]이라면 명령어를 사용한다.

ssh copy-id root@172.17.0.3

도커 서버 주소는 172.17.0.3이다. 그리고 패스워드 (P@ssw0rd)를 입력해준다.

ssh root@172.17.0.3

Ansible 서버 그대로인 상태에서 Docker 서버로 ssh 접속을 하면 비밀번호 없이 바로 접속 되는 것을 확인할 수 있다.

🧸 Ansible 기본 명령어

실행 옵션

  • -i : 접속하고자 하는 Ansible 클라이언트들의 정보 파일들을 특정한 텍스트 파일로 만들어 놓을 수 있다. 그것을 인벤토리 파일이라고 한다.
    즉, 인벤토리 파일은 용이 되고자 하는 호스트들에 대한 정보를 지정할 수 있다.
    앞서 실습에서 이 파일을 /etc/ansible/hosts 파일에 등록을 해놓았다.

  • -m : 모듈을 선택할 수 있다.

  • -k : 관리자 암호를 입력할 수 있도록 -k 를 요청할 수 있다.
    현재 나는 ssh-copy-id 라는 명령어를 가지고 Ansible 서버에서 각각의 호스트에 접속을 할 수 있도록 아이디 값을 다 복사를 해놓은 상태이다.

  • --list-hosts : 적용되고자 하는 호스트 목록을 체크할 때 list-host라는 명령어를 사용할 수 있다.


멱등성

Ansible를 사용할 때 중요한 특징이 하나가 있다.

바로 멱등성이라는 특징을 알아야 한다.

같은 설정을 여러 번 적용한다 하더라도 결과가 달라지지 않고 한 번만 적용되는 특징을 말한다.

예를 들어서 copy라는 명령어를 실행했을 때 실행하는 횟수대로 copy의 계속 파일들이 생기게 될 것이다.
또는 어떤 텍스트 파일을 만들어서 추가 작업을 계속하면 같은 내용이라 할지라도 계속 파일이 추가가 될 것이다.

그런데 Ansible에서는 같은 내용의 명령어를 두 번, 세 번 실행하게 되면 한 번만 적용이 된다.

멱등성이라는 개념을 도입하지 않은 상태에서 echo 명령어를 계속 실행해보자

echo -e "[mygroup]\n172.17.0.5" >> /etc/ansible/hosts 명령어를 두 번 실행하게 되면 hosts 파일에는

[mygroup]
172.17.0.5

텍스트가 두 번 기록이 되버린다.

하지만 Ansible을 사용하게 되면 아무리 echo -e "[mygroup]\n172.17.0.5" >> /etc/ansible/hosts명령어를 여러 번 입력해도 hosts 파일에는 한 번만 입력하게 된다는 것이다.

모듈 요소들 테스트

Ansible 서버와 클라이언트에 사용할 수 있는 모듈을 몇 가지 테스트해보자.

ansible all -m ping

  • all: hosts 파일에 있는 적용시키고자 하는 그룹의 이름을 정한다.
    그룹이 하나인 경우엔 하나의 그룹을 지정해도 좋고, all이라고 지정해도 좋다.
    all이라고 지정하게되면 전체 내용이 다 적용되는 것이고, 그룹이 여러 개 있을 경우엔 특정 그룹명만 지정해서 모듈의 내용을 전달할 수 있다.

  • -m: 모듈

  • ping: 모듈의 종류 중 하나

현재 나는 Ansible 서버, Docker 서버 모두 가동 중이어서 SUCCESS가 떴지만, 서버가 중단되면 UNREACHABLE!이 뜨게 된다.


디스크 용량을 확인하는 free -h 명령어도 Ansible에서 실행 가능하다.

ansible all -m shell -a "free -h"

  • -m: 모듈

  • shell: 모듈의 종류 중 하나, 명령어 입력 시 사용하는 모듈

  • "free -h": all을 사용하였기에 hosts에 등록된 172.17.0.3과 172.17.0.4 서버에 전달하여 디스크 용량을 확인하는 결과 값을 가져온다.


이번엔 파일 전송 예제를 살펴보자

touch test.txt

touch 명령어로 0바이트 짜리 임시 파일 test.txt를 생성한다.

echo "Hi there~" >> test.txt

echo 명령어로 test.txt 파일에 내용을 복사한다.

ansible all -m copy -a "src=./test.txt dest=/tmp"

test.txt 파일을 복사하여 tmp 디렉토리로 이동하여 동일 파일을 생성한다.

여기서도 Docker 서버, Ansible 서버 모두에 반영이 된다.


ansible devops -m yum -a "name=httpd state=present"

  • devops: all 대신에 그룹명 devops를 사용하여 해당 그룹에 속하는 주소 (= Docker 서버, Asible 서버)에 적용시킨다.

  • yum : 모듈의 종류 중 하나로 다운로드 받는 기능이다.

  • name : httpd라는 서비스를 설치할 것이고,

  • state : present, 지금 바로 다운로드 한다.

🎮 Ansible Playbook 사용하기

Ansible Playbook 이란?

Ansible Playbook은 사용자가 원하는 내용을 미리 작업해 놓는 스크립트 파일을 말한다.

우측 그림처럼 작업하고자 하는 내용들, 여러가지 단계에 있는 내용들을 미리 작성해 놓고 Asible에서 지정되어 있는 타겟에다가 몇 번이고 적용을 할 수 있는 특징을 가지고 있다.

프로그램을 설치한다든가 파일을 전송한다든가 서비스에 대한 재시작하는 작업 뿐만 아니라 여러 서버, 여러 타겟의 반복적인 작업을 처리할 때도 많이 사용된다.

Ansible Playbook 예제

예제1 - 파일에 내용 추가

Ansible Playbook을 만들기 위해 테스트 파일을 만들어보자.

이름은 first playbook.yml으로 하였다.

vi first playbook.yml

vi 에디터로 들어가 해당 내용을 기입해주었다.

---
- name: Add an ansible hosts
  hosts: localhost
  tasks:
    - name: Add a ansible hosts
      blockinfile:
        path: /etc/ansible/hosts
        block: |
          [mygroup]
          172.17.0.5
  • name : 만들고자 하는 엔서블 플레이북의 이름을 지정한다.

  • hosts : 적용시키고자 하는 그룹의 이름 또는 ip address의 이름을 지정한다.

  • task : 실제로 이 서버에, 이 타겟에 어떠한 내용들을 적용시켜 줄 것인지 기술한다.
    실질적인 작업은 tasks 아래 부분이다.

    • bolockinfile : 파일에 특정한 블록을 만들어 어떤 내용을 추가하는 작업이다.
    • path : 어떤 파일에다가 추가할 것인지 적는다.
    • block : "| (파이프)"를 꼭 입력한 뒤 들여쓰기 후 파일에 추가할 내용을 적는다.

해당 테스트 파일을 Ansible playbook으로서 사용하기 위해 다음과 같은 명령어를 입력한다.

ansible-playbook first-playbook.yml

파일에 입력한 대상 host였던 localhost에 적용이 잘 됐으며 테스트가 정상적으로 진행되었다.

cat /etc/ansible/hosts

적용 대상 파일에 정상적으로 추가가 된 것을 확인할 수 있다.

Ansible에는 멱등성 특성을 가지고 있기 때문에 Ansible Playbook을 여러 번 실행해도 계속해서 추가되는 것이 아니라 한번 만 추가되며 결과가 달라지지 않을 것이다. 한번 확인해보자.

동일 명령어를 반복했을 때 결과값은 변하지 않는 것을 확인할 수 있다.

예제2 - 파일 복사

이번엔 파일을 copy하는 파일, download 받는 파일을 만들어 진행해보자.

vi playbook-sample1.yml

vi 에디터로 들어가 해당 내용을 기입해주었다.

** 이때 들여쓰기가 제대로 되어있지 않으면 Ansible Playbook 실행이 되지 않으므로 정확히 입력해야 한다.

---
- name: Ansible Copy Example Local to Remtoe 
  hosts: devops
  tasks:
    - name: copying file with playbook
      copy:
        src: ~/sample.txt
        dest: /tmp
        owner: root
        mode: 0644
  • hosts : 대상은 그룹명 devops에 적용한다.

  • task의

    • copy하고자 하는

      • src : 원본 소스는 틸드(=현재 디렉토리, 홈디렉토리)에 있는 sample.txt 파일을

      • dest : 목적지 폴더인 /tmp에 복사한다.

      • owner : 소유자는 root이며

      • mode : 파일의 퍼미션은 0644로 ---rw--w--w-, 소유자에 읽고 쓰기가 가능하며, 그룹과 다른 사용자에게는 읽기만 가능한 상태이다.

ansible-playbook playbook-sample1.yml

Ansible Playbook을 방금 만든 파일로 실행해준다.

3번과 4번 서버에 정상적으로 복사가 되었다고 결과값을 반환하므로 확인해보자.

도커 서버(호스트가 3번)는 노란색 테마를 적용했다고 포스트 첫머리에 밝혔었다.

확인해보면 sample.txt가 생겨난 것을 확인할 수 있다.

예제3 - 다운로드

vi playbook-sample2-windows.yml

---
- name: Download Tomcat9 from tomcat.apache.org
  hosts: all
  #become: yes
  # become_user: root
  tasks:
   - name: Create a Directory /opt/tomcat9
     file:
       path: /opt/tomcat9
       state: directory
       mode: 0755
   - name: Download the Tomcat checksum
     get_url:
       url: https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.88/bin/apache-tomcat-9.0.88.tar.gz.sha512
       dest: /opt/tomcat9/apache-tomcat-9.0.88.tar.gz.sha512
   - name: Register the checksum value
     shell: cat /opt/tomcat9/apache-tomcat-9.0.88.tar.gz.sha512 | grep apache-tomcat-9.0.88.tar.gz | awk '{ print $1 }'
     register: tomcat_checksum_value
   - name: Download Tomcat using get_url
     get_url:
       url: https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.88/bin/apache-tomcat-9.0.88.tar.gz
       dest: /opt/tomcat9
       mode: 0755
       checksum: sha512:{{ tomcat_checksum_value.stdout }}"
  • task의

    • name: /opt/tomcat9 폴더를 생성을 할 것

    • file의

      • path: /opt/tomcat9 를 적어주고,

      • state : 상태는 폴더로

      • mode : 퍼미션은 0755로 설정한다.

    • name: 다운로드 url 주소를 가져와 톰켓 압축파일을 다운로드할 것

    • get_url의

      • url: 다운로드 링크

      • dest : /opt/tomcat9, 다운로드 파일을 어디에 복사하는지 폴더 위치 선정

      • mode : 퍼미션은 0755로 설정한다.

      • checksum: checksum을 통해서 확인하겠다는 뜻이다.

ansible-playbook playbook-sample2-windows.yml

마찬가지로 Ansible Playbook을 방금 만든 파일로 실행해준다.

해당 서버의 폴더로 이동하여 다운로드 되었는지 확인해보면 정상적으로 처리된 것을 확인할 수 있다.

🎨 Jenkins + Ansible 연동하기

Jekins 와 Ansible 서버 모두 도커 형태로 기동되어 있다.

이 각각의 호스트를 윈도우 PC에서 실행하고 있는 Ansible 서버로 접속을 시도할 때는 20022번 포트 번호가 필요하다.

만약에 윈도우 서버에서 실행하는 것이 아니라 Jenkins 서버에서 Ansible 서버로 접속을 시도할 때를 생각해보자.

같은 도커 컨테이너로 기동하고 있는 상태이기 때문에 Jenkins에서 Ansible 서버 서버로 ssh 명령을 가지고 기동을 할 때 ip address는 172.17.0.3 이라고 입력하면 된다.

이때 필요한 id는 root, 패스워드는 앞서 말했듯 P@ssw0rd로 입력하면 된다.

여기서 포트번호는 윈도우 PC가 아닌 Jekins에서 Ansible로 접속을 시도하기 때문에 22번 포트로 접속하면 된다.

젠킨스 대시보드로 들어가 Dashboard -> Jenkins 관리 -> System 으로 들어와 SSH Server 를 추가해줘야 한다.

추가버튼을 눌러 Name은 ansible-server, Hostname은 ansible-server 주소를 172.17.0.4, Username은 root, Remote Directory는 . (홈 디렉토리)을 입력하였다.

패스워드를 입력해야 하므로 User password authentication, or use s different key 체크박스를 클릭해주고 Passphrase/Password 란에 P@ssw0rd를 입력해준다.

포스번호는 22번으로 고정시켜 놓는다.

적용이 끝났다면 Test Configuration를 클릭하여 테스트가 정상적으로 되는지 확인 후 Apply를 눌러주면 설정이 끝난다.

🛒 Jenkins + Ansible Playbook 사용하기

Jenkins와 Ansible을 연동하였으니 Item을 만들어보자.

Item 내용은 다음과 같다.

Jekins에서 Git에서 가져온 코드를 Jekins에서 빌드를 하여 war 파일을 만들어낸다.

그 다음에 Jekins에서 만든 war 파일을 Ansible 서버에 복사를 하여 Playbook을 이용해 사용해보려 한다.

Dashboard -> 새로운 Item을 클릭한다.

만들 Item명은 My-Ansible-Project, 기본에 만들었던 Item을 복사하여 사용하기 위해 스크롤을 내려 Copy Item에 기존에 만들었던 My-Docker-Project을 입력해준다.

OK를 눌러 구성에 들어간다.

다른건 My-Docker-Project와 동일하게 하되 빌드 후 조치를 기존에 있던 것을 삭제하고

빌드 후 조치 추가에서 Send build artifacts over SSH를 클릭하여 새로 만든다.

젠킨스에서 만들어지는 결과물을 SSH를 통해서 전송하겠다는 뜻이다.

Jenkins에서 만들어진 결과물을 Ansible 서버로 복사할 것이기 때문에 Name은 ansible-server를 클릭해주고,

Soucre files은 target/*.war를 적고, targe t폴더 자체는 필요가 없기 때문에 Remove prefix는 target, 복사할 위치인 Remote directory는 .을 적는다.

지금은 exec command는 적지않는다

설정이 끝났으면 저장을 눌러준다.

지금 빌드를 눌러 빌드를 진행해보자.

콘솔을 확인해 보면 SUCCESS가 뜨는 것으로 보아 정상적으로 빌드가 되었다.

Ansible 서버의 프로세스를 확인해보면 hello-world.war 파일이 잘 복사 되었다.

vi first-devops-playbook.yml

vi 에디터를 켜서 playbook으로 만들 파일에 내용을 채워보자.

- hosts: all
#   become: true

  tasks:
  - name: create a docker image with deployed waf file
    command: docker build -t edowon0623/cicd-project-ansible -f Dockerfile .
    args: 
        chdir: /root
  • hosts : all, 전체 내상으로 하였다.
    전체 devops 대상이 호스트 3번,4번 두 개였다.

  • task의

    • name : 현재 가지고 있는 war file을 가지고 도커 이미지를 만들겠다는 부분

    • command : docker build -t cicd-project-ansible -f Dockerfile .
      Docker를 사용하여 "cicd-project-ansible"라는 이름의 이미지를 빌드한다.
      현재 디렉토리에 있는 Dockerfile을 사용하여 빌드를 진행하고, 최종 이미지는 "cicd-project-ansible" 태그가 붙는다.

      • args의

        • chdir : /root , 작업을 할 대상은 root 디렉토리

Dokerfile을 확인해보면 tomcat 9.0을 가지고, ./heelo-world.war 파일을 /usr/local/tomcat/webwapps 폴더에다가 복사하는 작업이 들어가 있다.

playbook을 실행하게 되면 파일 내 명령어에 의해 도커파일이 실행될 것이다.

호스트 파일에는 다른 내용 없이 현재 Ansible 서버의 ip 주소만 지정하였다.

ansible-playbook -i hosts first-devops-playbook.yml

Ansible 플레이북을 실행한다. "-i" 옵션은 인벤토리 파일을 지정하는데, 여기서 방금 설정한 "hosts" 파일을 사용한다.

이렇게 하면 지정된 인벤토리에 정의된 호스트들에 대해 Ansible 플레이북을 실행된다.

docker images

도커 이미지를 확인해보면 Ansible Playbook에 의해 이미지가 생성된 것을 확인할 수 있다.

vi first-devops-playbook.yml

편집했던 vi에 다시 들어가

- hosts: all
#   become: true  

  tasks:
  - name: create a docker image with deployed waf file
    command: docker build -t edowon0623/cicd-project-ansible -f Dockerfile .
    args: 
        chdir: /root

  - name: create a container using cicd-project-ansible image
    command: docker run -d --name my_cicd_project -p 8081:8080 edowon0623/cicd-project-ansible

컨테이너를 추가해주는 내용을 삽입한다.

  • task의

    • name : cicd-project-ansible 이미지를 가지고 컨테이너를 생성

    • command : docker run -d --name my_cicd_projcet -p 8081:8080 edowon0623/cicd-project-ansible
      컨테이너명은 my_cicd_projcet로 하였다.
      포트번호는 DooD 방식으로 도커 컨테이너를 생성하기 때문에 내부에 있는 8080 포트를 외부에서 8081포트로 들어와 사용할 수 있도록 포워딩을 만들어주었다.

ansible-playbook -i hosts first-devops-playbook.yml

Ansible Playbook을 실행하고

docker ps

도커 프로세스를 확인해보면 방금 전에 my_cidcd_project 컨테이너가 생성된 것을 확인할 수 있다.

docker stop my_cicd_project
docker rm my_cicd_project
docker rmi cicd-project-ansible


Ansible 서버 안에서 이미지와 컨테이너가 정상적으로 만들어지는 것을 확인했으니 생성한 이미지와 컨테이너를 지워주고, 이 파일을 Jenkins에서 실행할 수 있도록 지정을 해주자.

젠킨스로 돌아와 My-Ansible-Project의 구성을 수정해주자.

이전에 빌드 후 조치에서 Exec command를 생략하고 넘어갔는데 여기에 Ansible Playbook 실행 명령어를 써주자

ansible-playbook -i hosts first-devops-playbook.yml

수정이 끝나면 저장을 눌러주고 다시 한번 빌드를 하자.

Ansible 서버에 컨테이너가 가동되고, 이미지가 만들어지는 것을 확인할 수 있다.

웹브라우저도 http://localhost:8081/hello-world/ 로 접속하면 index.jsp의 내용이 정상적으로 출력되는 것을 확인할 수 있다.


그 다음에 추가할 작업은 여러 번 소스 코드를 변경을 했을 때 변경된 내용을 자동으로 가지고 와서 이미지 빌드하는 작업까지 완성해보자.

젠킨스로 돌아와 My-Ansible-Project의 구성에서 빌드 유발의 Poll SCM 부분을 추가해준다.

Schedule이라고 되어 있는 부분에 에스테틱 마크 5개 * * * * *를 추가해준다.

그래서 1분에 한 번씩 데이터를 자동으로 가져올 수 있도록 만들 것이다.

이번엔 index.jsp의 내용을 변경해보자.

cmd를 켜서 프로젝트 내부로 이동해 code .명령어로 Vscode를 실행한다.

코드 수정을 위해 나는 하단에 <h3>Change Code<h3>로 코드를 변경해주었다.

변경된 코드를 저장해주고 GitHub에 올리는 작업을 해준다.

git add .
git commit -m "changed the index page"
git push

  • git add .: 현재 디렉토리의 모든 변경된 파일을 스테이징 영역에 추가

  • git commit -m "changed the index page": 스테이징 영역에 추가된 변경 사항을 커밋, 커밋 메시지는 "changed the index page"로 지정

  • git push: 변경된 커밋들을 원격 저장소로 푸시

Github의 소스코드가 변경되었고,

젠킨스 안에서도 빌드가 자동으로 이루어지는 것을 확인할 수 있다.

하지만 SUCCESS가 아닌 UNSTABLE이 뜨며 문제가 발생하였다.

이미지는 정상적으로 만들어지지만 컨테이너는 만들어지지 않는다.

이유는 같은 컨테이너가 존재하고 있기 때문에 이 컨테이너를 두 번 연속 만들 수가 없다.

따라서 기존에 만들어졌던 컨테이너를 중지하고 삭제하고 새롭게 컨테이너를 만들기에 필요하다는 뜻이다. 이 작업은 이전 포스트에서도 발생했던 문제이다.

이 문제는 Playbook 파일을 조금만 수정하면 된다.

vi first-devops-playbook.yml

편집했던 vi에 다시 들어가

- 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 edowon0623/cicd-project-ansible
    ignore_errors: yes

  - name: pull the newest docker image from Docker Hub
    command: docker pull edowon0623/cicd-project-ansible

  - name: create a container using cicd-project-ansible image
    command: docker run -d --name my_cicd_project -p 8080:8080 edowon0623/cicd-project-ansible

빨간 박스 부분이 수정되었다.

  • task의

    • name : 현재 실행 중인 컨테이너를 중지한다

    • command : docker stop my_cicd_projcet

    • ignore_errors : yes

    • name : 해당 컨테이너를 삭제한다.

    • command : docker rm my_cicd_projcet

    • ignore_errors : yes

    • name : 현재 도커 이미지를 삭제한다.

    • command : docker rmi cicd-projcet-ansible

    • ignore_errors : yes

기존에 존재하더라도 그걸 삭제하고 새롭게 이미지를 만들 것이기 때문에 문제가 발생하지 않는다.

만약 해당 작업에서 오류가 발생하더라도 무시하고 계속해서 플레이북을 실행할 수 있다.

다시 빌드를 하면 SUCCESS가 뜬다.

이미지와 컨테이너 모두 새로 만들어진 것을 확인할 수 있다.

이렇게 해서 PlayBook을 가지고 기존 Docker 서버에서 문제가 되었던 컨테이너가 중복적으로 실행하는 부분에 대해 해결이 되었다.

🎠 Ansible을 이용한 Docker 이미지 관리

리뷰

이번에는 테스트했던 도커의 이미지를 허브 사이트에 실제로 업로드를 시켜 보자.

Ansible 서버에 만든 이미지를 docker push 라는 명령어를 가지고 해당 이미지를 허브에 올려볼 것이다.

이미지를 업로드 시키기 위해 이미지 앞에 도커 계정 이름을 적어줘야 도커 허브 사이트에 업로드가 된다.

ex> 10000ji/cicd-project-ansible

이 작업을 진행을 하면서 만약에 access denied가 발생한다고 하면 도커 로그인을 한번 해주어야 한다.

실제로 작성했던 이미지가 서브에 업로드가 되는가 확인을 할 예정이다.

이번에 이렇게 허브 사이트에 업로드 시켜주는 동작을 플레이북 파일로 만들 수 있다.

첫 번째 job에서는 cicd-project-ansible 이미지를 생성하고.

두 번째 job에서는 Docker Hub 사이트에 이미지를 push 하고 있다.

세 번째 job에서 push가 정상적으로 되었다고 하면 이제 현재 Ansible 서버에 남아 있었던 이미지는 삭제시켜주는 동작을 해 볼 것이다.

그리고 이 작업들이 다 완료가 됐을 경우에는 현재 Ansible 서버에 도커 이미지는 존재하지 않겠지만, 실제로 허브 사이트에 만들었던 이미지가 업로드 된 것을 확인해 볼 수 있다.

두 번째, 앞서 실행했었던 playbook 예제 파일을 container 파일명으로 변경한 다음 해당 파일의 내용을 수정해줄 것이다.

기존에 작동 중이었던 컨테이너를 중지하고, 삭제하는 작업은 동일하고,

다음에 현재 이미지 삭제하고, 최신 이미지를 다운로드 받고 , 최신 이미지 갖고 컨테이너를 만들어주는 작업에 있어서 이미지의 이름이 계정명이 포함된 채 수정되어야 한다.

ex> 10000ji/cicd-project-ansible

정상적으로 다 처리가 되었다면 Ansible 서버에도 이 작업이 반영될 거고, Docker 서버에도 이 작업이 반영되게 테스트를 해볼 것이다.

앞서 테스트했던 hosts 파일에서는 Ansible 서버에 IP주소 밖에 추가를 시키지 않았기 때문에 Docker 서버에 IP주소를 추가시켜서 두 개 다 적용시켜 보자.

그런데 이미지를 굳이 똑같이 만들어서 허브 사이트에다가 push를 두 번 한다는 것은 의미가 없다.

그래서 작성했던 호스트 파일을 실행시켜 주는 것은 동일한데, 제일 마지막에 limit라는 옵션을 부여해 줌으로써 현재 이 플레이북의 대상을 어떤 서버에다만 해 줄 건지 지정을 해 줄 것이다.

이미지를 생성하는 작업은 Ansible 서버에다만 지정을 해 줄 것이고,

컨테이너를 생성하는 작업은 Docker 서버에다만 지정을 해 주겠다.

이 작업이 다 끝난 다음에 도커 서버에 가서 이미지를 다운로드 받고 컨테이너가 정상적으로 만들어졌는가 확인을 해볼 것이다.

실습

docker tag cicd-project-ansible 10000ji/cicd-project-ansible

"cicd-project-ansible"라는 이름의 Docker 이미지를 "10000ji/cicd-project-ansible"라는 이름으로 다시 태그한다.

이렇게 하면 동일한 이미지가 "10000ji"라는 Docker 허브 사용자의 계정 아래에 있는 "cicd-project-ansible" 리포지토리로 푸시될 수 있다.

docker login

푸시하기 위해선 docker 로그인을 해야한다.

본인 계정의 아이디와 비밀번호를 올바르게 입력해주면 정상적으로 로그인이 된다.

docker push 10000ji/cicd-project-ansible

docker push를 실행하면

정상적으로 Docker Hub에 자신의 계정명으로 이미지가 생성된다.

vi create-cicd-devops-image.yml

이를 파일을 만들어 Ansible Playbook을 통해 실행해보자

- hosts: all
#   become: true

  tasks:
  - name: create a docker image with deployed waf file
    command: docker build -t 10000ji/cicd-project-ansible .
    args: 
        chdir: /root
    
  - name: push the image on Docker Hub
    command: docker push 10000ji/cicd-project-ansible

  - name: remove the docker image from the ansible server
    command: docker rmi 10000ji/cicd-project-ansible  
    ignore_errors: yes

위 리뷰에서 말했듯

첫 번째 job에서는 cicd-project-ansible 이미지를 생성하고.

두 번째 job에서는 Docker Hub 사이트에 이미지를 push 하고 있다.

세 번째 job에서 push가 정상적으로 되었다고 하면 이제 현재 Ansible 서버에 남아 있었던 이미지는 삭제시켜주는 동작을 해 볼 것이다.

여기서 본인의 계정명을 꼭 이미지명 앞에 쓰고 / 를 써준다.

ansible-playbook -i hosts first-devops-playbook.yml

몇 초전에 생성되었다는 표시가 뜨며 이미지가 새롭게 만들어짐을 알 수 있다.

mv first-devops-playbook.yml create-cicd-devops-container.yml
vi create-cicd-devops-container.yml
cat 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 10000ji/cicd-project-ansible
    ignore_errors: yes

  - name: pull the newest docker image from Docker Hub
    command: docker pull 10000ji/cicd-project-ansible

  - name: create a container using cicd-project-ansible image
    command: docker run -d --name my_cicd_project -p 8081:8080 10000ji/cicd-project-ansible

리뷰에서 말했듯 앞서 실행했었던 playbook 예제 파일을 container 파일명으로 변경한 다음 해당 파일의 내용을 수정해주었다.

기존에 작동 중이었던 컨테이너를 중지하고, 삭제하는 명령어는 동일하고,

다음에 현재 이미지 삭제하고, 최신 이미지를 다운로드 받고 , 최신 이미지 갖고 컨테이너를 만들어주는 작업에 있어서 이미지의 이름이 계정명이 포함된다.

기존에 있었던 docker images와 docker container는 깨끗하게 지워 실행할 수 있도록 관련 이미지와 컨테이너는 삭제시켜 준다.

vi hosts

172.17.0.4
172.17.0.3

hosts 파일도 Ansible 서버(172.17.0.4)만 추가되어 있었는데 Docker 서버(172.17.0.3)도 추가해주었다.

ansible-playbook -i hosts first-devops-image.yml --limit 172.17.0.4

이미지를 굳이 두 번 올릴 필요 없으므로 이미지를 생성하여 도커 허브에 올리는 작업은 Ansible 서버만 지정을 해주었다.

ansible-playbook -i hosts first-devops-container.yml --limit 172.17.0.3

허브에 올린 이미지를 가지고 컨테이너를 생성하는 건 Docker 서버만 실행하도록 limit를 걸었다.

위 빨간 문구들은 ignore_errors: yes명령어에 의해 컨테이너 stop, rm / 이미지 rmi 작업이 실행에 실패했지만 무시되었음을 알려주는 문구이다.

결과적으로 Ansible 서버에서 도커 허브에 이미지가 올라가고, Docker 서버에서 올린 이미지를 다운받아 컨테이너 생성하는 작업이 성공적으로 이루어졌다.

🛒 Jenkins + Ansible Playbook => Docker 이미지 관리

Jenkins에서 새로운 Item을 만들도록 하자

바로 이전에 만든 My-Ansible-Project를 Copy하여 쓰도록 만들었다.

모든 구성은 동일하나 아까는 비워둔 Exec Command를 추가해주었다.

ansible-playbook -i hosts first-devops-image.yml --limit 172.17.0.4
ansible-playbook -i hosts first-devops-container.yml --limit 172.17.0.3

위에서 실행했던 이미지를 생성하고 도커 허브에 올리는 작업과 도커 허브에 올라온 이미지를 당겨와 컨테이너를 만드는 작업이다.

각각 Ansible서버, Docker서버에서만 실행할 수 있도록 limit 옵션을 걸었다.

저장을 누른 뒤

지금 빌드를 클릭해준다.

콘솔 창을 확인해보면 SUCCESS로 빌드가 성공한 것을 알 수 있다.

도커 허브에는 몇 분 전에 이미지가 생성되었다고 뜨고,

Docker 서버에도 도커 이미지와 컨테이너 생성이 몇 분 전에 만들어졌다고 뜨는 것으로 보아 의도한대로 정상 실행되었다.

출처

Jenkins를 이용한 CI/CD Pipeline 구축

profile
Velog에 기록 중

0개의 댓글