새소식

기술/Application

Packer와 Ansible을 이용한 Docker Image 생성 및 배포

  • -

Packer는 자동화된 이미지 빌더입니다.

 

Hashicorp에서 개발된 툴로, 다양한 프로비저너를 지원하여 AWS, Azure, GCP, VMware, Docker 등 다양한 이미지들을 HCL문을 이용하여 생성할 수 있습니다.

 

 

이번 가이드는 Docker를 Jenkins가 아닌 Packer를 이용하여 Build 후 Docker Hub로 바로 배포할 수 있도록 하였습니다.

 

* 준비물

Packer
Ansible

 

# Python3 pip 설치 및 Ansible 설치

pip3 설치
root@ubuntu:/home/ubuntu# apt-get install python3-pip -y

Ansible 설치
root@ubuntu:/home/ubuntu# pip3 install ansible


# Packer 설치
다운로드
root@ubuntu:/home/ubuntu# wget https://releases.hashicorp.com/packer/1.6.5/packer_1.6.5_linux_amd64.zip

압축 풀기
root@ubuntu:/home/ubuntu# unzip ./packer_1.6.5_linux_amd64.zip

bin으로 이동
root@ubuntu:/home/ubuntu# cp packer /usr/bin/

버전 확인
root@ubuntu:/home/ubuntu# packer version
Packer v1.6.5

 

스크립트 작성 방식은 두 가지가 있습니다.

 

json 형식이 있고, hcl2가 있습니다.

 

본 글은 hcl2를 기준으로 작성하였습니다.

 

먼저, terraform과 마찬가지로 variable이 있으며, 이를 통해 변수를 지정할 수 있습니다.

 

variable "dockerhub_username" {
  type    = string
  default = "teichae"
}

variable "dockerhub_password" {
  type    = string
  default = "password"
}

이미지 빌드 후 Docker Hub로 Push를 하기 위해 사용자 인증정보를 입력하였습니다.

 

이와 별개로 파일 이름이나 다른 형태로도 사용이 가능합니다.

 

 

이제 불러올 소스를 지정합니다.

source "docker" "docker" {
  changes = ["VOLUME /data", "WORKDIR /data", "EXPOSE 80", "ENTRYPOINT [\"docker-entrypoint.sh\"]"]
  commit  = true
  image   = "ubuntu:18.04"
}

changes는 컨테이너에 적용할 옵션을 작성할 수 있습니다.

 

자세한 옵션은 이곳을 참조해주세요.

 

 

이제 불러온 소스를 바탕으로 빌드를 시작합니다.

 

sources에는 위에서 작성한 source.docker.docker를 불러옵니다.

