본문 바로가기
MLOps/Doker & Kubernetes

[클라우드 스터디잼] Introduction to Docker

by 공부하는 무니 2022. 7. 7.
반응형

* 클라우드 스터디잼 쿠버네티스 입문반 과정을 수강하면서 정리한 내용입니다. 

Overview

Docker는 애플리케이션을 개발하고 배포하고 실행하기 위한 오픈 플랫폼입니다. Docker를 사용하면 애플리케이션을 인프라에서 분리하며 인프라 자체도 관리되는 애플리케이션처럼 다룰 수 있게 됩니다. Docker를 사용하면 더 빠르게 코드를 제공하고, 더 빠르게 테스트하고, 더 빠르게 배포하고, 코드 작성과 코드 실행 사이의 주기를 단축할 수 있습니다.
Docker 컨테이너는 Kubernetes에서 직접 사용할 수 있으며, Kubernetes Engine에서 쉽게 실행할 수 있습니다. Docker의 핵심적인 내용을 먼저 배운다면 Kubernetes 및 컨테이너화된 애플리케이션 개발을 시작할 수 있는 기술을 갖게 됩니다.

 

What you'll learn

이 실습에서는 다음을 배웁니다.

  • Docker 컨테이너를 빌드, 실행, 디버그하는 방법
  • Docker Hub 및 Google Container Registry에서 Docker 이미지를 가져오는 방법.
  • Docker 이미지를 Google Container Registry에 푸시하는 방법.

 

Prerequisites

이 실습은 입문 레벨 실습으로, Docker 및 컨테이너 사용 경험이 거의 없는 사람을 대상으로 합니다. Cloud Shell 및 명령줄을 잘 안다면 실습을 더 수월하게 할 수 있지만 필수 사항은 아닙니다. 더 상위 단계의 자료를 찾고 있다면 아래 실습을 확인하세요. 

 

Activate Cloud Shell

Google Cloud Shell은 다양한 개발 도구가 탑재된 가상 머신으로, 5GB의 영구적인 홈 디렉토리를 제공하며 Google Cloud에서 실행됩니다. Google Cloud Shell을 사용하면 명령줄을 통해서 GCP 리소스에 접근할 수 있습니다. 

 

1. GCP Console의 오른쪽 상단 툴바에서 Cloud Shell 열기 버튼을 클릭합니다.

2. Cloud Shell에 관한 설명이 나옵니다. Continue를 클릭합니다. 환경을 설정하는 데 약간 시간이 걸립니다. 연결이 되면 "Welcome to Cloud Shell!" 이라는 메시지와 함께 PROJECT_ID가 나타납니다. 

▼ 아웃풋

Your Cloud Platform project in this session is set to YOUR_PROJECT_ID

gcloud

gcloud는 Google Cloud Platform의 명령줄 도구입니다. Cloud Shell에 사전설치되어 있으며, 탭 자동 완성을 지원합니다. 

사용 중인 계정 목록

다음 명령어로 사용 중인 계정 목록을 표시할 수 있습니다. 

▼ 명령어

gcloud auth list

▼ 아웃풋

ACTIVE: *
ACCOUNT: student-01-xxxxxxxxxxxx@qwiklabs.net
To set the active account, run:
    $ gcloud config set account `ACCOUNT`

프로젝트 ID 목록

다음 명령어로 프로젝트 ID목록을 표시할 수 있습니다.

▼ 명령어

gcloud config list project

▼ 아웃풋

[core]
project = <project_ID>

▼ 아웃풋 예시

 

[core]
project = qwiklabs-gcp-44776a13dea667a6

gcloud 관련 전체 설명은 아래 링크에서 확인할 수 있습니다.

gcloud command-line tool overview.

 

Hello World

Cloud Shell을 열고 다음 명령어를 입력하여 hello world 컨테이너를 실행합니다.

▼ 명령어

docker run hello-world

▼ 아웃풋

 

이 간단한 컨테이너는 화면에 Hello from Docker! 메시지를 리턴합니다. 명령어는 간단하지만 출력된 결과에서 실행된 단계 개수에 주목하세요. (총 4단계) Docker 데몬이 hello-world이미지를 검색했으나 로컬에서 이미지를 찾지 못했고, Docker Hub라는 공개 레지스트리에서 이미지를 가져오고, 가져온 이미지에서 컨테이너를 생성하고, 컨테이너를 실행했습니다. 

 

