2023 클라우드 스터디 잼 Introduction to Docker

Peter·2023년 4월 29일
0
post-thumbnail

과정 내용

이 실습에서는 다음 작업을 진행하는 방법을 학습합니다.

  • Docker 컨테이너 빌드, 실행, 디버그하기
  • Docker Hub 및 Google Artifact Registry에서 Docker 이미지 가져오기
  • Docker 이미지를 Google Artifact Registry로 푸시하기

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

gcloud auth list

출력 예:

Credentialed accounts:
- google1623327_student@qwiklabs.net

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

gcloud config list project

출력 예:

[core]
project = qwiklabs-gcp-44776a13dea667a6

작업 1. Hello World

  1. 시작하려면 Cloud Shell을 열고 다음 명령어를 입력하여 hello world 컨테이너를 실행합니다.
docker run hello-world

(명령어 결과)

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
9db2ca6ccae0: Pull complete
Digest: sha256:4b8ff392a12ed9ea17784bd3c9a8b1fa3299cac44aca35a85c90c5e3c7afacdc
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
...

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

  1. 다음 명령어를 실행하여 Docker Hub에서 가져온 컨테이너 이미지를 확인하세요.
docker images

(명령어 결과)

REPOSITORY TAG IMAGE ID CREATED SIZE

hello-world latest feb5d9fea6a5 14 months ago 13.3kB

Docker Hub 공개 레지스트리에서 가져온 이미지입니다. 이미지 ID는 SHA256 해시 형식입니다. 이 필드에서는 프로비저닝된 Docker 이미지를 지정합니다. Docker 데몬이 로컬에서 이미지를 찾을 수 없으면 기본적으로 공개 레지스트리에서 이미지를 검색합니다.

  1. 컨테이너를 다시 실행합니다.
docker run hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
...

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

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

(명령어 결과)

CONTAINER ID IMAGE COMMAND CREATED
STATUS PORTS NAMES

실행 중인 컨테이너가 없습니다. 전에 실행한 hello-world 컨테이너를 이미 종료했습니다.

  1. 실행이 완료된 컨테이너를 포함하여 모든 컨테이너를 보려면 docker ps -a를 실행하세요.
docker ps -a

(명령어 결과)

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

여기에는 Docker가 컨테이너를 식별하기 위해 생성한 UUID인 CONTAINER ID와 실행에 관한 추가 메타데이터가 표시됩니다. 또한 컨테이너 Names도 무작위로 생성되지만 docker run --name [container-name] hello-world를 사용하여 지정할 수 있습니다.

작업 2. 빌드

이 섹션에서는 간단한 노드 애플리케이션을 기반으로 한 Docker 이미지를 빌드합니다.

  1. 다음 명령어를 실행하여 test라는 이름의 폴더를 만들고 이 폴더로 전환합니다.
mkdir test && cd test
  1. 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 데몬에 이미지를 빌드하는 방법을 안내합니다.

첫 번째 줄은 기본 상위 이미지를 지정합니다. 이 경우 기본 상위 이미지는 노드 버전 장기적 지원(LTS)의 공식 Docker 이미지입니다.
두 번째 줄에서 컨테이너의 (현재) 작업 디렉터리를 설정합니다.
세 번째 줄에서는 현재 디렉터리의 콘텐츠("."로 표시)를 컨테이너에 추가합니다.
그런 다음 컨테이너의 포트를 공개하여 해당 포트에서의 연결을 허용하고 마지막으로 노드 명령어를 실행하여 애플리케이션을 시작합니다.

참고: 시간을 내어 Dockerfile 명령어 참조를 검토하고 Dockerfile의 각 줄을 숙지하세요.

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

  1. 다음 명령어를 실행하여 노드 애플리케이션을 생성합니다.
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

포트 80에서 수신 대기하고 'Hello World'를 반환하는 간단한 HTTP 서버입니다.

이제 이미지를 빌드합니다.

  1. 다음 명령어를 Dockerfile이 있는 디렉터리에서 실행해야 합니다. 현재 디렉터리를 의미하는 "."에 다시 한번 유의하세요.
