이 실습에서는 다음 작업을 진행하는 방법을 학습합니다.
다음 명령어로 사용 중인 계정 이름 목록을 표시할 수 있습니다.
gcloud auth list
출력 예:
Credentialed accounts:
- google1623327_student@qwiklabs.net
다음 명령어로 프로젝트 ID 목록을 표시할 수 있습니다.
gcloud config list project
출력 예:
[core]
project = qwiklabs-gcp-44776a13dea667a6
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라는 공개 레지스트리에서 이미지를 가져오고, 가져온 이미지에서 컨테이너를 생성하고, 컨테이너를 실행했습니다.
docker images
(명령어 결과)
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-world latest feb5d9fea6a5 14 months ago 13.3kB
Docker Hub 공개 레지스트리에서 가져온 이미지입니다. 이미지 ID는 SHA256 해시 형식입니다. 이 필드에서는 프로비저닝된 Docker 이미지를 지정합니다. Docker 데몬이 로컬에서 이미지를 찾을 수 없으면 기본적으로 공개 레지스트리에서 이미지를 검색합니다.
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에서 이미지를 가져올 필요는 없습니다.
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
여기에는 Docker가 컨테이너를 식별하기 위해 생성한 UUID인 CONTAINER ID와 실행에 관한 추가 메타데이터가 표시됩니다. 또한 컨테이너 Names도 무작위로 생성되지만 docker run --name [container-name] hello-world를 사용하여 지정할 수 있습니다.
이 섹션에서는 간단한 노드 애플리케이션을 기반으로 한 Docker 이미지를 빌드합니다.
mkdir test && cd test
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의 각 줄을 숙지하세요.
이제 노드 애플리케이션을 작성한 다음 이미지를 빌드해 보겠습니다.
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 서버입니다.
이제 이미지를 빌드합니다.
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의 각 행을 통해 중간 컨테이너 레이어가 만들어지는 방식을 확인하세요.
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과 같은 노드 이미지의 다른 버전을 사용하면 더 작은 이미지를 제공하여 이식성을 높일 수 있습니다. 컨테이너 크기 줄이기에 관해서는 고급 주제에서 자세히 설명하겠습니다. 노드의 공식 저장소에서 모든 버전을 확인할 수 있습니다.
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에서 컨테이너에 접속할 수 없습니다.
(명령어 결과)
Hello World
초기 터미널이 실행되는 동안 컨테이너가 실행됩니다. 컨테이너를 터미널 세션에 종속시키지 않고 백그라운드에서 실행하려면 -d 플래그를 지정해야 합니다.
docker stop my-app && docker rm my-app
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
참고: 앞부분에 입력한 문자들로 컨테이너를 고유하게 식별할 수 있다면 전체 컨테이너 ID를 입력할 필요는 없습니다. 예를 들어 컨테이너 ID가 17bcaca6f....인 경우 docker logs 17b를 실행할 수 있습니다.
docker logs [container_id]
(명령어 결과)
Server running at http://0.0.0.0:80/
이제 애플리케이션을 수정합니다.
cd test
....
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Welcome to Cloud\n');
});
....
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단계까지만 나온다. 실행에는 지장 없다.)
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 ...
curl http://localhost:8080
curl http://localhost:4000
(명령어 결과)
Hello World
컨테이너 빌드와 실행을 숙지했으니 이제 몇 가지 디버깅 사례를 살펴보겠습니다.
docker logs -f [container_id]
(명령어 결과)
Server running at http://0.0.0.0:80/
실행 중인 컨테이너에서 대화형 Bash 세션을 시작해야 할 수 있습니다.
docker exec -it [container_id] bash
-it 플래그는 pseudo-tty를 할당하고 stdin을 열린 상태로 유지하여 컨테이너와 상호작용할 수 있도록 합니다. Dockerfile에 지정된 WORKDIR 디렉터리(/app)에서 bash가 실행된 것을 확인할 수 있습니다. 이제 디버깅할 컨테이너 내에서 대화형 셸 세션을 사용할 수 있습니다.
(명령어 결과)
root@xxxxxxxxxxxx:/app#
ls
(명령어 결과)
Dockerfile app.js
exit
docker inspect [container_id]
(명령어 결과)
[
{
"Id": "xxxxxxxxxxxx....",
"Created": "2017-08-07T22:57:49.261726726Z",
"Path": "node",
"Args": [
"app.js"
],
...
뭔가 엄청 많이 나온다.
docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' [container_id]
(출력 예시)
192.168.9.3
이제 이미지를 Google Artifact Registry로 푸시합니다. 그런 다음 모든 컨테이너와 이미지를 삭제하여 새로운 환경을 시뮬레이션하고 컨테이너를 가져와서 실행합니다. 이를 통해 Docker 컨테이너의 이식성을 확인할 수 있습니다.
Artifact Registry에서 호스팅하는 비공개 레지스트리에 이미지를 푸시하려면 이미지에 레지스트리 이름으로 태그를 지정해야 합니다. 형식은 -docker.pkg.dev/my-project/my-repo/my-image입니다.
이미지를 푸시하려면 먼저 저장소를 만들어야 합니다. 이미지를 푸시해도 저장소 만들기가 트리거되지 않으며 Cloud Build 서비스 계정에는 저장소를 만들 권한이 없습니다.
탐색 메뉴의 CI/CD에서 Artifact Registry > 저장소로 이동합니다.
저장소 만들기를 클릭합니다.
저장소 이름으로 my-repository를 지정합니다.
형식으로 Docker를 선택합니다.
위치 유형에서 리전을 선택한 후 us-central1 (Iowa) 위치를 선택합니다. (서울로 선택해봤으나 구글 정책때문인지 안된다)
만들기를 클릭합니다.
이미지를 푸시하거나 가져오려면 먼저 Docker가 Artifact Registry에 대한 요청을 인증하는 데 Google Cloud CLI를 사용하도록 구성해야 합니다.
gcloud auth configure-docker us-central1-docker.pkg.dev
이 명령어는 Docker 구성을 업데이트합니다. 이제 Google Cloud 프로젝트의 Artifact Registry와 연결하여 이미지를 푸시하고 가져올 수 있습니다.
export PROJECT_ID=$(gcloud config get-value project)
cd ~/test
docker build -t us-central1-docker.pkg.dev/$PROJECT_ID/my-repository/node-app:0.2 .
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
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
새로운 VM을 시작하고 SSH로 새 VM에 접속한 다음 gcloud를 설치할 수도 있지만 여기서는 간단하게 모든 컨테이너와 이미지를 삭제하여 새로운 환경을 시뮬레이션하겠습니다.
docker stop $(docker ps -q)
docker rm $(docker ps -aq)
노드 이미지를 삭제하기 전에 (node:lts의) 하위 이미지를 삭제해야 합니다.
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
이제 새로운 환경이나 다름없습니다.
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 라고 떴다.
근데 무사 통과