아래 명령어로 Docker Hub에서 가져온 컨테이너 이미지를 확인하세요.

▼ 명령어

docker images

▼ 아웃풋

REPOSITORY     TAG      IMAGE ID       CREATED       SIZE
hello-world    latest   1815c82652c0   6 days ago    1.84 kB

이미지를 Docker Hub 공개 레지스트리에서 가져왔습니다. 이미지 ID는 SHA256 hash 형식입니다. Docker 데몬이 로컬에서 이미지를 찾을 수 없으면 기본적으로 공개 레지스트리에서 이미지를 검색합니다. 컨테이너를 다시 실행해 보겠습니다.

▼ 명령어

docker run hello-world

▼ 아웃풋

두번째로 실행했을 때 도커 데몬이 로컬 레지스트리에서 이미지를 찾은 뒤 이 이미지에서 컨테이너를 실행하였음을 확인할 수 있습니다. 도커 데몬이 반드시 도커 허브에서 이미지를 가져올 필요는 없습니다. 

 

마지막으로 다음 명령어를 실행하여 실행 중인 컨테이너를 확인합니다.

▼ 명령어

docker ps

▼ 아웃풋

CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

실행중인 컨테이너가 없음을 확인할 수 있습니다. 앞에서 실행한 hello-world컨테이너는 이미 종료되었습니다. 실행이 완료된 컨테이너를 포함해서 모든 컨테이너를 보려면 아래 명령어로 확인하시면 됩니다.

▼ 명령어

docker ps -a

▼ 아웃풋

CONTAINER ID      IMAGE           COMMAND      ...     NAMES
6027ecba1c39      hello-world     "/hello"     ...     elated_knuth
358d709b8341      hello-world     "/hello"     ...     epic_lewin

여기에는 도커가 컨테이너를 식별하기 위해 생성한 UUID인 CONTAINER ID와 실행과 관련한 추가 메타데이터가 표시됩니다. 또한 컨테이너 NAMES도 무작위로 생성됩니다. 이 NAMES는 docker run --name [container_name] hello-world 와 같이 --name 옵션으로 이름을 지정해서 사용할 수 있습니다.

 

Build

다음으로, 간단한 노드 애플리케이션을 기반으로하는 도커 이미지를 빌드해보겠습니다. 다음 명령어를 실행해서 test라는 이름의 폴더를 만들고 이 폴더로 들어갑니다.

▼ 명령어

mkdir test && cd test

▼ 아웃풋

도커파일 생성

Dockerfile 파일을 만듭니다.

▼ 명령어

cat > Dockerfile <<EOF
# Use an official Node runtime as the parent image
FROM node:lts
# Set the working directory in the container to /app
WORKDIR /app
# Copy the current directory contents into the container at /app
ADD . /app
# Make the container's port 80 available to the outside world
EXPOSE 80
# Run app.js using node when the container launches
CMD ["node", "app.js"]
EOF

이 파일은 Docker 데몬에게 이미지를 빌드하는 방법을 지시하는 파일입니다.

  • 첫 번째 행은 기본 상위 이미지를 지정합니다. 이 경우에는 노드 버전 6의 공식 도커 이미지를 지정했습니다.
  • 두 번째 핼은 컨테이너의 (현재)작업 디렉토리를 설정합니다.
  • 세 번째 행은 현재 디렉토리의 내용("."으로 표시)을 컨테이너에 추가합니다.
  • 그런 다음 컨테이너의 포트를 공개하여 공개된 컨테이너 포트에서의 연결을 허용하고, 마지막으로 노드 명령어를 실행하여 애플리케이션을 시작합니다.

도커파일 명령어 참조: Dockerfile command references 

 

노드 애플리케이션 생성

이제 노트 애플리케이션을 작성한 다음 이미지를 빌드해 보겠습니다.

다음 명령을 실행하여 노드 애플리케이션을 생성합니다.

▼ 명령어