docker build -t node-app:0.1 .

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

Sending build context to Docker daemon 3.072 kB
Step 1 : FROM node:lts
6: Pulling from library/node
...
...
...
Step 5 : CMD node app.js
---> Running in b677acd1edd9
---> f166cd2a9f10
Removing intermediate container b677acd1edd9
Successfully built f166cd2a9f10

-t는 name:tag 문법을 사용하여 이미지의 이름과 태그를 지정하는 역할을 합니다. 이미지 이름은 node-app이고 태그는 0.1입니다. Docker 이미지를 빌드할 때는 태그를 사용하는 것이 좋습니다. 태그를 지정하지 않으면 태그가 기본값인 latest로 지정되어 최신 이미지와 기존 이미지를 구분하기 어려워집니다. 또한 이미지를 빌드할 때 위 Dockerfile의 각 행을 통해 중간 컨테이너 레이어가 만들어지는 방식을 확인하세요.

  1. 이제 다음 명령어를 실행하여 빌드한 이미지를 봅니다.
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과 같은 노드 이미지의 다른 버전을 사용하면 더 작은 이미지를 제공하여 이식성을 높일 수 있습니다. 컨테이너 크기 줄이기에 관해서는 고급 주제에서 자세히 설명하겠습니다. 노드의 공식 저장소에서 모든 버전을 확인할 수 있습니다.

작업 3. 실행

  1. 이 모듈에서는 다음 코드를 사용하여 빌드한 이미지를 기반으로 하는 컨테이너를 실행합니다.
docker run -p 4000:80 --name my-app node-app:0.1

(명령어 결과)

Server running at http://0.0.0.0:80/

--name 플래그를 사용하면 원하는 경우 컨테이너 이름을 지정할 수 있습니다. -p는 Docker가 컨테이너의 포트 80에 호스트의 포트 4000을 매핑하도록 지시하는 플래그입니다. 이제 http://localhost:4000에서 서버에 접속할 수 있습니다. 포트 매핑이 없으면 localhost에서 컨테이너에 접속할 수 없습니다.

  1. 다른 터미널을 열고(Cloud Shell에서 + 아이콘을 클릭) 서버를 테스트합니다.

    curl http://localhost:4000

(명령어 결과)

Hello World

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

  1. 초기 터미널을 닫은 후 다음 명령어를 실행하여 컨테이너를 중지하고 삭제합니다.
docker stop my-app && docker rm my-app
  1. 이제 다음 명령어를 실행하여 백그라운드에서 컨테이너를 시작합니다.
docker run -p 4000:80 --name my-app -d node-app:0.1
docker ps

(명령어 결과)

CONTAINER ID IMAGE COMMAND CREATED ... NAMES

xxxxxxxxxxxx node-app:0.1 "node app.js" 16 seconds ago ... my-app

  1. docker ps의 출력된 결과에서 컨테이너가 실행 중임을 확인할 수 있습니다. docker logs [container_id]를 실행하면 로그를 볼 수 있습니다.

참고: 앞부분에 입력한 문자들로 컨테이너를 고유하게 식별할 수 있다면 전체 컨테이너 ID를 입력할 필요는 없습니다. 예를 들어 컨테이너 ID가 17bcaca6f....인 경우 docker logs 17b를 실행할 수 있습니다.

docker logs [container_id]

(명령어 결과)

Server running at http://0.0.0.0:80/

이제 애플리케이션을 수정합니다.

  1. Cloud Shell에서 앞서 실습에서 만든 테스트 디렉터리를 엽니다.
cd test
  1. 원하는 텍스트 편집기(예: nano 또는 vim)로 app.js를 편집하고 'Hello World'를 다른 문자열로 바꿉니다.
....
const server = http.createServer((req, res) => {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/plain');
    res.end('Welcome to Cloud\n');
});
....
  1. 이 새 이미지를 빌드하고 0.2로 태그를 지정합니다.
docker build -t node-app:0.2 .

(명령어 결과)

