1. 개요
Microsoft .NET Core는 기본적으로 컨테이너에서 실행할 수 있는 .NET의 오픈소스 및 크로스 플랫폼 버전입니다. .NET Core는 GitHub에서 사용할 수 있으며 Microsoft 및 .NET 커뮤니티에서 유지관리합니다. 이 실습에서는 컨테이너화된 .NET Core 앱을 Google Kubernetes Engine (GKE)에 배포합니다.
이 실습에서는 애플리케이션을 개발자의 로컬 환경에서 개발한 다음 프로덕션에 배포하는 일반적인 개발 패턴을 따릅니다. 실습 1부에서는 Cloud Shell에서 실행되는 컨테이너를 사용하여 .NET Core 앱 예를 검증합니다. 검증이 완료되면 앱이 GKE를 사용하여 Kubernetes에 배포됩니다. 이 실습에는 GKE 클러스터를 만드는 단계가 포함되어 있습니다.
실습 2부에서는 앱 인스턴스를 실행 중인 컨테이너의 호스트 이름을 표시하는 앱을 약간 변경합니다. 그러면 업데이트된 애플리케이션이 Cloud Shell에서 검증되며, 배포가 새 버전을 사용하도록 업데이트됩니다. 다음 그림은 이 실습의 활동 순서를 보여줍니다.
비용
이 실습을 작성한 그대로 실행하면 다음 서비스에 대한 일반 요금이 적용됩니다.
2. 설정 및 요구사항
기본 요건
이 실습을 완료하려면 Google Cloud 계정과 프로젝트가 필요합니다. 새 프로젝트를 만드는 방법에 관한 자세한 내용은 이 Codelab을 참고하세요.
이 실습에서는 Cloud Shell에서 실행되는 Docker를 사용합니다. Cloud Shell은 Google Cloud 콘솔을 통해 제공되며 gcloud 및 Docker와 같은 여러 유용한 도구로 사전 구성되어 있습니다. Cloud Shell에 액세스하는 방법은 아래에 나와 있습니다. 오른쪽 상단에 있는 Cloud Shell 아이콘을 클릭하면 콘솔 창의 하단 창에 표시됩니다.
GKE 클러스터의 대체 구성 옵션 (선택사항)
이 실습에는 Kubernetes 클러스터가 필요합니다. 다음 섹션에서는 간단한 구성을 갖춘 GKE 클러스터를 만듭니다. 이 섹션에서는 GKE를 사용하여 Kubernetes 클러스터를 빌드할 때 사용할 대체 구성 옵션을 제공하는 몇 가지 gcloud
명령어를 보여줍니다. 예를 들어 아래 명령어를 사용하면 다양한 머신 유형, 영역은 물론 GPU (가속기)까지 식별할 수 있습니다.
gcloud compute machine-types list
명령어를 사용하여 머신 유형 나열- 이 명령어(
gcloud compute accelerator-types list
)를 사용하여 GPU 나열 - 다음 명령어를 사용하여 컴퓨팅 영역 나열:
gcloud compute zones list
- 모든 gcloud 명령어에 대한 도움말 보기
gcloud container clusters --help
- 예를 들어
gcloud container clusters create --help
Kubernetes 클러스터 생성에 대한 세부정보를 제공합니다.
- 예를 들어
GKE 구성 옵션의 전체 목록은 이 문서를 참조하세요.
Kubernetes 클러스터 생성 준비
Cloud Shell에서는 일부 환경 변수를 설정하고 gcloud 클라이언트를 구성해야 합니다. 이 작업은 다음 명령어를 사용하여 실행됩니다.
export PROJECT_ID=YOUR_PROJECT_ID
export DEFAULT_ZONE=us-central1-c
gcloud config set project ${PROJECT_ID}
gcloud config set compute/zone ${DEFAULT_ZONE}
GKE 클러스터 만들기
이 실습에서는 .NET Core 앱을 Kubernetes에 배포하므로 클러스터를 만들어야 합니다. 다음 명령어를 사용하여 GKE를 사용하는 Google Cloud에서 새 Kubernetes 클러스터를 만듭니다.
gcloud container clusters create dotnet-cluster \
--zone ${DEFAULT_ZONE} \
--num-nodes=1 \
--node-locations=${DEFAULT_ZONE},us-central1-b \
--enable-stackdriver-kubernetes \
--machine-type=n1-standard-1 \
--workload-pool=${PROJECT_ID}.svc.id.goog \
--enable-ip-alias
--num-nodes
는 영역별에 추가할 노드 수이며 나중에 확장할 수 있습니다.--node-locations
는 쉼표로 구분된 영역 목록입니다. 이 경우 위의 환경 변수에서 식별한 영역과us-central1-b
이 사용됩니다.- 참고: 이 목록에는 중복되는 주소를 포함할 수 없습니다.
--workload-pool
가 워크로드 아이덴티티를 설정하므로 GKE 워크로드가 Google Cloud 서비스에 액세스할 수 있습니다.
클러스터를 빌드하는 동안 다음이 표시됩니다.
Creating cluster dotnet-cluster in us-central1-b... Cluster is being deployed...⠼
kubectl 구성
kubectl
CLI는 Kubernetes 클러스터와 상호작용하는 기본적인 방법입니다. 방금 만든 새 클러스터에서 사용하려면 클러스터를 인증하도록 구성해야 합니다. 이 작업은
명령에 따라 진행됩니다
$ gcloud container clusters get-credentials dotnet-cluster --zone ${DEFAULT_ZONE}
Fetching cluster endpoint and auth data.
kubeconfig entry generated for dotnet-cluster.
이제 kubectl
를 사용하여 클러스터와 상호작용할 수 있습니다.
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
gke-dotnet-cluster-default-pool-02c9dcb9-fgxj Ready <none> 2m15s v1.16.13-gke.401
gke-dotnet-cluster-default-pool-ed09d7b7-xdx9 Ready <none> 2m24s v1.16.13-gke.401
3. 로컬에서 테스트 및 원하는 기능 확인
이 실습에서는 Docker Hub의 공식 .NET 저장소에 있는 다음과 같은 컨테이너 이미지를 사용합니다.
컨테이너를 로컬에서 실행하여 기능 확인
Cloud Shell에서 다음 Docker 명령어를 실행하여 Docker가 제대로 작동되어 실행 중인지, .NET 컨테이너가 예상대로 작동하는지 확인합니다.
$ docker run --rm mcr.microsoft.com/dotnet/samples
Hello from .NET!
__________________
\
\
....
....'
....
..........
.............'..'..
................'..'.....
.......'..........'..'..'....
........'..........'..'..'.....
.'....'..'..........'..'.......'.
.'..................'... ......
. ......'......... .....
. ......
.. . .. ......
.... . .......
...... ....... ............
................ ......................
........................'................
......................'..'...... .......
.........................'..'..... .......
........ ..'.............'..'.... ..........
..'..'... ...............'....... ..........
...'...... ...... .......... ...... .......
........... ....... ........ ......
....... '...'.'. '.'.'.' ....
....... .....'.. ..'.....
.. .......... ..'........
............ ..............
............. '..............
...........'.. .'.'............
............... .'.'.............
.............'.. ..'..'...........
............... .'..............
......... ..............
.....
Environment:
.NET 5.0.1-servicing.20575.16
Linux 5.4.58-07649-ge120df5deade #1 SMP PREEMPT Wed Aug 26 04:56:33 PDT 2020
웹 앱 기능 확인
샘플 웹 애플리케이션은 Cloud Shell에서도 검증할 수 있습니다. 아래의 Docker 실행 명령어는 포트 80
을 노출하고 이를 localhost
포트 8080
에 매핑하는 새 컨테이너를 만듭니다. 여기서 localhost
는 Cloud Shell에 있습니다.
$ docker run -it --rm -p 8080:80 --name aspnetcore_sample mcr.microsoft.com/dotnet/samples:aspnetapp
warn: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[60]
Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
No XML encryptor configured. Key {64a3ed06-35f7-4d95-9554-8efd38f8b5d3} may be persisted to storage in unencrypted form.
info: Microsoft.Hosting.Lifetime[0]
Now listening on: http://[::]:80
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: /app
웹 앱이므로 웹브라우저에서 확인하고 검증해야 합니다. 다음 섹션에서는 웹 미리보기를 사용하여 Cloud Shell에서 이 작업을 수행하는 방법을 보여줍니다.
4. '웹 미리보기'를 사용하여 Cloud Shell에서 서비스에 액세스
Cloud Shell은 브라우저를 사용하여 Cloud Shell 인스턴스에서 실행되는 프로세스와 상호작용할 수 있는 기능인 웹 미리보기를 제공합니다.
'웹 미리보기' 사용 Cloud Shell에서 앱 보기
Cloud Shell에서 웹 미리보기 버튼을 클릭하고 '포트 8080에서 미리보기'를 선택합니다. (또는 웹 미리보기를 사용하도록 설정된 포트)
그러면 다음과 같은 주소의 브라우저 창이 열립니다.
https://8080-cs-754738286554-default.us-central1.cloudshell.dev/?authuser=0
웹 미리보기를 사용하여 .NET 샘플 애플리케이션 보기
이제 마지막 단계에서 시작한 샘플 앱을 웹 미리보기를 시작하고 제공된 URL을 로드하여 볼 수 있습니다. 예를 들면 다음과 같습니다.
5. Kubernetes에 배포
YAML 파일 빌드 및 적용
다음 단계에서는 두 가지 Kubernetes 리소스, 즉 배포와 서비스를 설명하는 YAML 파일이 필요합니다. Cloud Shell에서 dotnet-app.yaml
라는 파일을 만들고 다음 콘텐츠를 추가합니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: dotnet-deployment
labels:
app: dotnetapp
spec:
replicas: 3
selector:
matchLabels:
app: dotnetapp
template:
metadata:
labels:
app: dotnetapp
spec:
containers:
- name: dotnet
image: mcr.microsoft.com/dotnet/samples:aspnetapp
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: dotnet-service
spec:
selector:
app: dotnetapp
ports:
- protocol: TCP
port: 8080
targetPort: 80
이제 kubectl
를 사용하여 이 파일을 Kubernetes에 적용합니다.
$ kubectl apply -f dotnet-app.yaml
deployment.apps/dotnet-deployment created
service/dotnet-service created
원하는 리소스가 생성되었음을 나타내는 메시지를 확인합니다.
결과 리소스 살펴보기
kubectl
CLI를 사용하여 위에서 만든 리소스를 검토할 수 있습니다. 먼저 배포 리소스를 살펴보고 새 배포가 있는지 확인해 보겠습니다.
$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
dotnet-deployment 3/3 3 3 80s
다음으로 ReplicaSet를 살펴보겠습니다. 위 배포에서 생성된 ReplicaSet가 있어야 합니다.
$ kubectl get replicaset
NAME DESIRED CURRENT READY AGE
dotnet-deployment-5c9d4cc4b9 3 3 3 111s
마지막으로 포드를 살펴보세요. 배포는 세 개의 인스턴스가 있어야 한다고 나타냈습니다. 아래 명령어는 세 개의 인스턴스가 있음을 보여줍니다. 인스턴스가 실행 중인 노드가 표시되도록 -o wide
옵션이 추가됩니다.
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
dotnet-deployment-5c9d4cc4b9-cspqd 1/1 Running 0 2m25s 10.16.0.8 gke-dotnet-cluster-default-pool-ed09d7b7-xdx9 <none> <none>
dotnet-deployment-5c9d4cc4b9-httw6 1/1 Running 0 2m25s 10.16.1.7 gke-dotnet-cluster-default-pool-02c9dcb9-fgxj <none> <none>
dotnet-deployment-5c9d4cc4b9-vvdln 1/1 Running 0 2m25s 10.16.0.7 gke-dotnet-cluster-default-pool-ed09d7b7-xdx9 <none> <none>
서비스 리소스 검토
Kubernetes의 서비스 리소스는 부하 분산기입니다. 엔드포인트는 포드의 라벨에 따라 결정됩니다. 이렇게 하면 위의 kubectl scale deployment
작업을 통해 새 포드가 배포에 추가되는 즉시 결과 포드를 해당 서비스에서 처리하는 요청에 즉시 사용할 수 있습니다.
다음 명령어는 서비스 리소스를 표시합니다.
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
dotnet-service ClusterIP 10.20.9.124 <none> 8080/TCP 2m50s
...
다음 명령어로 서비스에 대한 세부정보를 확인할 수 있습니다.
$ kubectl describe svc dotnet-service
Name: dotnet-service
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=dotnetapp
Type: ClusterIP
IP: 10.20.9.124
Port: <unset> 8080/TCP
TargetPort: 80/TCP
Endpoints: 10.16.0.7:80,10.16.0.8:80,10.16.1.7:80
Session Affinity: None
Events: <none>
서비스는 ClusterIP
유형입니다. 즉, 클러스터 내의 모든 포드가 서비스 이름 dotnet-service
을 IP 주소로 확인할 수 있습니다. 서비스로 전송되는 요청은 모든 인스턴스 (포드)에 걸쳐 부하 분산됩니다. 위의 Endpoints
값은 현재 이 서비스에 사용할 수 있는 포드의 IP를 보여줍니다. 이를 위에서 출력된 포드의 IP와 비교하세요.
실행 중인 앱 확인
이 시점에서 애플리케이션은 라이브 상태이며 사용자 요청을 받을 준비가 된 상태입니다. 액세스하려면 프록시를 사용하세요. 다음 명령어는 포트 8080
에서 요청을 수락하고 Kubernetes 클러스터에 전달하는 로컬 프록시를 만듭니다.
$ kubectl proxy --port 8080
Starting to serve on 127.0.0.1:8080
이제 Cloud Shell의 웹 미리보기를 사용하여 웹 애플리케이션에 액세스합니다.
웹 미리보기에서 생성된 URL에 다음을 추가합니다. /api/v1/namespaces/default/services/dotnet-service:8080/proxy/
그러면 다음과 같은 형태가 됩니다.
https://8080-cs-473655782854-default.us-central1.cloudshell.dev/api/v1/namespaces/default/services/dotnet-service:8080/proxy/
수고하셨습니다. Google Kubernetes Engine에 .NET Core 앱을 배포했습니다. 그런 다음 앱을 변경하고 다시 배포하겠습니다.
6. 앱 수정
이 섹션에서는 인스턴스가 실행 중인 호스트를 표시하도록 애플리케이션을 수정합니다. 이를 통해 부하 분산이 작동하고 있고 사용 가능한 포드가 예상대로 응답하는지 확인할 수 있습니다.
소스 코드 가져오기
git clone https://github.com/dotnet/dotnet-docker.git
cd dotnet-docker/samples/aspnetapp/
호스트 이름을 포함하도록 앱 업데이트
vi aspnetapp/Pages/Index.cshtml
<tr>
<td>Host</td>
<td>@Environment.MachineName</td>
</tr>
새 컨테이너 이미지 빌드 및 로컬에서 테스트
업데이트된 코드로 새 컨테이너 이미지를 빌드합니다.
docker build --pull -t aspnetapp:alpine -f Dockerfile.alpine-x64 .
이전과 마찬가지로 새 애플리케이션을 로컬에서 테스트합니다.
$ docker run --rm -it -p 8080:80 aspnetapp:alpine
warn: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[60]
Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
No XML encryptor configured. Key {f71feb13-8eae-4552-b4f2-654435fff7f8} may be persisted to storage in unencrypted form.
info: Microsoft.Hosting.Lifetime[0]
Now listening on: http://[::]:80
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: /app
이전과 마찬가지로 웹 미리보기를 사용하여 앱에 액세스할 수 있습니다. 이번에는 아래와 같이 Host 매개변수가 표시됩니다.
Cloud Shell에서 새 탭을 열고 docker ps
을 실행하여 컨테이너 ID가 위에 표시된 호스트 값과 일치하는지 확인합니다.
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ab85ce11aecd aspnetapp:alpine "./aspnetapp" 2 minutes ago Up 2 minutes 0.0.0.0:8080->80/tcp relaxed_northcutt
Kubernetes에서 사용할 수 있도록 이미지에 태그 지정 및 푸시
Kubernetes가 이미지를 가져올 수 있으려면 이미지에 태그를 지정하고 푸시해야 합니다. 먼저 컨테이너 이미지를 나열하고 원하는 이미지를 식별합니다.
$ docker image list
REPOSITORY TAG IMAGE ID CREATED SIZE
aspnetapp alpine 95b4267bb6d0 6 days ago 110MB
그런 다음 해당 이미지에 태그를 지정하고 Google Container Registry로 푸시합니다. 위의 이미지 ID를 사용하면 다음과 같이 표시됩니다.
docker tag 95b4267bb6d0 gcr.io/${PROJECT_ID}/aspnetapp:alpine
docker push gcr.io/${PROJECT_ID}/aspnetapp:alpine
7. 업데이트된 애플리케이션 재배포
YAML 파일 수정
dotnet-app.yaml
파일이 저장된 디렉터리로 다시 변경합니다. YAML 파일에서 다음 줄을 찾습니다.
image: mcr.microsoft.com/dotnet/core/samples:aspnetapp
위의 gcr.io에 생성되어 푸시된 컨테이너 이미지를 참조하도록 변경해야 합니다.
image: gcr.io/PROJECT_ID/aspnetapp:alpine
PROJECT_ID
를 사용하려면 수정해야 합니다. 완료되면 다음과 같이 표시됩니다.
image: gcr.io/myproject/aspnetapp:alpine
업데이트된 YAML 파일 적용
$ kubectl apply -f dotnet-app.yaml
deployment.apps/dotnet-deployment configured
service/dotnet-service unchanged
배포 리소스는 업데이트된 것으로 표시되고 서비스 리소스는 변경되지 않은 것으로 표시됩니다. 업데이트된 포드는 이전과 같이 kubectl get pod
명령어를 사용하여 확인할 수 있지만, 이번에는 모든 변경사항을 감시하는 -w
를 추가합니다.
$ kubectl get pod -w
NAME READY STATUS RESTARTS AGE
dotnet-deployment-5c9d4cc4b9-cspqd 1/1 Running 0 34m
dotnet-deployment-5c9d4cc4b9-httw6 1/1 Running 0 34m
dotnet-deployment-5c9d4cc4b9-vvdln 1/1 Running 0 34m
dotnet-deployment-85f6446977-tmbdq 0/1 ContainerCreating 0 4s
dotnet-deployment-85f6446977-tmbdq 1/1 Running 0 5s
dotnet-deployment-5c9d4cc4b9-vvdln 1/1 Terminating 0 34m
dotnet-deployment-85f6446977-lcc58 0/1 Pending 0 0s
dotnet-deployment-85f6446977-lcc58 0/1 Pending 0 0s
dotnet-deployment-85f6446977-lcc58 0/1 ContainerCreating 0 0s
dotnet-deployment-5c9d4cc4b9-vvdln 0/1 Terminating 0 34m
dotnet-deployment-85f6446977-lcc58 1/1 Running 0 6s
dotnet-deployment-5c9d4cc4b9-cspqd 1/1 Terminating 0 34m
dotnet-deployment-85f6446977-hw24v 0/1 Pending 0 0s
dotnet-deployment-85f6446977-hw24v 0/1 Pending 0 0s
dotnet-deployment-5c9d4cc4b9-cspqd 0/1 Terminating 0 34m
dotnet-deployment-5c9d4cc4b9-vvdln 0/1 Terminating 0 34m
dotnet-deployment-5c9d4cc4b9-vvdln 0/1 Terminating 0 34m
dotnet-deployment-85f6446977-hw24v 0/1 Pending 0 2s
dotnet-deployment-85f6446977-hw24v 0/1 ContainerCreating 0 2s
dotnet-deployment-5c9d4cc4b9-cspqd 0/1 Terminating 0 34m
dotnet-deployment-5c9d4cc4b9-cspqd 0/1 Terminating 0 34m
dotnet-deployment-85f6446977-hw24v 1/1 Running 0 3s
dotnet-deployment-5c9d4cc4b9-httw6 1/1 Terminating 0 34m
dotnet-deployment-5c9d4cc4b9-httw6 0/1 Terminating 0 34m
위 출력은 진행 중인 순차적 업데이트를 보여줍니다. 먼저 새 컨테이너가 시작되고 실행될 때 이전 컨테이너가 종료됩니다.
실행 중인 앱 확인
이 시점에서 애플리케이션이 업데이트되고 사용자 요청을 받을 준비가 되었습니다. 이전과 마찬가지로 프록시를 사용하여 액세스할 수 있습니다.
$ kubectl proxy --port 8080
Starting to serve on 127.0.0.1:8080
이제 Cloud Shell의 웹 미리보기를 사용하여 웹 애플리케이션에 액세스합니다.
웹 미리보기에서 생성된 URL에 다음을 추가합니다. /api/v1/namespaces/default/services/dotnet-service:8080/proxy/
그러면 다음과 같은 형태가 됩니다.
https://8080-cs-473655782854-default.us-central1.cloudshell.dev/api/v1/namespaces/default/services/dotnet-service:8080/proxy/
Kubernetes 서비스가 부하를 분산하고 있는지 확인
이 URL을 여러 번 새로고침하면 서비스에 의해 요청이 여러 포드에 부하 분산되면서 호스트가 변경되는 것을 확인할 수 있습니다. 호스트 값을 위의 포드 목록과 비교하여 모든 포드가 트래픽을 수신하고 있는지 확인합니다.
인스턴스 수직 확장
Kubernetes에서 앱을 확장하는 것은 쉽습니다. 다음 명령어는 애플리케이션의 인스턴스 6개까지 배포를 확장합니다.
$ kubectl scale deployment dotnet-deployment --replicas 6
deployment.apps/dotnet-deployment scaled
다음 명령어로 새 포드와 포드의 현재 상태를 볼 수 있습니다.
kubectl get pod -w
동일한 브라우저 창을 새로고침하면 이제 트래픽이 모든 새 포드에 분산되고 있음을 알 수 있습니다.
8. 축하합니다.
이 실습에서는 개발자 환경에서 .NET Core 샘플 웹 애플리케이션을 검증한 후 GKE를 사용하여 Kubernetes에 배포했습니다. 그런 다음 앱이 실행 중이던 컨테이너의 호스트 이름을 표시하도록 수정되었습니다. 그런 다음 Kubernetes 배포를 새 버전으로 업데이트하고 앱을 확장하여 추가 인스턴스에 부하가 분산되는 방식을 보여주었습니다.
.NET 및 Kubernetes에 대해 자세히 알아보려면 다음 튜토리얼을 살펴보세요. 보다 정교한 라우팅 및 복원력 패턴을 위해 Istio Service Mesh를 도입하여 이 실습에서 학습한 내용을 기반으로 합니다.
9. 삭제
의도하지 않은 비용을 방지하려면 다음 명령어를 사용하여 이 실습에서 만든 클러스터와 컨테이너 이미지를 삭제합니다.
gcloud container clusters delete dotnet-cluster --zone ${DEFAULT_ZONE}
gcloud container images delete gcr.io/${PROJECT_ID}/aspnetapp:alpine