cat > app.js <<EOF
const http = require('http');
const hostname = '0.0.0.0';
const port = 80;
const server = http.createServer((req, res) => {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/plain');
    res.end('Hello World\n');
});
server.listen(port, hostname, () => {
    console.log('Server running at http://%s:%s/', hostname, port);
});
process.on('SIGINT', function() {
    console.log('Caught interrupt signal and will exit');
    process.exit();
});
EOF

 

이 애플리케이션은 간단한 http서버로, 포트 80으로 수신하고 'Hello World'를 리턴합니다.

 

이미지 빌드

이제 이미지를 빌드해보겠습니다.

다음 명령어를 Dockerfile이 있는 디렉토리에서 실행합니다. 현재 디렉토리를 의미하는 "." 를 사용함에 다시 한번 주목해주세요.

▼ 명령어

docker build -t node-app:0.1 .

-t는 name:tag 구문을 사용하여 이미지의 이름과 태그를 지정하는 역할을 합니다. 이미지 이름은 node-app이고, 태그는 0.1입니다. Docker 이미지를 빌드할 때에는 태그를 사용하는 것이 좋습니다. 태그를 지정하지 않으면 태그가 기본값인 latest로 지정되어 최신 이미지와 기존 이미지를 구분하기 어려워집니다. 

이 명령어를 완전히 실행하는 데에는 몇 분 정도 걸릴 수 있습니다. 실행이 끝나면 다음과 같은 결과가 출력됩니다.

▼ 아웃풋

이미지를 빌드할 때 위 Dockerfile의 각 행을 통해 중간 컨테이너 레이어가 만들어지는 방식을 확인하세요.

 

이제 다음 명령을 실행하여 빌드한 이미지를 확인합니다.

▼ 명령어

docker images

▼ 아웃풋

REPOSITORY     TAG      IMAGE ID        CREATED            SIZE
node-app       0.1      f166cd2a9f10    25 seconds ago     656.2 MB
node           lts      5a767079e3df    15 hours ago       656.2 MB
hello-world    latest   1815c82652c0    6 days ago         1.84 kB

node는 기본 이미지이고 node-app은 사용자가 빌드한 이미지 입니다. node를 제거하려면 우선 node-app을 제거해야 합니다. 이미지의 크기는 VM에 비해 상대적으로 작습니다. node:slim 및 node:alpine과 같은 노드 이미지의 다른 버전을 사용하면 더 작은 이미지를 제공하여 이식성을 높일 수 있습니다. 컨테이너 크기를 줄이는 주제에 대해서는 고급 주제에서 자세히 설명하도록 하겠습니다. 다음 공식저장소에서 모든 버전을 확인할 수 있습니다.

공식 저장소:  here.

 

Run

컨테이너 실행

아래 명령어를 사용하여 빌드한 이미지를 기반으로하는 컨테이너를 실행합니다.

▼ 명령어

docker run -p 4000:80 --name my-app node-app:0.1

--name 플래그를 사용하면 컨테이너 이름을 지정할 수 있습니다. (위에서도 언급함) -p 는 도커가 컨테이너의 포트 80에, 호스트의 포트 4000에 매핑하도록 지시하는 플래그입니다. 

▼ 아웃풋

이제 http://localhost:4000에 접속할 수 있습니다. 포트 매핑이 없으면 localhost에서 컨테이너에 접속할 수 없습니다. 

테스트

다른 터미널을 열고(Cloud Shell에서 + 아이콘을 클릭), 서버를 테스트해봅시다.

▼ 명령어

curl http://localhost:4000

▼ 아웃풋

처음 터미널이 실행되고 있어야 해당 컨테이너가 실행됩니다. 컨테이너를 터미널 세션에 종속시키지 않고 백그라운드에서 실행하려면 -d 플래그를 지정해야 합니다. 

컨테이너 중단&삭제

처음 열었던 터미널을 닫은 후 다음 명령어를 실행하여 컨테이너를 중단하고 삭제합니다.

▼ 명령어

docker stop my-app && docker rm my-app

▼ 아웃풋

백그라운드에서 컨테이너 실행

이제 다음 명령어를 실행하여 백그라운드에서 컨테이너를 시작합니다.

▼ 명령어

docker run -p 4000:80 --name my-app -d node-app:0.1
docker ps

▼ 아웃풋

컨테이너가 docker ps 결과에서 실행 중임에 주목하세요.

로그 보기