Step 1/5 : FROM node:lts
---> 67ed1f028e71
Step 2/5 : WORKDIR /app
---> Using cache
---> a39c2d73c807
Step 3/5 : ADD . /app
---> a7087887091f
Removing intermediate container 99bc0526ebb0
Step 4/5 : EXPOSE 80
---> Running in 7882a1e84596
---> 80f5220880d9
Removing intermediate container 7882a1e84596
Step 5/5 : CMD node app.js
---> Running in f2646b475210
---> 5c3edbac6421
Removing intermediate container f2646b475210
Successfully built 5c3edbac6421
Successfully tagged node-app:0.2

2단계에서 기존 캐시 레이어를 사용하고 있음을 확인할 수 있습니다. 3단계 이후부터는 app.js를 변경했기 때문에 레이어가 수정되었습니다.

(실제 해봤을 때 Step 5단계까지가 아니라 3단계까지만 나온다. 실행에는 지장 없다.)

  1. 새 이미지 버전으로 다른 컨테이너를 실행합니다. 이때 호스트 포트를 80 대신 8080으로 매핑하는 방법을 확인하세요. 호스트 포트 4000은 이미 사용 중이므로 사용할 수 없습니다.
docker run -p 8080:80 --name my-app-2 -d node-app:0.2
docker ps

(명령어 결과)

CONTAINER ID IMAGE COMMAND CREATED
xxxxxxxxxxxx node-app:0.2 "node app.js" 53 seconds ago ...
xxxxxxxxxxxx node-app:0.1 "node app.js" About an hour ago ...

  1. 컨테이너를 테스트합니다.
curl http://localhost:8080
  1. 이제 처음 작성한 컨테이너를 테스트합니다.
curl http://localhost:4000

(명령어 결과)

Hello World

작업 4. 디버그

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

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

(명령어 결과)

Server running at http://0.0.0.0:80/

실행 중인 컨테이너에서 대화형 Bash 세션을 시작해야 할 수 있습니다.

  1. 이 경우 docker exec를 사용합니다. 다른 터미널을 열고(Cloud Shell에서 + 아이콘을 클릭) 다음 명령어를 입력합니다.
docker exec -it [container_id] bash

-it 플래그는 pseudo-tty를 할당하고 stdin을 열린 상태로 유지하여 컨테이너와 상호작용할 수 있도록 합니다. Dockerfile에 지정된 WORKDIR 디렉터리(/app)에서 bash가 실행된 것을 확인할 수 있습니다. 이제 디버깅할 컨테이너 내에서 대화형 셸 세션을 사용할 수 있습니다.

(명령어 결과)

root@xxxxxxxxxxxx:/app#

  1. 디렉터리 확인
ls

(명령어 결과)

Dockerfile app.js

  1. Bash 세션을 종료합니다.
exit
  1. Docker inspect를 통해 Docker에서 컨테이너의 메타데이터를 검토할 수 있습니다.
docker inspect [container_id]

(명령어 결과)

