도커 컨테이너 배포
도커/쿠버네티스를 활용한 컨테이너 개발 실전 입문
Chapter02 도커 컨테이너 배포
01 컨테이너로 애플리케이션 실행하기
- 도커 이미지 vs 도커 컨테이너
- 도커 이미지 = 도커 컨테이너를 구성하는 파일 시스템 설정 + 실행할 애플리케이션 설정
- 도커 컨테이너 = 도커 이미지가 구체화돼 실행되는 상태
- 도커 이미지 다운로드
docker image pull gihyodocker/echo:latest
- 처음 받으면 모든 레이어 이미지를 받는 것을 볼수 있다.
도커 명령어 모음
도커 명령어 | 용도 | remark |
---|---|---|
docker image pull | 도커 허브에서 이미지 다운로드 | =docker pull |
docker image build | 도커 이미지 빌드 | =docker build, -t 태그 |
docker image ls | 도커 이미지 리스트 | -q : 컨테이너 아이디만 조회 가능 |
docker container run | 도커 컨테이너 실행 | = docker run -p : 포트 |
-d=true or -d : 컨테이너 실행시 로그 올라오는데 안봐도 되면 백그라운드 모드로 실행 | ||
-t : Allocate a pseudo-tty (foreground mode에서 컨테이너에 터미널로 접속할 수 있게 해준다. -t explanation) | ||
docker container ls | 도커 컨테이너 리스트 조회 | “docker ps” 와 같고, “docker ps -a”인 경우 실행중이지 않고, 삭제되지 않은 컨테이너를 볼 수 있다. |
docker container stop [컨테이너 아이디 혹은 이름] | 도커 컨테이너 정지 | (docker stop) |
docker rm [컨테이너 아이디 혹은 이름] | 도커 컨테이너 삭제 |
간단한 애플리케이션과 도커 이미지 만들기
순서
- 애플리케이션을 만든다. -> main.go
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Println("received request")
fmt.Fprintf(w, "Hello Docker!!")
})
log.Println("start server")
server := &http.Server{Addr: ":8080"}
if err := server.ListenAndServe(); err != nil {
log.Println(err)
}
}
- 애플리케이션을 기반으로 도커 이미지를 만든다. -> Dockerfile
FROM golang:1.9
RUN mkdir /echo
COPY main.go /echo
CMD ["go", "run", "/echo/main.go"]
- 도커파일 인스트럭션
인스트럭션(명령) | 용도 | remark |
---|---|---|
FROM | 도커 이미지의 바탕이 될 베이스 | 도커 허브에서 받아오며 ‘:’ 뒤는 태그라고 하며 이미지 버전을 구별하는 식별자 |
도커 이미지는 각자 고유의 해시값을 갖는데, 태그가 없으면 해시만으로 구별해야 하여 쉽게 파악이 안되니 태그를 붙이는것이 좋다. | ||
RUN | 도커 이미지를 실행할 때 컨테이너 안에서 실행할 명령을 정의 | 예제에서는 애플리케이션을 배치하기 위한 /echo 디렉토리를 만든다. |
이미지를 빌드할 때 실행된다. | ||
COPY | 도커가 동작중인 호스트 머신의 파일/디렉토리 -> 도커 컨테이너 안으로 복사 | 비슷한 기능으로 ADD가 있다. |
CMD | 도커 컨테이너를 실행할 때 안에서 실행할 프로세스 | 명령을 공백으로 나눈 배열로 구성되며, 컨테이너를 시작할 때 1회 실행된다. |
CMD[“실행파일”, “인자1”, “인자2”] -> 실행파일에 인자를 전달. 권장 사용방식 | ||
CMD “명령인자” “인자1” “인자2” -> 명령과 인자 지정으로 셸에서 실행되므로 셸에 정의된 변수를 참조할 수 있다. | ||
CMD [“인자1”, “인자2”] -> ENTRYPOINT에 지정된 명령어에 사용할 인자를 전달한다. | ||
LABEL | 이미지를 만든 사람의 이름 등을 적을 수 있다. | |
ENV | 컨테이너 안에서 활용할 수 있는 환경변수 지정 | |
ARG | 이미지 빌드 시 정보를 함께 넣기 위한 용도로 빌드할때만 사용된다. |
- LABEL, ENV, ARG 포함 dockerfile 예시
FROM alpine:3.7
LABEL maintainer="user@example.com"
ARG builddate
ENV BUILDDATE=${builddate}
ENV BUILDFROM="from Alpine"
ENTRYPOINT ["bin/bash", "-c"]
CMD ["env"]
docker image build --build-arg builddate=today -t example/others
- 도커 이미지를 빌드 -> (docker image build -t [이미지명(:태그명)] [도커파일경로] )
docker image build -t example/echo .
이미지명의 ‘/’ 앞은 네임스페이스로 이미지명이 겹치는 것을 방지하기 위함이다. 빌드된 이미지는 다음 명령어로 조회할 수 있다.
docker image ls
- 도커 이미지를 기반으로 도커 컨테이너를 실행한다. -> (docker container run [이미지명:태그])
docker container run example/echo:latest
docker container run -d example/echo:latest
- -d 옵션을 주고 실행하였을시 나오는 해시값처럼 생긴 문자열은 도커 컨테이너의 ID로 유일 식별자이다.
포트포워딩
main.go 코드를 보면 8080을 리스닝하고 있지만 curl http://localhost:8080
으로 요청을 보내면 Connection refused 메세지를 받는다.
- 도커 컨테이너는 가상 환경이지만 외부에서 봤을 때 독립된 하나의 머신처럼 다룰 수 있다.
- 애플리케이션은 8080을 리스닝하지만, 이 포트는 컨테이너 포트라고 하여 컨테이너에 한정된 포트이다. 따라서, 컨테이너 안에서 curl로 8080 접근하면 가능하지만 host os에서는 접근이 안된다.
- 도커 포워딩을 사용하여 컨테이너 밖에서 온 요청을 컨테이너 안에 있는 애플리케이션에 전달해야한다.
- 현재 실행중인 컨테이너 정지
docker container stop $(docker container ls --filter "ancestor=example/echo" -q)
- 포트포워딩을 포함하여 실행한다. (-p 호스트포트:컨테이너포트)
docker container run -d -p 9000:8080 example/echo:latest
- 호스트포트로 요청한다. (curl http://localhost:9000)
- 현재 실행중인 컨테이너 정지
- 호스트 포트를 생략하고 포트포워딩을 사용하면 도커가 자동으로 할당함으로 부여받은 포트로 접근하면 된다.
02 도커 이미지 다루기
docker image build - 이미지 빌드
- 옵션 -f : 그냥 docker image build를 사용하게 되면 기본적으로 Dockerfile 이라는 이름을 가진 Dockerfile을 찾아 빌드한다.
그외 다른 이름으로 된 Dockerfile을 사용하고 싶다면 -f 옵션을 붙여 주어야 한다.
docker image build -f Dockerfile-test -t example/echo:latest .
- 옵션 –pull : Dockerfile 의 FROM에 기재된 베이스 이미지를 받게 되면 호스트 운영체제에 저장되기 때문에 이미 저장된 레이어 이미지는 다시 받아오지 않는다.
만일 강제로 이미지를 새로 받아 오고 싶다면 옵션 -pull=true를 사용한다.
작동 원리는 도커 허브에서 최신 버전이 있는지 확인하고 빌드를 하기 때문에 빌드 속도 면에서는 조금 불리하다.
docker image build --pull=true -t example/echo:latest .
docker search - 이미지 검색
- 도커 허브에 등록된 리포지토리를 검색할 수 있다. (docker search [options] 검색키워드)
docker search --limit 5 mysql
- 목록 중 네임스페이스가 생략되어 있는 mysql을 볼 수 있는데 이 리포지토리가 공식 mysql 리포지토리이다. 공식 리포지토리의 네임스페이스는 모두 library이며 생략할 수 있다.
- 리포지토리 검색은 가능하지만, 리포지토리가 관리하는 도커 이미지의 태그까지는 검색할 수 없으니 다음 API를 사용한다.
curl -s 'https://hub.docker.com/v2/repositories/library/golang/tags/?page_size=10' | jq -r '.results[].name'
docker image tag - 이미지 태그 붙이기
- 어플리케이션 파일이나 이미지 빌드에 사용되는 Dockerfile을 변경하고 빌드하면 새로운 ID를 갖는 이미지가 생성되며, 이전 이미지가 latest였어도,
새로운 이미지가 생성되면 이전 이미지는 tag가
이 된다. (latest 태그는 한개밖에 사용못함) ![tag_latest.png](/assets/images/docker/ch02/tag_latest.png) - 도커의 버전은 이미지 ID 로 관리가 되며, tag는 별칭인데, none으로 변경되면서 어떤 이미지였는지 구분이 쉽지 않으니 tag를 따로 붙여줄 수도 있다.
docker image tag example/echo:latest example/echo:0.1.0
- 그럼 이제 이미지가 한개가 더 생기는데 id가 동일함을 확인할 수 있어 동일한 이미지를 가리킨다는 것을 알 수 있다. 태그만 다름.
docker image push - 이미지 외부에 공개
- 도커 이미지를 도커 허브 등의 레지스트리에 등록하기 위해 사용한다. 이때 네임스페이스는 자신, 혹은 소속 기관이 소유한 레포지토리에만 등록할 수 있어, 네임스페이스를 변경한다.
- 도커 허브에 푸시하면 터미널에 프로그레스바가 생기고, 등록이 완료되면 sha256 해시값이 출력된다. 올라가고 나면 누구나 (docker image pull) 명령어로 받을 수 있다.
03 도커 컨테이너 다루기
도커 컨테이너 생명주기
- 실행,정지,파기의 상태를 가진다.
- 각 컨테이너는 같은 이미지로 생성했다고 해도 별개의 상태를 갖는데, 상태를 가지지 않는 이미지와의 가장 큰 차이이다.
- 컨테이너가 정지되고 파기되지 않는다면, 디스크에 그대로 남아있게 되고 디스크를 차지하는 용량이 많이 질 것을 대비해 불필요한 컨테이너는 완전히 삭제하는것이 좋다.
- 파기가 된 컨테이너는 다시 실행할 수 없다.
docker container run - 컨테이너 생성 및 실행
- 컨테이너에 이름 붙이기
- 컨테이너를 실행하고 나서
docker container ls
명령으로 컨테이너 목록을 보면 NAMES 목록에 무작위 단어로 이름이 지어진 것을 볼 수 있다. 옵션으로 –name을 사용하여 이름을 붙여줄 수 있는데 개발단계에서는 용이하나 실제 운영에서는 같은 이름을 또 생성하려면 삭제하고 진행하여야 하여 잘 사용하지 않는다.
docker container run -d --name echo example/echo
- 컨테이너를 실행하고 나서
항목 | 내용 |
---|---|
CONTAINER ID | 컨테이너를 식별하기 위한 유일 식별자 |
IMAGE | 컨테이너를 만드는데 사용된 도커 이미지 |
COMMAND | 컨테이너에서 실행되는 애플리케이션 프로세스 |
CREATED | 컨테이너 생성 후 경과된 시간 |
STATUS | Up(실행중), Exited(종료) 등 컨테이너의 실행 상태 |
PORTS | 호스트 포트와 컨테이너 포트의 연결관계 (포트 포워딩) |
NAMES | 컨테이너의 이름 |
- 컨테이너 ID만 추출 하기
docker container ls -q
- 컨테이너 목록 필터링 하기 (docker container ls –filter “필터명=값”)
- 아래 예제는 name컬럼으로 필터한다.
docker container ls --filter "name=echo"
- 컨테이너를 생성한 이미지를 기준으로 하면 ancestor라는 필드명을 사용한다.
docker container ls --filter "ancestor=example/echo"
- 종료된 컨테이너 목록 조회
docker container ls -a
- 아래 예제는 name컬럼으로 필터한다.
docker container stop - 컨테이너 정지
- 컨테이너 정지
docker container stop 컨테이너 ID 또는 컨테이너명
- 컨테이너 재시작
docker container restart 컨테이너 ID 또는 컨테이너명
docker container rm - 컨테이너 삭제
개발중이면, 컨테이너를 여러번 실행/정지 할텐데, 그럼 정지된 컨테이너들이 쌓일 수 있고, 이것들을 조회하여 컨테이너 아이디로 삭제할수 있다.
- 정지된 컨테이너만 조회
docker container ls --filter "status=exited"
- ID 혹은 이름을 삭제
docker container rm c9ba48fa6962
- 혹은 ㅈ어지된 컨테이너는 항상 삭제하고 싶은 경우가 있을 수 있따. 이때는
docker container run --rm
을 사용해 컨테이너를 정지할 때 함께 삭제하게 할 수 있다.
docker container logs - 표준 출력 연결하기
현재 실행 중인 특정 도커 컨테이너의 표준 출력 내용을 확인할 수 있는 명령어로, 컨테이너의 출력 내용중 표준 출력만 확인할 수 있으며 파일에 출력한 것은 볼 수 없다.
-f 옵션을 사용하면 라이브로 출력이 가능하다. (docker container logs -f 컨테이너ID또는이름)
docker container logs echo
docker container exec - 실행 중인 컨테이너에서 명령어 실행하기
실행중인 컨테이너에 대해 원하는 명령어를 실행할 수 있는데, 마치 컨테이너에 ssh로 로그인 한 것처럼 컨테이너 내부를 조작할 수 있다. 표준 입력 연결을 유지하는 -i와 유사 터미널을 할당하는 -t옵션을 좋바하면 컨테이너를 셸을 통해 다룰 수 있다.
docker container cp - 파일 복사하기
실행중인 컨테이너<>컨테이너, 컨테이너<>호스트 간에 파일을 복사하기 위한 명령어이다.
- 컨테이너의 파일을 호스트로 복사하려면 (docker container cp 컨테이너ID 혹은 명:원본파일 대상파일)
docker container cp echo:/echo/main.go .
- 호스트에서 컨테이너로 파일을 복사하려면 (docker container cp 호스트원본파일 컨테이너ID 또는 명:대상파일)
docker container cp ./cp/dummy.txt echo:/tmp
04 운영과 관리를 위한 명령
prune - 컨테이너 및 이미지 파기
- docker container prune : 실행중이 아닌 모든 컨테이너 삭제
- docker image prune : 태그가 붙지 않은 모든 이미지 삭제
- docker system prune : 사용하지 않는 도커 이미지 및 컨테이너, 볼륨, 네트워크 등 모든 도커 리소스 일괄적으로 삭제
docker container stats - 사용 현황 확인
유닉스 계열 운영체제의 top명령과 같은 역할을 한다고 보면 된다.
docker container stats
05 도커 컴포즈로 여러 컨테이너 실행하기
- 도커 컨테이너 = 단일 애플리케이션이라고 봐도 무방하며, 애플리케이션 간의 연동 없이는 실용적 수준의 시스템을 구축할 수 없다.
- 도커 컨테이너로 시스템을 구축하면 하나 이상의 컨테이너가 서로 통신하며 그 사이의 의존관계가 생기게된다.
- 컨테이너간의 의존관계를 고려한 포트포워딩, 컨테이너의 동작을 제어하기 위한 설정파일 및 환경변수 전달 등의 고려가 필요하다.
docker-compose 명령으로 컨테이너 실행하기
- yaml 포맷으로 기술된 설정 파일로, 어려 컨테이너의 실행을 한번에 관리할 수 있게 해준다.
- version : docker-compose 파일을 내용을 해석하기 위한 문법 버전
- services
- echo : 컨테이너 이름
- image : 도커 이미지
- ports : 포트 포워딩 설정
version: "3"
services:
echo:
image: example/echo:latest
ports:
- 9000:8080
-
docker-compose.yml에 기술된 컨테이너를 모두 실행하려면
docker compose up
을 활용하면 된다. - docker-compose.yml에 기술된 컨테이너를 모두 정지하려면
docker compose down
을 활용하면 된다. - 이미지를 함께 빌드해서 새로 생성한 이미지를 실행할 수 도 있다. image속성 대신, build 속성을 사용하면 된다. build 속성에는 Dockerfile의 경로를 지정해준다.
docker-compse 가 이미 이미지를 빌드한 적이 있다면, 생략하고 컨테이너가 실행되지만 –build 옵션을 사용해서 강제로 다시 빌드하게 할 수 있다.
version: "3" services: echo: # image: example/echo:latest build: . ports: - 9000:8080
docker compose up -d --build
06 컴포즈로 여러 컨테이너 실행하기
docker-compose 는 여러 컨테이너를 실행할때 매우 유용하다. 예로 젠킨스를 사용해보자.
젠킨스 컨테이너 실행하기
version: "3"
services:
master:
container_name: master
image: jenkins/jenkins:lts
ports:
- 8080:8080
volumes:
- ./jenkins_home:/var/jenkins_home
- volumes : 호스트와 컨테이너 사이에 파일을 공유할 수 있는 매커니즘이다. 젠킨스 컨테이너를 호스트쪽에서 편리하게 다룰 수 있도록 volumes를 정의해 호스트쪽 현재 작업 디렉터리 바로 아래에 jenkins_home 디렉터리를 젠킨스 컨테이너의 /var/jenkins_home에 마운트한다.
- 컨테이너를 실행하면 secret 정보가 /var/jekins_home에 저장되고 접근하여 확인할 수 있다.
마스터 젠킨스 용 SSH 키 생성
- 실제로 젠킨스를 운영할 때 단일 서버로 운영하는 경우는 흔치않다.
- 관리/작업실행 지시는 마스터 인스턴스가 진행하고, 작업을 실제로 진행하는 것은 슬레이브 인스턴스가 담당한다.
- 마스터가 슬레이브에 접근할 수 있도록 마스터 컨테이너에서 SSH키를 생성한다.
docker container exec -it master ssh-keygen -t rsa -C ""
- /var/jenkins_home/.ssh/id_sa.pub 파일이 마스터 젠킨스가 슬레이브 젠킨스에 접속할 때 사용할 키이다.
- 마스터가 슬레이브에 접근할 수 있도록 마스터 컨테이너에서 SSH키를 생성한다.
- 슬레이브 젠킨스 컨테이너 생성
- 슬레이브 컨테이너는 SSH로 접속하는 슬레이브용도로 구성된 도커 이미지 jenkins/ssh-slave를 사용한다.
- 환경변수로 JENKINS_SLAVE_SSH_PUBKEY를 설정하는데, SSH로 접속하는 상대가 이 키를 보고 마스터 젠킨스임을 식별한다.
- 슬레이브 컨테이너의 ~/.ssh/authorized_keys 파일에 마스터 컨테이너의 SSH공개키가 추가된다.
- ssh 접속 대상 설정
- 마스터 컨테이너가 어떻게 슬레이브 컨테이너를 찾아 추가할 것이가는, 마스터 컨테이너 설정에 links 속성을 사용하여 services 그룹에 해당하는 다른 컨테이너와 통신할 수 있다.
- 최종 yml
version: "3"
services:
master:
container_name: master
image: jenkins/jenkins:lts
ports:
- 8080:8080
volumes:
- ./jenkins_home:/var/jenkins_home
links:
- slave01
slave01:
container_name: slave01
image: jenkins/ssh-agent
environment:
- JENKINS_AGENT_SSH_PUBKEY=
-
docker compose up으로 띄우고 난뒤 jenkins에 접속하여 노드관리에서 slave node추가가 필요.
- 에러1. 책에있는 jenkins 이미지를 사용하면 플러그인 다운로드가 모두 실패하여 실습을 진행할 수 없다. 도커허브의 젠킨스 공식 이미지를 받아온다. (ssh 도 마찬가지)
- https://hub.docker.com/_/jenkins
- https://hub.docker.com/r/jenkins/ssh-agent
- 에러2. 젠킨스를 실행하고 slave를 연결하려고하면 java를 찾지 못해서 에러가 난다. launch log에 보면 JAVA_HOME 경로가 나오는데 이 경로를 복사하여, 설정에서 고급에 자바 경로에 넣어준다.
관련 서적
도커/쿠버네티스를 활용한 컨테이너 개발 실전 입문 link