docker logs [container_id]를 실행하면 로그를 볼 수 있습니다.

Tip: 컨테이너 아이디를 전부 작성할 필요는 없습니다. 예를 들어 컨테이너 ID가 17bcaca6f....일 경우 'docker logs 17b'만 입력해서 로그를 볼 수 있습니다.

 

▼ 명령어

docker logs [container_id]

 

▼ 아웃풋

애플리케이션 수정

다음으로, 애플리케이션을 수정해보겠습니다. 앞서 실습에서 만든 텍스트 디렉토리를 엽니다.

▼ 명령어

cd test

원하는 텍스트 편집기(ex. nano, vim)로 app.js를 편집합니다. 'Hello World'를 'Welcome to Cloud'로 바꿉니다.

....
const server = http.createServer((req, res) => {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/plain');
    res.end('Welcome to Cloud\n');
});
....

저는 vi로 편집했습니다. 

  1. vi app.js 입력
  2. i 입력하여 수정모드
  3. 수정 완료 후 esc버튼 눌러 수정모드 빠져나온 뒤,  :wq 입력하여 저장 후 종료

새 이미지 빌드 

수정한 새 이미지를 빌드하고, 0.2로 태그를 지정합니다.

▼ 명령어

docker build -t node-app:0.2 .

▼ 아웃풋

2단계에서 기존 캐시 레이어를 사용하고 있음에 주목해주세요. app.js를 수정했기 때문에 3단계 이후부터 레이어가 수정되었습니다. 

새 이미지 버전으로 다른 컨테이너 실행

새 이미지 버전으로 다른 컨테이너를 실행해봅시다. 이 때 호스트 포트를 80 대신 8080으로 매핑합니다. 호스트 포트 4000은 이미 사용 중이므로 사용할 수 없습니다.

▼ 명령어

docker run -p 8080:80 --name my-app-2 -d node-app:0.2
docker ps
▼ 아웃풋

컨테이너 테스트

▼ 명령어

curl http://localhost:8080
▼ 아웃풋

이제 처음 작성한 컨테이너를 테스트합니다.

▼ 명령어

curl http://localhost:4000

▼ 아웃풋

 

Debug

컨테이너 빌드와 실행을 실습했으니, 이제 몇 가지 디버깅 사례를 살펴보겠습니다.

실행 중인 컨테이너 로그 보기

docker logs [container_id]를 사용하여 컨테이너의 로그를 볼 수 있었습니다. 컨테이너가 실행 중일 때 로그를 확인하려면 -f 옵션을 사용합니다.

▼ 명령어

docker logs -f [container_id]

▼ 아웃풋

bash 세션

가끔은 실행중인 컨테이너에서 대화식 bash 세션을 시작해야 할 수도 있습니다. 이 경우 docker exec를 사용합니다. 다른 터미널을 열고 다음 명령어를 입력합니다.

▼ 명령어

docker exec -it [container_id] bash

-it 플래그는 pseudo-tty를 할당하고 stdin을 열린 상태로 유지하여 컨테이너와 상호작용할 수 있도록합니다. 

▼ 아웃풋

Dockerfile에 지정된 WORKDIR 디렉초리(/app)에서 bash가 실행된 것을 확인할 수 있습니다. 이제 디버깅할 컨테이너 내에서 대화형 셸 세션을 사용할 수 있습니다.

디렉토리 확인

▼ 명령어

ls
▼ 아웃풋

bash 세션 종료

bash세션을 종료하려면 새 터미널에 아래 명령어를 입력합니다.

▼ 명령어

exit

▼ 아웃풋

컨테이너 메타데이터 검토

Docker inspect를 통해서 컨테이너의 메타데이터를 검토할 수 있습니다.

▼ 명령어

docker inspect [container_id]
▼ 아웃풋

--format을 사용하여 리턴된 JSON의 특정 필드를 검사할 수 있습니다. 

▼ 명령어

docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' [container_id]

▼ 아웃풋

192.168.9.3

디버깅과 관련된 자세한 내용은 아래 리소스를 참고하세요.

 

 

Publish

이제 이미지를 Google Container Registry (gcr)로 푸시하도록 하겠습니다. 그런 다음 모든 컨테이너와 이미지를 제거하여 새로운 환경을 시뮬레이션하고 컨테이너를 가져와서 실행합니다. 이를 통해 Docker 컨테이너의 이식성을 확인하도록 하겠습니다. 