[
{
"Id": "xxxxxxxxxxxx....",
"Created": "2017-08-07T22:57:49.261726726Z",
"Path": "node",
"Args": [
"app.js"
],
...

뭔가 엄청 많이 나온다.

  1. --format을 사용하여 반환된 JSON의 특정 필드를 검사합니다. 다음은 예시입니다.
docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' [container_id]

(출력 예시)

192.168.9.3

작업 5. 게시

이제 이미지를 Google Artifact Registry로 푸시합니다. 그런 다음 모든 컨테이너와 이미지를 삭제하여 새로운 환경을 시뮬레이션하고 컨테이너를 가져와서 실행합니다. 이를 통해 Docker 컨테이너의 이식성을 확인할 수 있습니다.

Artifact Registry에서 호스팅하는 비공개 레지스트리에 이미지를 푸시하려면 이미지에 레지스트리 이름으로 태그를 지정해야 합니다. 형식은 -docker.pkg.dev/my-project/my-repo/my-image입니다.

대상 Docker 저장소 만들기

이미지를 푸시하려면 먼저 저장소를 만들어야 합니다. 이미지를 푸시해도 저장소 만들기가 트리거되지 않으며 Cloud Build 서비스 계정에는 저장소를 만들 권한이 없습니다.

  1. 탐색 메뉴의 CI/CD에서 Artifact Registry > 저장소로 이동합니다.

  2. 저장소 만들기를 클릭합니다.

  3. 저장소 이름으로 my-repository를 지정합니다.

  4. 형식으로 Docker를 선택합니다.

  5. 위치 유형에서 리전을 선택한 후 us-central1 (Iowa) 위치를 선택합니다. (서울로 선택해봤으나 구글 정책때문인지 안된다)

  6. 만들기를 클릭합니다.

인증 구성하기

이미지를 푸시하거나 가져오려면 먼저 Docker가 Artifact Registry에 대한 요청을 인증하는 데 Google Cloud CLI를 사용하도록 구성해야 합니다.

  1. us-central1 리전의 Docker 저장소에 인증을 설정하려면 Cloud Shell에서 다음 명령어를 실행합니다.
gcloud auth configure-docker us-central1-docker.pkg.dev
  1. 메시지가 표시되면 Y를 입력합니다.

이 명령어는 Docker 구성을 업데이트합니다. 이제 Google Cloud 프로젝트의 Artifact Registry와 연결하여 이미지를 푸시하고 가져올 수 있습니다.

컨테이너를 Artifact Registry로 푸시하기

  1. 다음 명령어를 실행하여 프로젝트 ID를 설정하고 Dockerfile이 포함된 디렉터리로 변경합니다.
export PROJECT_ID=$(gcloud config get-value project)
cd ~/test
  1. 명령어를 실행하여 node-app:0.2에 태그를 지정합니다.
docker build -t us-central1-docker.pkg.dev/$PROJECT_ID/my-repository/node-app:0.2 .
  1. 다음 명령어를 실행하여 빌드된 Docker 이미지를 확인합니다.
docker images

(명령어 결과)

REPOSITORY TAG IMAGE ID CREATED
node-app 0.2 76b3beef845e 22 hours
us-central1-....node-app:0.2 0.2 76b3beef845e 22 hours
node-app 0.1 f166cd2a9f10 26 hours
node lts 5a767079e3df 7 days
hello-world latest 1815c82652c0 7 weeks

  1. 이 이미지를 Artifact Registry로 푸시합니다.
docker push us-central1-docker.pkg.dev/$PROJECT_ID/my-repository/node-app:0.2

명령어 결과(사용자마다 다를 수 있음):

The push refers to a repository [us-central1-docker.pkg.dev/[project-id]/my-repository/node-app:0.2]
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

  1. 빌드가 완료되면 탐색 메뉴의 CI/CD에서 Artifact Registry > 저장소로 이동합니다.
  2. my-repository를 클릭합니다. node-app Docker 컨테이너가 생성된 것을 볼 수 있습니다.

이미지 테스트하기

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

  1. 모든 컨테이너를 중지하고 삭제합니다.
docker stop $(docker ps -q)
docker rm $(docker ps -aq)

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

  1. 다음 명령어를 실행하여 모든 Docker 이미지를 삭제합니다.
docker rmi us-central1-docker.pkg.dev/$PROJECT_ID/my-repository/node-app:0.2
docker rmi node:lts
docker rmi -f $(docker images -aq) # remove remaining images
docker images

(명령어 결과)

REPOSITORY TAG IMAGE ID CREATED SIZE

이제 새로운 환경이나 다름없습니다.

  1. 이미지를 가져와서 실행합니다.
docker pull us-central1-docker.pkg.dev/$PROJECT_ID/my-repository/node-app:0.2
docker run -p 4000:80 -d us-central1-docker.pkg.dev/$PROJECT_ID/my-repository/node-app:0.2
curl http://localhost:4000

(명령어 결과)

Welcome to Cloud

나는 이렇게 마지막 줄에 curl: (52) Empty reply from server 라고 떴다.
근데 무사 통과

profile
개발자 지망생. 일단 하고보자

0개의 댓글