build {
  sources = ["source.docker.docker"]

  provisioner "ansible" {
    playbook_file = "provision.yml"
    user          = "root"
  }
  post-processors {
    post-processor "docker-tag" {
      repository = "teichae/packer-nginx"
      tags       = ["latest"]
    }
    post-processor "docker-push" {
      login          = "true"
      login_username = "${var.dockerhub_username}"
      login_password = "${var.dockerhub_password}"
    }
  }

빌드를 진행하기 위해 provisioner를  Ansible로 입력합니다.

 

Ansible Playbook을 이용하여 Docker Image안에 필요한 것들을 설치하기 위함입니다.

 

post-processors에서는 docker-tag를 이용하여 Docker의 tag 정보를 입력합니다.

 

이후 docker-push를 이용하여 Docker Hub에 Push를 해야 하는데, Push 하기 위해 login과 login_username, login_password 옵션은 필수적으로 들어가야 합니다.

 

맨 앞서 variable문으로 작성하였고, var.dockerhub_*를 이용하여 변수를 사용할 수 있습니다.

 

전체 코드입니다.

  
variable "dockerhub_username" {
  type    = string
  default = "teichae"
}

variable "dockerhub_password" {
  type    = string
  default = "password"
}

source "docker" "docker" {
  changes = ["VOLUME /data", "WORKDIR /data", "EXPOSE 80", "ENTRYPOINT [\"docker-entrypoint.sh\"]"]
  commit  = true
  image   = "ubuntu:18.04"
}

build {
  sources = ["source.docker.docker"]

  provisioner "ansible" {
    playbook_file = "provision.yml"
    user          = "root"
  }
  post-processors {
    post-processor "docker-tag" {
      repository = "teichae/packer-nginx"
      tags       = ["latest"]
    }
    post-processor "docker-push" {
      login          = "true"
      login_username = "${var.dockerhub_username}"
      login_password = "${var.dockerhub_password}"
    }
  }
}

 

이제 Container 안에서 사용할 ansible playbook의 내용입니다.

 

내용은 간단하게 Container 안에 Timezone을 KST로 변경하고, nginx를 설치한 후 docker-entrypoint.sh을 통해 nginx를 구동시켜 보았습니다.

 

- name: Python Install
  hosts: all
  gather_facts: no
  tasks:
    - name: Python Install
      raw: apt-get -y update && apt-get install -y python3 python3-apt

- name: Build Container Image
  hosts: all

  tasks:
    - name: Creating Workdir
      file:
        path: /data
        state: directory
        owner: root
        group: root
        mode: 0755

    - name: Installing Applications
      apt:
        name: nginx, tzdata
        state: present

    - name: set timezone
      timezone:
        name: Asia/Seoul

    - name: Add Entrypoints shell
      copy:
        src: config/{{ item }}
        dest: /usr/local/bin/{{ item }}
        mode: 0755
        owner: root
        group: root
      with_items:
        - docker-entrypoint.sh

- name: Container Cleanup
  hosts: all
  gather_facts: no
  tasks:
    - name: Remove Python
      raw: apt-get purge -y python3 python3-apt && apt-get autoremove -y

    - name: Remove Apt Lists
      raw: rm -rf /var/lib/apt/lists/*

Python3은 Ansible을 사용하기 위해 설치하였고, 설치 이후 삭제하도록 설정했습니다.


tzdata는 timezone module을 사용하기 위해 설치하였습니다.

 

이제 packer build filename으로 하여 빌드를 시작해보겠습니다.

 

root@ubuntu:~/git/packer-example# packer build nginx.pkr.hcl
docker.docker: output will be in this color.

==> docker.docker: Creating a temporary directory for sharing data...
==> docker.docker: Pulling Docker image: ubuntu:18.04
    docker.docker: 18.04: Pulling from library/ubuntu
    docker.docker: Digest: sha256:646942475da61b4ce9cc5b3fadb42642ea90e5d0de46111458e100ff2c7031e6
    docker.docker: Status: Image is up to date for ubuntu:18.04
    docker.docker: docker.io/library/ubuntu:18.04
==> docker.docker: Starting docker container...
    docker.docker: Run command: docker run -v /root/.packer.d/tmp326850331:/packer-files -d -i -t --entrypoint=/bin/sh -- ubuntu:18.04
    docker.docker: Container ID: eea0f6d6824721face5dad840a766fecefa359a04060457cce22f1ea7dd84b9d
==> docker.docker: Using docker communicator to connect: 172.17.0.2
==> docker.docker: Provisioning with Ansible...
    docker.docker: Setting up proxy adapter for Ansible....
==> docker.docker: Executing Ansible: ansible-playbook -e packer_build_name="docker" -e packer_builder_type=docker -e ansible_ssh_private_key_file=/tmp/ansible-key941741365 -i /tmp/packer-provisioner-ansible874125072 /root/git/packer-example/provision.yml
    docker.docker:
    docker.docker: PLAY [Python Install] **********************************************************
    docker.docker:
    docker.docker: TASK [Python Install] **********************************************************
    docker.docker: changed: [default]
    docker.docker:
    docker.docker: PLAY [Build Container Image] ***************************************************
    docker.docker:
    docker.docker: TASK [Gathering Facts] *********************************************************
    docker.docker: ok: [default]
    docker.docker:
    docker.docker: TASK [Creating Workdir] ********************************************************
    docker.docker: changed: [default]
    docker.docker:
    docker.docker: TASK [Installing Applications] *************************************************
    docker.docker: changed: [default]
    docker.docker:
    docker.docker: TASK [set timezone] ************************************************************
    docker.docker: changed: [default]
    docker.docker:
    docker.docker: TASK [Add Entrypoints shell] ***************************************************
    docker.docker: changed: [default] => (item=docker-entrypoint.sh)
    docker.docker:
    docker.docker: PLAY [Container Cleanup] *******************************************************
    docker.docker:
    docker.docker: TASK [Remove Python] ***********************************************************
    docker.docker: changed: [default]
    docker.docker:
    docker.docker: TASK [Remove Apt Lists] ********************************************************
    docker.docker: changed: [default]
    docker.docker:
    docker.docker: PLAY RECAP *********************************************************************
    docker.docker: default                    : ok=8    changed=7    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    docker.docker:
==> docker.docker: Committing the container
    docker.docker: Image ID: sha256:4810af171821da5a2dd662b2ba52038964b472eb1491c8c0b9d5c311f5ac9b4f
==> docker.docker: Killing the container: eea0f6d6824721face5dad840a766fecefa359a04060457cce22f1ea7dd84b9d
==> docker.docker: Running post-processor:  (type docker-tag)
    docker.docker (docker-tag): Tagging image: sha256:4810af171821da5a2dd662b2ba52038964b472eb1491c8c0b9d5c311f5ac9b4f
    docker.docker (docker-tag): Repository: teichae/packer-nginx:latest
==> docker.docker: Running post-processor:  (type docker-push)
    docker.docker (docker-push): Logging in...
    docker.docker (docker-push): WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
    docker.docker (docker-push): Login Succeeded
    docker.docker (docker-push): Configure a credential helper to remove this warning. See
    docker.docker (docker-push): https://docs.docker.com/engine/reference/commandline/login/#credentials-store
    docker.docker (docker-push): Pushing: teichae/packer-nginx:latest
    docker.docker (docker-push): The push refers to repository [docker.io/teichae/packer-nginx]
    docker.docker (docker-push): 7fd23a2bd6bd: Preparing
    docker.docker (docker-push): 7a694df0ad6c: Preparing
    docker.docker (docker-push): 3fd9df553184: Preparing
    docker.docker (docker-push): 805802706667: Preparing
    docker.docker (docker-push): 7a694df0ad6c: Layer already exists
    docker.docker (docker-push): 805802706667: Layer already exists
    docker.docker (docker-push): 3fd9df553184: Layer already exists
    docker.docker (docker-push): 7fd23a2bd6bd: Pushed
    docker.docker (docker-push): latest: digest: sha256:c96e6656210cb1ebc298fe749df5bfc5e41e3717235552f89f75001faefa9673 size: 1155
    docker.docker (docker-push): Pushing: teichae/packer-nginx:latest
    docker.docker (docker-push): The push refers to repository [docker.io/teichae/packer-nginx]
    docker.docker (docker-push): 7fd23a2bd6bd: Preparing
    docker.docker (docker-push): 7a694df0ad6c: Preparing
    docker.docker (docker-push): 3fd9df553184: Preparing
    docker.docker (docker-push): 805802706667: Preparing
    docker.docker (docker-push): 805802706667: Layer already exists
    docker.docker (docker-push): 3fd9df553184: Layer already exists
    docker.docker (docker-push): 7fd23a2bd6bd: Layer already exists
    docker.docker (docker-push): 7a694df0ad6c: Layer already exists
    docker.docker (docker-push): latest: digest: sha256:c96e6656210cb1ebc298fe749df5bfc5e41e3717235552f89f75001faefa9673 size: 1155
    docker.docker (docker-push): Logging out...
    docker.docker (docker-push): Removing login credentials for https://index.docker.io/v1/
Build 'docker.docker' finished after 6 minutes 16 seconds.

==> Wait completed after 6 minutes 16 seconds

==> Builds finished. The artifacts of successful builds are:
--> docker.docker: Imported Docker image: sha256:4810af171821da5a2dd662b2ba52038964b472eb1491c8c0b9d5c311f5ac9b4f
--> docker.docker: Imported Docker image: teichae/packer-nginx:latest with tags teichae/packer-nginx:latest
--> docker.docker: Imported Docker image: teichae/packer-nginx:latest with tags teichae/packer-nginx:latest

 

빌드 후 Docker Hub에 업로드된 내용까지 확인이 됩니다.

 

이제 Docker Hub 홈페이지에서 정상적으로 업로드되었는지 확인하여 봅니다.

 

업로드된 것을 확인할 수 있습니다.

 

이처럼 Packer를 이용하여 Docker 이미지도 생성하고 이미지 생성에 성공하면 Repository로 배포까지 할 수 있고, 여러 프로바이더를 지원하기 때문에 편리하고 유용한 툴인 것을 확인할 수 있었습니다.

 

긴 글 읽어주셔서 감사합니다.

 

 

본 글에서 사용한 소스코드는 아래에서 공개하고 있습니다. :)

 

Github

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.