GCR에서 호스팅하는 비공개 레지스트리에 이미지를 푸시하려면 이미지에 레지스트리 이름으로 태그를 지정해야 합니다. 양식은 [hostname]/[project-id]/[image]:[tag] 입니다. 

GCR의 경우 아래 값으로 지정됩니다.

  • [hostname]= gcr.io
  • [project-id]= your project's ID
  • [image]= your image name
  • [tag]= any string tag of your choice. If unspecified, it defaults to "latest".

 

다음 명령을 실행하여 프로젝트 ID를 찾을 수 있습니다.

▼ 명령어

gcloud config list project

▼ 아웃풋

[core]
project = [project-id]
Your active configuration is: [default]

node-app:0.2를 태그하고,  [project-id] 를 내 구성으로 바꿉니다.

▼ 명령어

docker tag node-app:0.2 gcr.io/[project-id]/node-app:0.2
docker images

▼ 아웃풋

REPOSITORY                      TAG         IMAGE ID          CREATED
node-app                        0.2         76b3beef845e      22 hours ago
gcr.io/[project-id]/node-app    0.2         76b3beef845e      22 hours ago
node-app                        0.1         f166cd2a9f10      26 hours ago
node                            lts         5a767079e3df      7 days ago
hello-world                     latest      1815c82652c0      7 weeks ago

이 이미지를 GCR로 푸시합니다.  

▼ 명령어

docker push gcr.io/[project-id]/node-app:0.2

▼ 아웃풋

Command output (yours may differ):

The push refers to a repository [gcr.io/[project-id]/node-app]
057029400a4a: Pushed
342f14cb7e2b: Pushed
903087566d45: Pushed
99dac0782a63: Pushed
e6695624484e: Pushed
da59b99bbd3b: Pushed
5616a6292c16: Pushed
f3ed6cb59ab0: Pushed
654f45ecb7e3: Pushed
2c40c66f7667: Pushed
0.2: digest: sha256:25b8ebd7820515609517ec38dbca9086e1abef3750c0d2aff7f341407c743c46 size: 2419

웹 브라우저의 이미지 레지스트리로 이동하여 GCR에 이미지가 있는지 확인하세요. 콘솔에서 도구> Container Registry로 이동하거나 http://gcr.io/[project-id]/node-app을 방문하여 확인할 수 있습니다. 아래와 비슷한 페이지로 연결될 것입니다.

저는 아래와 같이 container registry를 검색해서 찾았습니다. 

이 이미지를 테스트해보겠습니다. 새로운 VM을 시작하고 SSH로 새로운 VM에 접속한 다음 gcloud를 설치할 수도 있지만, 여기서는 간단하게 모든 컨테이너와 이미지를 제거하여 새로운 환경으로 시뮬레이션하겠습니다.

모든 컨테이너를 중지하고 제거합니다.

▼ 명령어

docker stop $(docker ps -q)
docker rm $(docker ps -aq)

▼ 아웃풋

노드 이미지를 제거하기 전에 node:lts의 하위 이미지를 제거해야 합니다. 

▼ 명령어

docker rmi node-app:0.2 gcr.io/[project-id]/node-app node-app:0.1
docker rmi node:lts
docker rmi $(docker images -aq) # remove remaining images
docker images

▼ 아웃풋

REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE

이제 새로운 환경이나 다름없습니다. 이미지를 푸시하여 실행합니다.

▼ 명령어

docker pull gcr.io/[project-id]/node-app:0.2
docker run -p 4000:80 -d gcr.io/[project-id]/node-app:0.2
curl http://localhost:4000

▼ 아웃풋

Welcome to Cloud

Congratulations

Docker 소개 과정 학습을 완료했습니다. 이 과정에서 배운 내용은 아래와 같습니다.

  • Ran containers based on public images from Docker Hub.
  • Built your own container images and pushed them to Google Container Registry.
  • Learned ways to debug running containers.
  • Ran containers based on images pulled from Google Container Registry.

Take Your Next Lab

Continue your Quest with Hello Node Kubernetes, or check out these suggestions:

Next Steps / Learn More

반응형

댓글