1. 소개
최종 업데이트: 2022년 7월 14일
애플리케이션의 관측 가능성
관측 가능성 및 지속적 프로파일러
관측 가능성은 시스템의 속성을 설명하는 데 사용되는 용어입니다. 관측 가능성이 있는 시스템을 통해 팀이 시스템을 적극적으로 디버그할 수 있습니다. 이러한 맥락에서 관측 가능성의 세 가지 핵심 요소 로그, 측정항목, trace는 시스템에서 관측 가능성을 얻기 위한 기본 계측입니다.
또한 연속 프로파일링은 관측 가능성의 세 가지 핵심 요소 외에도 관측 가능성의 또 다른 핵심 구성요소이며 업계의 사용자 기반을 확대하고 있습니다. Cloud Profiler는 창시자 중 하나로, 애플리케이션 호출 스택의 성능 측정항목을 상세히 살펴볼 수 있는 간편한 인터페이스를 제공합니다.
이 Codelab은 시리즈의 2부로, 연속 프로파일러 에이전트 계측을 다룹니다. 1부에서는 OpenTelemetry와 Cloud Trace를 사용한 분산 추적을 다루고, 1부에서는 마이크로서비스의 병목 현상을 더 잘 식별하는 방법을 자세히 알아봅니다.
빌드할 항목
이 Codelab에서는 'Shakespeare 애플리케이션'의 서버 서비스에서 연속 프로파일러 에이전트를 계측합니다. Google Kubernetes Engine 클러스터에서 실행되는 서비스(일명 Shakesapp)입니다 Shakesapp의 아키텍처는 다음과 같습니다.
- Loadgen은 HTTP로 클라이언트에게 쿼리 문자열을 전송합니다.
- 클라이언트는 loadgen에서 gRPC의 서버로 쿼리를 전달합니다.
- 서버가 클라이언트에서 쿼리를 수락하고, Google Cloud Storage에서 텍스트 형식의 모든 Shakespare를 가져오고, 쿼리가 포함된 줄을 검색하고, 클라이언트와 일치하는 줄의 번호를 반환합니다.
파트 1에서는 서버 서비스 어딘가에 병목 현상이 있음을 발견했지만 정확한 원인을 식별할 수 없었습니다.
학습할 내용
- 프로파일러 에이전트를 삽입하는 방법
- Cloud Profiler에서 병목 현상을 조사하는 방법
이 Codelab에서는 애플리케이션에서 연속 프로파일러 에이전트를 계측하는 방법을 설명합니다.
필요한 항목
- Go에 관한 기본 지식
- Kubernetes에 관한 기본 지식
2. 설정 및 요구사항
자습형 환경 설정
아직 Google 계정(Gmail 또는 Google Apps)이 없으면 계정을 만들어야 합니다. Google Cloud Platform Console(console.cloud.google.com)에 로그인하고 새 프로젝트를 만듭니다.
프로젝트가 이미 있으면 Console 왼쪽 위에서 프로젝트 선택 풀다운 메뉴를 클릭합니다.
그리고 표시된 대화상자에서 '새 프로젝트' 버튼을 클릭하여 새 프로젝트를 만듭니다.
아직 프로젝트가 없으면 첫 번째 프로젝트를 만들기 위해 다음과 비슷한 대화상자가 표시됩니다.
이후의 프로젝트 만들기 대화상자에서 새 프로젝트의 세부정보를 입력할 수 있습니다.
모든 Google Cloud 프로젝트에서 고유한 이름인 프로젝트 ID를 기억하세요(위의 이름은 이미 사용되었으므로 사용할 수 없습니다). 이 이름은 나중에 Codelab에서 PROJECT_ID로 참조됩니다.
아직 사용 설정하지 않은 경우 Google Cloud 리소스를 사용하고 Cloud Trace API를 사용 설정하려면 Developers Console에서 결제를 사용 설정해야 합니다.
이 codelab을 실행하는 과정에는 많은 비용이 들지 않지만 더 많은 리소스를 사용하려고 하거나 실행 중일 경우 비용이 더 들 수 있습니다(이 문서 마지막의 '삭제' 섹션 참조). Google Cloud Trace, Google Kubernetes Engine, Google Artifact Registry의 가격은 공식 문서에 나와 있습니다.
- Google Cloud 운영 제품군 가격 책정 | 운영 제품군
- 가격 책정 | Kubernetes Engine 문서
- Artifact Registry 가격 책정 | Artifact Registry 문서
Google Cloud Platform 신규 사용자는 $300 상당의 무료 체험판을 사용할 수 있으므로, 이 Codelab을 완전히 무료로 사용할 수 있습니다.
Google Cloud Shell 설정
Google Cloud 및 Google Cloud Trace는 노트북에서 원격으로 조작할 수 있지만, 이 Codelab에서는 Cloud에서 실행되는 명령줄 환경인 Google Cloud Shell을 사용합니다.
이 Debian 기반 가상 머신에는 필요한 모든 개발 도구가 로드되어 있습니다. 영구적인 5GB 홈 디렉터리를 제공하고 Google Cloud에서 실행되므로 네트워크 성능과 인증이 크게 개선됩니다. 즉, 이 Codelab에 필요한 것은 브라우저뿐입니다(Chromebook에서도 작동 가능).
Cloud 콘솔에서 Cloud Shell을 활성화하려면 Cloud Shell 활성화 를 클릭하세요. 환경을 프로비저닝하고 연결하는 데 몇 분 정도 걸립니다.
Cloud Shell에 연결되면 사용자 인증이 이미 완료되었고 프로젝트가 내 PROJECT_ID
에 설정되어 있음을 확인할 수 있습니다.
gcloud auth list
명령어 결과
Credentialed accounts: - <myaccount>@<mydomain>.com (active)
gcloud config list project
명령어 결과
[core] project = <PROJECT_ID>
어떤 이유로든 프로젝트가 설정되지 않았으면 다음 명령어를 실행하면 됩니다.
gcloud config set project <PROJECT_ID>
PROJECT_ID
를 찾고 계신가요? 설정 단계에서 사용한 ID를 확인하거나 Cloud Console 대시보드에서 확인하세요.
또한 Cloud Shell은 기본적으로 이후 명령어를 실행할 때 유용할 수 있는 몇 가지 환경 변수를 설정합니다.
echo $GOOGLE_CLOUD_PROJECT
명령어 결과
<PROJECT_ID>
마지막으로 기본 영역 및 프로젝트 구성을 설정합니다.
gcloud config set compute/zone us-central1-f
다양한 영역을 선택할 수 있습니다. 자세한 내용은 리전 및 영역을 참조하세요.
Go 언어 설정
이 Codelab에서는 모든 소스 코드에 Go를 사용합니다. Cloud Shell에서 다음 명령어를 실행하고 Go 버전이 1.17 이상인지 확인합니다.
go version
명령어 결과
go version go1.18.3 linux/amd64
Google Kubernetes 클러스터 설정
이 Codelab에서는 Google Kubernetes Engine (GKE)에서 마이크로서비스 클러스터를 실행합니다. 이 Codelab의 프로세스는 다음과 같습니다.
- Cloud Shell에 기준 프로젝트 다운로드
- 마이크로서비스를 컨테이너에 빌드
- Google Artifact Registry (GAR)에 컨테이너 업로드
- GKE에 컨테이너 배포
- trace 계측을 위한 서비스의 소스 코드 수정
- 2단계로 이동
Kubernetes Engine 사용 설정
먼저 GKE에서 Shakesapp이 실행되는 Kubernetes 클러스터를 설정했으므로 GKE를 사용 설정해야 합니다. 'Kubernetes Engine' 메뉴로 이동합니다. 활성화 버튼을 누릅니다.
이제 Kubernetes 클러스터를 만들 준비가 되었습니다.
Kubernetes 클러스터 만들기
Cloud Shell에서 다음 명령어를 실행하여 Kubernetes 클러스터를 만듭니다. Artifact Registry 저장소를 만드는 데 사용할 리전에 영역 값이 있는지 확인하세요. 저장소 리전에 영역이 포함되지 않으면 영역 값을 us-central1-f
로 변경합니다.
gcloud container clusters create otel-trace-codelab2 \ --zone us-central1-f \ --release-channel rapid \ --preemptible \ --enable-autoscaling \ --max-nodes 8 \ --no-enable-ip-alias \ --scopes cloud-platform
명령어 결과
Note: Your Pod address range (`--cluster-ipv4-cidr`) can accommodate at most 1008 node(s). Creating cluster otel-trace-codelab2 in us-central1-f... Cluster is being health-checked (master is healthy)...done. Created [https://container.googleapis.com/v1/projects/development-215403/zones/us-central1-f/clusters/otel-trace-codelab2]. To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-f/otel-trace-codelab2?project=development-215403 kubeconfig entry generated for otel-trace-codelab2. NAME: otel-trace-codelab2 LOCATION: us-central1-f MASTER_VERSION: 1.23.6-gke.1501 MASTER_IP: 104.154.76.89 MACHINE_TYPE: e2-medium NODE_VERSION: 1.23.6-gke.1501 NUM_NODES: 3 STATUS: RUNNING
Artifact Registry 및 Skaffold 설정
이제 배포할 Kubernetes 클러스터가 준비되었습니다. 다음으로 컨테이너를 푸시하고 배포하기 위한 Container Registry를 준비합니다. 이 단계에서는 Artifact Registry (GAR)와 Skaffold를 설정하여 사용해야 합니다.
Artifact Registry 설정
'Artifact Registry' 메뉴로 이동합니다. 활성화 버튼을 누릅니다.
잠시 후 GAR의 저장소 브라우저가 표시됩니다. '저장소 만들기'를 클릭합니다. 버튼을 클릭하고 저장소 이름을 입력합니다.
이 Codelab에서는 새 저장소의 이름을 trace-codelab
로 지정합니다. 아티팩트 형식은 'Docker'입니다. 위치 유형은 '지역'입니다 Google Compute Engine 기본 영역에 설정한 리전과 가까운 리전을 선택합니다. 예를 들어 이 예시에서는 'us-central1-f'를 선택했습니다. 여기서는 'us-central1 (아이오와)'을 선택합니다. 그런 다음 '만들기'를 클릭합니다. 버튼을 클릭합니다.
이제 'trace-codelab'이 표시됩니다. 확인할 수 있습니다
나중에 여기로 다시 돌아와 레지스트리 경로를 확인합니다.
Skaffold 설정
Skaffold는 Kubernetes에서 실행되는 마이크로서비스를 빌드할 때 유용한 도구입니다. 이 도구는 작은 명령어 집합으로 애플리케이션의 컨테이너를 빌드, 푸시, 배포하는 워크플로를 처리합니다. Skaffold는 기본적으로 Docker Registry를 Container Registry로 사용하므로 컨테이너를 푸시할 때 GAR을 인식하도록 Skaffold를 구성해야 합니다.
Cloud Shell을 다시 열고 Skaffold가 설치되어 있는지 확인합니다. Cloud Shell은 기본적으로 Skaffold를 환경에 설치합니다. 다음 명령어를 실행하고 Skaffold 버전을 확인합니다.
skaffold version
명령어 결과
v1.38.0
이제 skaffold가 사용할 기본 저장소를 등록할 수 있습니다. 레지스트리 경로를 가져오려면 Artifact Registry 대시보드로 이동하여 이전 단계에서 방금 설정한 저장소의 이름을 클릭합니다.
그러면 페이지 상단에 탐색경로 트레일이 표시됩니다. 아이콘을 클릭하여 레지스트리 경로를 클립보드에 복사합니다.
복사 버튼을 클릭하면 브라우저 하단에 다음과 같은 메시지가 있는 대화상자가 표시됩니다.
"us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab" 이(가) 복사되었습니다.
Cloud Shell로 돌아갑니다. 대시보드에서 방금 복사한 값을 사용하여 skaffold config set default-repo
명령어를 실행합니다.
skaffold config set default-repo us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab
명령어 결과
set value default-repo to us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab for context gke_stackdriver-sandbox-3438851889_us-central1-b_stackdriver-sandbox
또한 레지스트리를 Docker 구성으로 구성해야 합니다. 다음 명령어를 실행합니다.
gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
명령어 결과
{ "credHelpers": { "gcr.io": "gcloud", "us.gcr.io": "gcloud", "eu.gcr.io": "gcloud", "asia.gcr.io": "gcloud", "staging-k8s.gcr.io": "gcloud", "marketplace.gcr.io": "gcloud", "us-central1-docker.pkg.dev": "gcloud" } } Adding credentials for: us-central1-docker.pkg.dev
이제 GKE에서 Kubernetes 컨테이너를 설정하는 다음 단계로 진행할 차례입니다.
요약
이 단계에서는 Codelab 환경을 설정합니다.
- Cloud Shell 설정
- Container Registry용 Artifact Registry 저장소를 만들었습니다.
- Container Registry를 사용하도록 Skaffold 설정
- Codelab 마이크로서비스가 실행되는 Kubernetes 클러스터를 만들었습니다.
다음 단계
다음 단계에서는 서버 서비스에서 연속 프로파일러 에이전트를 계측합니다.
3. 마이크로서비스 빌드, 푸시, 배포
Codelab 자료 다운로드
이전 단계에서는 이 Codelab의 모든 기본 요건을 설정했습니다. 이제 이를 기반으로 전체 마이크로서비스를 실행할 준비가 되었습니다. 이 Codelab 자료는 GitHub에서 호스팅되므로 다음 git 명령어를 사용하여 Cloud Shell 환경에 다운로드합니다.
cd ~ git clone https://github.com/ymotongpoo/opentelemetry-trace-codelab-go.git cd opentelemetry-trace-codelab-go
프로젝트의 디렉터리 구조는 다음과 같습니다.
. ├── README.md ├── step0 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step1 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step2 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step3 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step4 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step5 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src └── step6 ├── manifests ├── proto ├── skaffold.yaml └── src
- 매니페스트: Kubernetes 매니페스트 파일
- proto: 클라이언트와 서버 간 통신을 위한 proto 정의
- src: 각 서비스의 소스 코드 디렉터리
- skaffold.yaml: skaffold용 구성 파일
이 Codelab에서는 step4
폴더에 있는 소스 코드를 업데이트합니다. 처음부터 변경사항을 확인하려면 step[1-6]
폴더의 소스 코드를 참조할 수도 있습니다. (1부에서는 0단계부터 4단계까지, 2부에서는 5단계와 6단계를 다룹니다.)
Skaffold 명령어 실행
마지막으로, 방금 만든 Kubernetes 클러스터에 전체 콘텐츠를 빌드, 푸시, 배포할 준비가 되었습니다. 여러 단계가 포함된 것처럼 들리지만 실제로는 Scaffold가 모든 것을 처리해 줍니다. 다음 명령어로 시도해 보겠습니다.
cd step4 skaffold dev
명령어를 실행하는 즉시 docker build
의 로그 출력이 표시되고 레지스트리에 성공적으로 푸시되었는지 확인할 수 있습니다.
명령어 결과
... ---> Running in c39b3ea8692b ---> 90932a583ab6 Successfully built 90932a583ab6 Successfully tagged us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step1 The push refers to repository [us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice] cc8f5a05df4a: Preparing 5bf719419ee2: Preparing 2901929ad341: Preparing 88d9943798ba: Preparing b0fdf826a39a: Preparing 3c9c1e0b1647: Preparing f3427ce9393d: Preparing 14a1ca976738: Preparing f3427ce9393d: Waiting 14a1ca976738: Waiting 3c9c1e0b1647: Waiting b0fdf826a39a: Layer already exists 88d9943798ba: Layer already exists f3427ce9393d: Layer already exists 3c9c1e0b1647: Layer already exists 14a1ca976738: Layer already exists 2901929ad341: Pushed 5bf719419ee2: Pushed cc8f5a05df4a: Pushed step1: digest: sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe size: 2001
모든 서비스 컨테이너를 푸시하면 Kubernetes 배포가 자동으로 시작됩니다.
명령어 결과
sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8 size: 1997 Tags used in deployment: - serverservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step4@sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe - clientservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/clientservice:step4@sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8 - loadgen -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/loadgen:step4@sha256:eea2e5bc8463ecf886f958a86906cab896e9e2e380a0eb143deaeaca40f7888a Starting deploy... - deployment.apps/clientservice created - service/clientservice created - deployment.apps/loadgen created - deployment.apps/serverservice created - service/serverservice created
배포 후에는 다음과 같이 각 컨테이너의 stdout에 내보낸 실제 애플리케이션 로그가 표시됩니다.
명령어 결과
[client] 2022/07/14 06:33:15 {"match_count":3040} [loadgen] 2022/07/14 06:33:15 query 'love': matched 3040 [client] 2022/07/14 06:33:15 {"match_count":3040} [loadgen] 2022/07/14 06:33:15 query 'love': matched 3040 [client] 2022/07/14 06:33:16 {"match_count":3040} [loadgen] 2022/07/14 06:33:16 query 'love': matched 3040 [client] 2022/07/14 06:33:19 {"match_count":463} [loadgen] 2022/07/14 06:33:19 query 'tear': matched 463 [loadgen] 2022/07/14 06:33:20 query 'world': matched 728 [client] 2022/07/14 06:33:20 {"match_count":728} [client] 2022/07/14 06:33:22 {"match_count":463} [loadgen] 2022/07/14 06:33:22 query 'tear': matched 463
이 시점에서는 서버의 모든 메일을 확인하려고 합니다. 자, 마지막으로 서비스의 분산 추적을 위해 OpenTelemetry로 애플리케이션을 계측할 준비가 되었습니다.
서비스 계측을 시작하기 전에 Ctrl-C를 사용하여 클러스터를 종료하세요.
명령어 결과
... [client] 2022/07/14 06:34:57 {"match_count":1} [loadgen] 2022/07/14 06:34:57 query 'what's past is prologue': matched 1 ^CCleaning up... - W0714 06:34:58.464305 28078 gcp.go:120] WARNING: the gcp auth plugin is deprecated in v1.22+, unavailable in v1.25+; use gcloud instead. - To learn more, consult https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke - deployment.apps "clientservice" deleted - service "clientservice" deleted - deployment.apps "loadgen" deleted - deployment.apps "serverservice" deleted - service "serverservice" deleted
요약
이 단계에서는 개발자 환경에 Codelab 자료를 준비했으며 Scaffold가 예상대로 실행되는지 확인했습니다.
다음 단계
다음 단계에서는 loadgen 서비스의 소스 코드를 수정하여 trace 정보를 계측합니다.
4. Cloud Profiler 에이전트 계측
지속적 프로파일링 개념
지속적 프로파일링의 개념을 설명하기 전에 먼저 프로파일링의 개념을 이해해야 합니다. 프로파일링은 애플리케이션을 동적으로 분석하는 방법 (동적 프로그램 분석) 중 하나이며 일반적으로 애플리케이션 개발 중에 부하 테스트 등의 프로세스를 거치게 됩니다. 이는 특정 기간 동안 CPU 및 메모리 사용량과 같은 시스템 메트릭을 측정하기 위한 단일 샷 활동입니다. 개발자는 프로필 데이터를 수집한 후 코드에서 이를 분석합니다.
연속 프로파일링은 일반 프로파일링의 확장된 접근 방식으로, 장기 실행 애플리케이션에 대해 짧은 기간 프로필을 주기적으로 실행하고 다수의 프로필 데이터를 수집합니다. 그런 다음 버전 번호, 배포 영역, 측정 시간 등 애플리케이션의 특정 속성을 기반으로 통계 분석을 자동으로 생성합니다. 개념에 대한 자세한 내용은 문서를 참조하세요.
타겟은 실행 중인 애플리케이션이므로, 주기적으로 프로필 데이터를 수집하여 통계 데이터를 후처리하는 백엔드로 전송하는 방법이 있습니다. 이것은 Cloud Profiler 에이전트이며 곧 서버 서비스에 삽입할 것입니다.
Cloud Profiler 에이전트 삽입
Cloud Shell 오른쪽 상단에 있는 버튼을 눌러 Cloud Shell 편집기를 엽니다. 왼쪽 창의 탐색기에서
step4/src/server/main.go
를 열고 기본 함수를 찾습니다.
step4/src/server/main.go
func main() { ... // step2. setup OpenTelemetry tp, err := initTracer() if err != nil { log.Fatalf("failed to initialize TracerProvider: %v", err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Fatalf("error shutting down TracerProvider: %v", err) } }() // step2. end setup svc := NewServerService() // step2: add interceptor interceptorOpt := otelgrpc.WithTracerProvider(otel.GetTracerProvider()) srv := grpc.NewServer( grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor(interceptorOpt)), grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor(interceptorOpt)), ) // step2: end adding interceptor shakesapp.RegisterShakespeareServiceServer(srv, svc) healthpb.RegisterHealthServer(srv, svc) if err := srv.Serve(lis); err != nil { log.Fatalf("error serving server: %v", err) } }
main
함수에는 Codelab 1부에서 수행한 OpenTelemetry 및 gRPC의 설정 코드가 표시됩니다. 이제 여기에서 Cloud Profiler 에이전트의 계측을 추가합니다. initTracer()
에서와 마찬가지로 가독성을 위해 initProfiler()
함수를 작성할 수 있습니다.
step4/src/server/main.go
import ( ... "cloud.google.com/go/profiler" // step5. add profiler package "cloud.google.com/go/storage" ... ) // step5: add Profiler initializer func initProfiler() { cfg := profiler.Config{ Service: "server", ServiceVersion: "1.0.0", NoHeapProfiling: true, NoAllocProfiling: true, NoGoroutineProfiling: true, NoCPUProfiling: false, } if err := profiler.Start(cfg); err != nil { log.Fatalf("failed to launch profiler agent: %v", err) } }
profiler.Config{}
객체에 지정된 옵션을 자세히 살펴보겠습니다.
- 서비스: 프로파일러 대시보드에서 선택하고 전환할 수 있는 서비스 이름입니다.
- ServiceVersion: 서비스 버전 이름 이 값을 기준으로 프로필 데이터 세트를 비교할 수 있습니다.
- NoHeapProfiling: 메모리 소비 프로파일링을 사용 중지합니다.
- NoAllocProfiling: 메모리 할당 프로파일링을 사용 중지합니다.
- NoGoroutineProfiling: goroutine 프로파일링을 사용 중지합니다.
- NoCPUProfiling: CPU 프로파일링을 중지합니다.
이 Codelab에서는 CPU 프로파일링만 사용 설정합니다.
이제 main
함수에서 이 함수를 호출하기만 하면 됩니다. import 블록에서 Cloud Profiler 패키지를 가져와야 합니다.
step4/src/server/main.go
func main() { ... defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Fatalf("error shutting down TracerProvider: %v", err) } }() // step2. end setup // step5. start profiler go initProfiler() // step5. end svc := NewServerService() // step2: add interceptor ... }
go
키워드를 사용하여 initProfiler()
함수를 호출합니다. profiler.Start()
가 차단되므로 다른 goroutine에서 실행해야 합니다. 이제 빌드할 준비가 되었습니다. 배포 전에 go mod tidy
를 실행해야 합니다.
go mod tidy
이제 새 서버 서비스로 클러스터를 배포합니다.
skaffold dev
Cloud Profiler에 Flame 그래프가 표시되는 데 일반적으로 몇 분 정도 걸립니다. '프로파일러'를 입력합니다. 를 클릭하고 Profiler 아이콘을 클릭합니다.
그러면 다음과 같은 Flame 그래프가 표시됩니다.
요약
이 단계에서는 Cloud Profiler 에이전트를 서버 서비스에 삽입하고 Flame 그래프가 생성되는 것을 확인했습니다.
다음 단계
다음 단계에서는 Flame 그래프를 사용하여 애플리케이션의 병목 현상 원인을 조사합니다.
5. Cloud Profiler Flame 그래프 분석
Flame 그래프란 무엇인가요?
Flame 그래프는 프로필 데이터를 시각화하는 방법 중 하나입니다. 자세한 내용은 Google 문서를 참고하세요. 간략한 요약은 다음과 같습니다.
- 각 막대는 애플리케이션의 메서드/함수 호출을 표현합니다.
- 세로 방향은 호출 스택입니다. 위에서 아래로 확장되는 호출 스택
- 가로 방향은 리소스 사용량입니다. 길어질수록 나빠집니다.
이를 고려하여 얻은 플레임 그래프를 살펴보겠습니다.
Flame 그래프 분석
이전 섹션에서는 플레임 그래프의 각 막대가 함수/메서드 호출을 표현하며 그 길이가 함수/메서드의 리소스 사용량을 의미한다는 것을 배웠습니다. Cloud Profiler의 Flame 그래프는 막대를 내림차순 또는 왼쪽에서 오른쪽 순서로 정렬하므로 먼저 그래프의 왼쪽 상단부터 볼 수 있습니다.
여기서는 grpc.(*Server).serveStreams.func1.2
가 CPU 시간을 가장 많이 소비하고 있음을 알 수 있습니다. 호출 스택을 위에서 아래로 살펴보면 대부분의 시간을 서버 서비스의 gRPC 서버 핸들러인 main.(*serverService).GetMatchCount
에서 소비합니다.
GetMatchCount에 regexp.MatchString
및 regexp.Compile
라는 일련의 정규식 함수가 표시됩니다. 표준 패키지에 속합니다. 즉, 성능을 비롯한 다양한 관점에서 제대로 테스트되어야 합니다. 하지만 여기서의 결과는 regexp.MatchString
및 regexp.Compile
에서 CPU 시간 리소스 사용량이 높음을 보여줍니다. 이러한 사실을 고려할 때 여기서는 regexp.MatchString
를 사용하는 것이 성능 문제와 관련이 있다고 가정합니다. 이제 함수가 사용되는 소스 코드를 읽어 보겠습니다.
step4/src/server/main.go
func (s *serverService) GetMatchCount(ctx context.Context, req *shakesapp.ShakespeareRequest) (*shakesapp.ShakespeareResponse, error) { resp := &shakesapp.ShakespeareResponse{} texts, err := readFiles(ctx, bucketName, bucketPrefix) if err != nil { return resp, fmt.Errorf("fails to read files: %s", err) } for _, text := range texts { for _, line := range strings.Split(text, "\n") { line, query := strings.ToLower(line), strings.ToLower(req.Query) isMatch, err := regexp.MatchString(query, line) if err != nil { return resp, err } if isMatch { resp.MatchCount++ } } } return resp, nil }
여기에서 regexp.MatchString
가 호출됩니다. 소스 코드를 읽으면 함수가 중첩된 for-loop 내에서 호출되는 것을 확인할 수 있습니다. 따라서 이 함수의 사용이 잘못될 수도 있습니다. regexp의 GoDoc를 찾아보겠습니다.
문서에 따르면 regexp.MatchString
는 모든 호출에서 정규 표현식 패턴을 컴파일합니다. 대규모 리소스 소비의 원인은 다음과 같습니다.
요약
이 단계에서는 Flame 그래프를 분석하여 리소스 소비의 원인을 파악했습니다.
다음 단계
다음 단계에서는 서버 서비스의 소스 코드를 업데이트하고 버전 1.0.0에서 변경된 사항을 확인합니다.
6. 소스 코드 업데이트 및 Flame 그래프 비교
소스 코드 업데이트
이전 단계에서는 regexp.MatchString
사용이 대규모 리소스 소비와 관련이 있다고 가정했습니다. 이제 이 문제를 해결해 보겠습니다. 코드를 열고 해당 부분을 약간 변경합니다.
step4/src/server/main.go
func (s *serverService) GetMatchCount(ctx context.Context, req *shakesapp.ShakespeareRequest) (*shakesapp.ShakespeareResponse, error) { resp := &shakesapp.ShakespeareResponse{} texts, err := readFiles(ctx, bucketName, bucketPrefix) if err != nil { return resp, fmt.Errorf("fails to read files: %s", err) } // step6. considered the process carefully and naively tuned up by extracting // regexp pattern compile process out of for loop. query := strings.ToLower(req.Query) re := regexp.MustCompile(query) for _, text := range texts { for _, line := range strings.Split(text, "\n") { line = strings.ToLower(line) isMatch := re.MatchString(line) // step6. done replacing regexp with strings if isMatch { resp.MatchCount++ } } } return resp, nil }
이제 regexp 패턴 컴파일 프로세스가 regexp.MatchString
에서 추출되어 중첩된 for 루프 밖으로 이동합니다.
이 코드를 배포하기 전에 initProfiler()
함수의 버전 문자열을 업데이트해야 합니다.
step4/src/server/main.go
func initProfiler() { cfg := profiler.Config{ Service: "server", ServiceVersion: "1.1.0", // step6. update version NoHeapProfiling: true, NoAllocProfiling: true, NoGoroutineProfiling: true, NoCPUProfiling: false, } if err := profiler.Start(cfg); err != nil { log.Fatalf("failed to launch profiler agent: %v", err) } }
이제 작동 방식을 알아보겠습니다. skaffold 명령어로 클러스터를 배포합니다.
skaffold dev
잠시 후 Cloud Profiler 대시보드를 새로고침하여 상태를 확인하세요.
버전 1.1.0의 프로필만 표시되도록 버전을 "1.1.0"
로 변경해야 합니다. 보시다시피 GetMatchCount의 막대 길이가 줄어들고 CPU 시간 사용 비율이 줄었습니다 (즉, 막대가 짧아짐).
단일 버전의 Flame 그래프를 보는 것뿐만 아니라 두 버전 간의 차이점도 비교할 수 있습니다.
'비교 대상' 값 변경 드롭다운 목록에서 '버전'을 선택합니다. '비교 버전'의 값을 '1.0.0'으로 변경합니다.
이런 종류의 Flame 그래프가 표시됩니다. 그래프의 모양은 1.1.0과 동일하지만 색상이 다릅니다. 비교 모드에서 색상의 의미는 다음과 같습니다.
- 파란색: 감소한 값 (리소스 소비)
- 주황색: 획득한 값 (리소스 소비)입니다.
- 회색: 중성색
범례를 고려하여 함수를 자세히 살펴보겠습니다. 확대하려는 막대를 클릭하면 스택 내에서 자세한 내용을 볼 수 있습니다. main.(*serverService).GetMatchCount
막대를 클릭하세요. 또한 막대 위로 마우스를 가져가면 비교 세부정보를 확인할 수 있습니다.
총 CPU 시간이 5.26초에서 2.88초로 줄었다고 표시됩니다 (총계는 10초 = 샘플링 기간). 상당한 개선 효과를 얻으셨네요.
이제 프로필 데이터 분석을 통해 애플리케이션 성능을 개선할 수 있습니다.
요약
이 단계에서는 서버 서비스에서 수정하고 Cloud Profiler의 비교 모드가 개선되었음을 확인했습니다.
다음 단계
다음 단계에서는 서버 서비스의 소스 코드를 업데이트하고 버전 1.0.0에서 변경된 사항을 확인합니다.
7. 추가 단계: Trace 폭포식 구조의 개선사항 확인
분산 트레이스와 지속적 프로파일링의 차이점
Codelab의 1부에서는 요청 경로의 마이크로서비스 전반에서 병목 현상 서비스를 파악할 수 있지만 특정 서비스에서 병목 현상의 정확한 원인을 파악할 수 없음을 확인했습니다. 이 Codelab 2부에서는 연속 프로파일링을 통해 호출 스택에서 단일 서비스 내부의 병목 현상을 식별할 수 있음을 배웠습니다.
이 단계에서는 분산 trace (Cloud Trace)의 폭포식 구조 그래프를 검토하고 연속 프로파일링과의 차이점을 살펴보겠습니다.
이 폭포식 구조 그래프는 'love' 쿼리가 포함된 트레이스 중 하나입니다. 총 약 6.7초 (6700ms)가 걸립니다.
이는 동일한 쿼리를 개선한 이후입니다. 아시다시피 현재 총 지연 시간은 1.5초 (1500ms)이며, 이는 이전 구현에 비해 크게 개선된 것입니다.
여기서 중요한 점은 분산 트레이스 폭포식 구조 차트에서 계측이 모든 전역에 걸쳐 있지 않으면 호출 스택 정보를 사용할 수 없다는 것입니다. 또한 분산 트레이스는 서비스 전체의 지연 시간에만 초점을 맞추는 반면 연속 프로파일링은 단일 서비스의 컴퓨터 리소스 (CPU, 메모리, OS 스레드)에 중점을 둡니다.
또 다른 측면에서 분산 trace는 이벤트 기반이며 연속 프로필은 통계입니다. 각 trace에는 지연 시간 그래프가 다르며 지연 시간 변화의 추세를 가져오려면 분포와 같은 다른 형식이 필요합니다.
요약
이 단계에서는 분산 트레이스와 연속 프로파일링의 차이점을 확인했습니다.
8. 축하합니다
OpenTelemery로 분산 트레이스를 성공적으로 만들고 Google Cloud Trace에서 마이크로서비스 전반의 요청 지연 시간을 확인했습니다.
더 많은 연습을 하려면 다음 주제를 직접 시도해 보세요.
- 현재 구현은 상태 점검에서 생성된 모든 스팬을 전송합니다. (
grpc.health.v1.Health/Check
) Cloud Trace에서 이러한 스팬을 필터링하려면 어떻게 해야 하나요? 힌트는 여기에서 확인하세요. - 이벤트 로그를 스팬과 연결하여 Google Cloud Trace 및 Google Cloud Logging에서 어떻게 작동하는지 확인합니다. 힌트는 여기에서 확인하세요.
- 일부 서비스를 다른 언어의 서비스로 바꾸고 해당 언어의 OpenTelemetry를 사용해 계측해 보세요.
또한 이후에 프로파일러에 대해 알아보려면 2부로 이동하세요. 이 경우 아래의 정리 섹션을 건너뛸 수 있습니다.
정리
이 Codelab 후에는 Google Kubernetes Engine, Google Cloud Trace, Google Artifact Registry에 예상치 못한 요금이 발생하지 않도록 Kubernetes 클러스터를 중지하고 프로젝트를 삭제해야 합니다.
먼저 클러스터를 삭제합니다. skaffold dev
로 클러스터를 실행하는 경우 Ctrl-C를 누르기만 하면 됩니다. skaffold run
로 클러스터를 실행하는 경우 다음 명령어를 실행합니다.
skaffold delete
명령어 결과
Cleaning up... - deployment.apps "clientservice" deleted - service "clientservice" deleted - deployment.apps "loadgen" deleted - deployment.apps "serverservice" deleted - service "serverservice" deleted
클러스터를 삭제한 후 메뉴 창에서 'IAM 및 관리 > '설정'을 클릭한 다음 '종료'를 클릭합니다. 버튼을 클릭합니다.
그런 다음 대화상자의 양식에 프로젝트 ID (프로젝트 이름 아님)를 입력하고 종료를 확인합니다.