들어가며
Pinpoint란?
Pinpoint는 Google의 대규모 분산 시스템 추적 인프라 Dapper를 모델로 삼아 네이버에서 만든 대규모 분산 시스템을 위한 APM 도구입니다.
Pinpoint는 시스템의 전체 구조를 분석하고 분산 어플리케이션 전반의 트랜잭션을 추적하여 시스템 내의 구성 요소가 상호 연결되는 방식을 분석하는데 도움이 되는 솔루션을 제공합니다.
Pinpoint의 구성요소
Pinpoint를 구성하는 구성요소는 크게 4가지입니다.
•
HBase
◦
Hadoop 기반의 분산 데이터베이스로 빅데이터를 저장하기 위해서 사용
◦
Agent를 통해 수집한 데이터를 적재하는 역할
◦
실제 운영 환경에서 대량의 데이터를 무중단으로 다루기 위해선 AWS EMR 등의 서비스 이용
•
Pinpoint Agent
◦
어플리케이션의 모니터링 정보를 Collector로 전달하는 역할
•
Pinpoint Collector
◦
Agent를 통해 받은 정보를 HBase에 적재하는 역할
•
Pinpoint Web
◦
HBase에 적재된 데이터를 Web으로 노출하여 모니터링을 제공하는 역할
Pinpoint의 특징
•
실시간으로 어플리케이션 내부의 활성 스레드를 모니터링
•
어플리케이션의 Topology를 이해할 수 있도록 ServerMap 제공
•
모든 Transaction에 대한 CallStack을 제공하여 코드 수준 가시성 확보
•
Inspector를 통한 CPU 사용량, 메모리/가비지 수집, TPS, JVM 등 어플리케이션에 대한 추가 세부 정보 제공
•
코드 변경 없는 APM Agent 설치
•
성능에 미치는 영향 최소화 (리소스 사용량 약 3% 증가)
Pinpoint 설치
우선 Dockerfile을 작성하기 위해 Local 환경에서 진행하겠습니다.
또한 JDK와 HBase, Controller, Web의 버전은 아래의 표를 참고하여 주시면 됩니다.
HBase
wget https://archive.apache.org/dist/hbase/1.2.7/hbase-1.2.7-bin.tar.gz
wget https://raw.githubusercontent.com/pinpoint-apm/pinpoint/master/hbase/scripts/hbase-create.hbase
tar xzf hbase-1.2.7-bin.tar.gz
ln -s hbase-1.2.7 hbase
Shell
복사
wget 을 통해 hbase-1.2.7과 pinpoint에서 제공하는 hbase 스크립트를 다운받습니다.
tar 로 hbase-1.2.7의 압축을 해제합니다.
hbase-1.2.7로 사용하는 명령어를 단축하기 위해 ln 을 통해 링크를 걸어줍니다.
hbase-env.sh
export JAVA_HOME=/usr/local/openjdk-8
#export HBASE_MASTER_OPTS="$HBASE_MASTER_OPTS -XX:PermSize=128m -XX:MaxPermSize=128m -XX:ReservedCodeCacheSize=256m"
#export HBASE_REGIONSERVER_OPTS="$HBASE_REGIONSERVER_OPTS -XX:PermSize=128m -XX:MaxPermSize=128m -XX:ReservedCodeCacheSize=256m"
Shell
복사
vi hbase/conf/hbase-env.sh 을 통해 hbase-env.sh를 열어줍니다.
HBase를 지원하는 JDK를 설치한 후 JAVA_HOME 환경변수를 설정해줍니다.
WARN 로그를 방지하기 위해 JDK8에선 사용하지 않는 GC Option을 주석처리 해줍니다.
hbase-site.xml
<configuration>
<property>
<name>hbase.rootDir</name>
<value>file:///pinpoint/hbase-1.2.7/data</value>
</property>
<property>
<name>hbase.zookeeper.property.dataDir</name>
<value>/pinpoint/hbase-1.2.7/zookeeper</value>
</property>
<property>
<name>hbase.zookeeper.property.clientPort</name>
<value>2181</value>
</property>
</configuration>
Shell
복사
vi hbase/conf/hbase-site.xml 을 통해 hbase-site.xml을 열어줍니다.
configuration에 rootDir, dataDir, clientPort에 해당하는 property를 추가해줍니다.
Zip hbase
tar -zcvf hbase-1.2.7.tar.gz hbase-1.2.7
Shell
복사
Dockerfile에 COPY를 통해 설정을 변경한 hbase를 포함시키기 위해 tar -zcvf 로 다시 압축해줍니다.
Pinpoint Controller
wget https://github.com/pinpoint-apm/pinpoint/releases/download/v2.2.2/pinpoint-collector-boot-2.2.2.jar
chmod +x pinpoint-collector-boot-2.2.2.jar
Shell
복사
Pinpoint Web
wget https://github.com/pinpoint-apm/pinpoint/releases/download/v2.2.2/pinpoint-web-boot-2.2.2.jar
chmod +x pinpoint-web-boot-2.2.2.jar
Shell
복사
wget 을 통해 controller와 web을 다운 받습니다.
쉘스크립트로 controller와 web의 jar를 실행하기 위해 chmod +x 로 실행 권한을 부여합니다.
start.sh
#!/bin/sh
hbase/bin/start-hbase.sh
hbase/bin/hbase shell ./hbase-create.hbase
nohup java -jar -Dpinpoint.zookeeper.address=localhost ./pinpoint-collector-boot-2.2.2.jar >/dev/null 2>&1 &
nohup java -jar -Dpinpoint.zookeeper.address=localhost ./pinpoint-web-boot-2.2.2.jar >/dev/null 2>&1 &
while true; do
sleep 60
done
Shell
복사
Docker 컨테이너가 실행 됐을 때 여러가지 명령을 수행하기 위해 쉘스크립트를 작성해줍니다.
hbase/bin/start-hbase.sh
Shell
복사
hbase를 실행합니다.
hbase/bin/hbase shell ./hbase-create.hbase
Shell
복사
hbase 쉘 명령을 통해 hbase 스크립트를 실행합니다.
nohup java -jar -Dpinpoint.zookeeper.address=localhost ./pinpoint-collector-boot-2.2.2.jar >/dev/null 2>&1 &
nohup java -jar -Dpinpoint.zookeeper.address=localhost ./pinpoint-web-boot-2.2.2.jar >/dev/null 2>&1 &
Shell
복사
nohup을 통해 백그라운드로 controller와 web을 실행하고 이 때 나온 로그는 >/dev/null 2>&1 를 통해 버립니다.
Docker 작성
저는 한 인스턴스에 HBase, Controller, Web을 한꺼번에 실행하기 위해 하나의 Dockerfile로 작성하였습니다.
혹시라도 각각 다른 인스턴스로 구성하고 싶다면 각각 Dockerfile을 작성한 후 docker-compose.yml 파일을 작성해주시면 됩니다.
Dockerfile-pinpoint
FROM ubuntu:latest
FROM openjdk:8
WORKDIR /pinpoint
COPY hbase-1.2.7.tar.gz /pinpoint/
COPY hbase-create.hbase /pinpoint/
COPY pinpoint-collector-boot-2.2.2.jar /pinpoint/
COPY pinpoint-web-boot-2.2.2.jar /pinpoint/
COPY start.sh /pinpoint/
RUN tar -xzvf hbase-1.2.7.tar.gz && \
ln -s hbase-1.2.7 hbase && \
chmod +x ./start.sh
ENTRYPOINT ["./start.sh"]
EXPOSE 8080 9991 9992 9993 16010
Shell
복사
FROM을 통해 ubuntu 와 jdk8을 설치해준 후 COPY로 앞서 설치한 파일들을 옮깁니다.
RUN으로 hbase의 압축을 풀어준 후 링크를 걸고 미리 작성한 쉘스크립트에 실행 권한을 부여합니다.
Docker 컨테이너가 실행된 후 쉘스크립트를 실행하기 위해 ENTRYPOINT를 적어줍니다.
마지막으로 HBase(16010), Controller(9991, 9992, 9993), Web(8080)에서 사용하는 각각의 포트를 컨테이너 포트로 열어줍니다.
Pinpoint Docker Push
aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin 661851899694.dkr.ecr.ap-northeast-2.amazonaws.com
docker build -t pinpoint:latest -f Dockerfile .
docker tag pinpoint:latest {{ECR-ARN}}:pinpoint
docker push {{ECR-ARN}}:pinpoint
Shell
복사
작성한 Dockerfile의 docker 이미지를 AWS ECR에 push 해주기 위한 과정입니다.
aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin {{ECR-ARN}}
Shell
복사
(Get-ECRLoginCommand).Password | docker login --username AWS --password-stdin {{ECR-ARN}}
Shell
복사
aws ecr에 로그인하여 push 권한을 얻어옵니다.
위 방법은 MacOS/Linux의 방법으로 Windows는 아래의 방법으로 진행합니다.
docker build -t pinpoint:latest -f Dockerfile .
docker tag pinpoint:latest {{ECR-ARN}}:pinpoint
docker push {{ECR-ARN}}:pinpoint
Shell
복사
이제 작성한 Dockerfile을 build한 후 ECR에 push하기 위한 tag를 지정합니다.
지정한 tag를 이용해 ECR로 push 해줍니다.
Pinpoint ECS 생성
Pinpoint task-definition 생성
•
시작 유형
◦
AWS Fargate 또는 EC2 상관없지만 네트워크 모드를 Agent를 붙일 서버와 같은 VPC로 지정해줍니다.
•
운영 체제/아키텍처
◦
저는 ARM64에서 실행하기 위한 쉘스크립트로 작성했기 때문에 ARM64로 설정하였으며 X86_64로 할 경우 쉘스크립트가 실행되지 않는 이슈가 발생합니다.
•
태스크 크기
◦
HBase, Controller, Web을 한 인스턴스에서 실행할 경우 메모리를 꽤 많이 사용하기 때문에 넉넉하게 설정해주지 않으면 HBase가 뻗는 이슈가 발생합니다.
•
태스크 실행 역할
◦
설정한 태스크 실행 역할엔 logs:CreateLogGroup 정책을 추가해주지 않으면 태스크를 생성하기 못하는 이슈가 발생합니다.
Agent와 Controller가 통신하는 프로토콜은 gRPC(Google Remote Procedure Call)이며 포트는 9991(Metadata), 9992(Stat), 9993(Span) 입니다.
그렇기 때문에 컨테이너 설정에서 포트는 9991-9993을 열어주고 프로토콜은 gRPC로 설정해주어야 합니다.
AWS Fargate에 할당되는 임시 스토리지는 20GB입니다.
저는 한 인스턴스에 HBase, Controller, Web을 한꺼번에 띄웠기 때문에 임시 스토리지를 200GB로 늘렸습니다.
이 임시 스토리지는 태스크를 생성할 때마다 새로운 임시 스토리지가 수신되기 때문에 AWS EFS 같은 파일 시스템을 통해 볼륨을 마운트하여 스토리지를 영구적으로 보존하고 늘릴 수 있습니다.
Pinpoint ECS 생성
ECS 클러스터의 서비스 생성에서 앞서 작성한 task-definition을 선택해줍니다.
서비스 연결 켜기를 통해 네임스페이스를 지정해줍니다.
네임스페이스에 대한 내용은 아래 페이지에 설명이 있어서 링크 첨부로 대신하겠습니다.
해당 네임스페이스엔 Agent를 붙일 서버들이 포함되어 있어야 합니다.
포트 매핑으로 task-definition에서 뚫어두었던 포트들을 매핑하는데 검색은 고유한 이름으로 유니크하게 설정하셔야하며 DNS는 통일해주셔야 합니다.
이렇게 하여야 http://pinpoint:9991, http://pinpoint:9992, http://pinpoint:9993으로 통신이 가능합니다.
•
VPC
◦
Agent를 붙일 서버와 동일한 VPC로 설정해줍니다.
•
Subnet
◦
서브넷은 VPC와 동일하게 Agent를 붙일 서버와 동일한 서브넷으로 설정하며 외부에선 ALB 없이 접근할 일이 없기 때문에 Private Subnet으로 설정합니다.
•
Security Group
◦
Pinpoint 인스턴스는 8080포트와 9991-9993 포트에 대한 접근만 필요하기 때문에 개별적인 보안 그룹으로 설정해줍니다.
•
Public IP
◦
ALB를 통한 접근만을 허용하기 때문에 퍼블릭IP는 DISABLED로 설정합니다.
저는 Pinpoint Web에 대한 접근을 ALB로 할 것이기 때문에 로드 밸런싱 설정을 해주었습니다.
ALB가 pinpoint 컨테이너의 8080 포트를 바라보게끔 설정하기 위해 컨테이너와 대상 그룹을 선택해줍니다.
상태 확인을 위한 / 와 /main 은 StatusCode 200, 304가 나오므로 /serverTime 로 설정하여 정상적인 상태 확인이 가능하도록 설정합니다.
또한 서버가 켜지는데 어느정도 시간이 걸리기 때문에 상태 확인 중 컨테이너 종료를 방지하기 위해 상태 검사 유예 시간을 300초로 설정합니다.
Pinpont Agent 설정
저는 프로젝트에 Agent를 포함시키지 않으려고 로컬에서 작업 후 EFS가 마운트되어 있는 EC2에 전송하여 사용하였습니다.
이후 Spring Boot 컨테이너에 Agent를 설치한 경로에 해당하는 EFS의 엑세스 포인트로 볼륨을 마운트해주었습니다.
EFS를 사용하지 않으려면 프로젝트에 Agent를 포함한 후 프로젝트에서의 경로로 설정해주시면 됩니다.
Pinpoint Agent
wget https://github.com/pinpoint-apm/pinpoint/releases/download/v2.3.2/pinpoint-agent-2.3.2.tar.gz
tar xvf pinpoint-agent-2.3.2.tar.gz
Shell
복사
wget 을 통해 Pinpoint Agent를 설치한 후 압축을 풀어줍니다.
vi /data/pinpoint/pinpoint-agent-2.3.2/pinpoint-root.config
#Collector IP 설정
profiler.transport.grpc.collector.ip=127.0.0.1
#Agent Collector IP 설정
profiler.transport.grpc.agent.collector.ip=${profiler.transport.grpc.collector.ip}
#Metadata Collector IP 설정
profiler.transport.grpc.metadata.collector.ip=${profiler.transport.grpc.collector.ip}
#Stat Collector IP 설정
profiler.transport.grpc.stat.collector.ip=${profiler.transport.grpc.collector.ip}
#Span Collector IP 설정
profiler.transport.grpc.span.collector.ip=${profiler.transport.grpc.collector.ip}
#수집할 Http Request Header 설정
profiler.http.record.request.headers=Path, Origin, Referer, User-Agent, X-Access-Token
#제외할 URL 설정
profiler.server.excludeurl=/, /actuator/prometheus
Shell
복사
pinpoint-root.config은 Agent의 전역 설정을 관리할 수 있습니다.
이 중 grpc에 해당하는 Collector IP를 수정해줍니다.
또한 -Dpinpoint.profiler.transport.grpc.collector.ip= 인자를 통해 Collector IP를 수정해줄 수 있습니다.
vi /data/pinpoint/pinpoint-agent-2.3.2/profiles/local/pinpoint.config
#Collector IP 설정
profiler.transport.grpc.collector.ip=127.0.0.1
#Collector IP 설정
profiler.transport.grpc.collector.ip=127.0.0.1
Shell
복사
vi /data/pinpoint/pinpoint-agent-2.3.2/profiles/release/pinpoint.config
#Collector IP 설정
profiler.transport.grpc.collector.ip=127.0.0.1
#Collector IP 설정
profiler.collector.ip=127.0.0.1
Shell
복사
Pinpoint는 profile을 지원해주며 Agent가 실행될 때 profile 설정을 먼저 읽습니다.
profile은 -Dpinpoint.profiler.profiles.active= 인자를 통해 설정할 수 있습니다.
Pinpoint-agent 압축 및 EC2 전송
tar -zcvf pinpoint-agent-2.3.2.tar.gz pinpoint-agent-2.3.2
scp -i key.pem ~/pinpoint-agent-2.3.2.tar.gz ec2-user@public-ip:~
Shell
복사
설정이 끝난 Agent를 tar -zcvf 를 통해 압축 후 scp 를 통해 EFS가 마운트 된 EC2로 전송해줍니다.
Dockerfile-app
java -jar -javaagent:/{{Agent-Path}}/pinpoint-bootstrap-2.3.2.jar \
-Dpinpoint.agentId={{Agent-ID}} \
-Dpinpoint.applicationName={{Group-ID}} &
Shell
복사
Spring Boot에 Pinpoint Agent를 붙이기 위해선 위 처럼 인자를 붙여줘야합니다.
FROM amazoncorretto:17
WORKDIR /app
ENV JAR_FILE="<JAR_FILE>"
ENV APM_FILE="<APM_FILE>"
ENV APM_OPTS="<APM_OPTS>"
ENV JAVA_OPTS="<JAVA_OPTS>"
COPY ${JAR_FILE} /app/gocho-api.jar
COPY ${APM_FILE} /app/agents
EXPOSE 8080
ENTRYPOINT java ${APM_OPTS} ${JAVA_OPTS} -jar gocho-api.jar
Shell
복사
CI/CD 과정에서 Docker Image를 만들기 위한 Dockerfile에 APM_FILE과 APM_OPTS를 추가하여 Pinpoint Agent를 붙이기 위한 설정을 해줍니다.
#Inject Variables in DockerFile
- name: Inject Variables in DockerFile
working-directory: be/deploy
run: |
sed "s|<JAR_FILE>|$(echo module-${{ env.SERVICE }}-api/build/libs/module-${{ env.SERVICE }}-api-0.0.1-SNAPSHOT.jar)|g" Dockerfile-app > Dockerfile-app-new
mv Dockerfile-app-new Dockerfile-app
sed "s|<JAVA_OPTS>|$(echo -Duser.timezone=Asia/Seoul -Dspring.profiles.active=${{ env.SERVER }})|g" Dockerfile-app > Dockerfile-app-new
mv Dockerfile-app-new Dockerfile-app
sed "s|<APM_FILE>|$(echo agents/.)|g" Dockerfile-app > Dockerfile-app-new
mv Dockerfile-app-new Dockerfile-app
sed "s|<APM_OPTS>|$(echo -javaagent:/app/agents/pinpoint-agent/pinpoint-bootstrap-2.3.2.jar -Dpinpoint.agentId=${{ env.SERVICE }} -Dpinpoint.applicationName=gochodaejol)|g" Dockerfile-app > Dockerfile-app-new
mv Dockerfile-app-new Dockerfile-app
Shell
복사
위에서 추가한 APM_FILE, APM_OPTS는 Github action의 Deploy 스크립트를 통해 실질적인 값을 주입해줍니다.
이 때 APM_FILE의 경로는 마운트한 EFS의 엑세스 포인트로 설정해줘서 EFS에 올려둔 Agent로 사용하도록 합니다.
결과물
Pinpoint Web을 접속해보면 아래와 같은 모습을 확인할 수 있습니다.