이번에 물류 검수 앱을 개발하면서 전체적인 인프라를 AWS에 구축함에 따라 서비스에 필요한 모든 부분을 구성하게 되었던 경험에 대한 회고를 작성한다.
범위 | 스택 | 버전 | 디펜던시 |
프론트 | Vue | 3.1.x | Vite |
백엔드 | SpringBoot | 3.0.3 | Gradle |
CICD | Github Actions | ||
로그 | FastAPI, ELK | Pydatic, Alembic | |
디비 | RDS(postgresql) | 15 | |
AMQP | Kafka | Kafka Connector |
처음 기획했던 인프라 구조도는 위와 같다.
전체적인 서비스를 기획하면서 개발 플로우를 어떻게 진행할지에 대한 고민이 많았다. 기존에 진행했던 방식은 3가지 브랜치를 운영하는 방식이었다.
- dev 브랜치: 개발을 진행하는 브랜치로써 머지 리퀘스트를 진행한 후 개발 서버에서 결과물을 빠르게 확인할 수 있다.
- qa 브랜치: 테스트를 진행하는 브랜치로써 dev 브랜치에서 만들어진 프로토타입을 풀받은 후 테스트를 진행한다.
- main 브랜치: 실제 서비스가 운영되는 브랜치로써 푸시, 머지 리퀘스트에 제한이 있는 브랜치, 각 배포 버전에 태그가 달리며 버전으로 관리된다.
애자일로 빠르게 진행해야 했던 프로젝트인 만큼 이번에는 과감하게 dev 브랜치를 없애고 스크럼 방식으로 개발을 진행해야 했기에 dev 서버 없이 main, qa 브랜치 만으로 개발을 진행해 보았다. 대신 지라의 작업 숫자를 브랜치 명으로 하여(예: dev-3413) qa 브랜치에서 따로 파고 진행하는 방식으로 협업을 진행했다.
프론트 구성은 위와 같다. 프론트 또한 블루 그린으로 구성해봤는데 이러면 배포 시에 프론트 또한 무중단으로 배포할 수 있지 않을까? 하는 생각에 구성해봤다. 그런데 구성하고 나니 그냥 빌드 파일을 던지는 거랑 1초 정도 밖에 차이가 안나서... 그냥 다시 빌드 파일만 던지는 구조로 구성을 변경했다. 요즘엔 S3에 dist 폴더를 던지는게 요금, 성능 적으로 유리하다고 하니 다음 구성은 S3를 이용한 구성으로 변경할 예정이다.
Github Actions를 이용해 build, test 스텝을 끝낸 후 배포하는 과정이다. 빌드 테스트 후에 EC2에서 다시 빌드를 하다보니 EC2 안에서 빌드 타임이 또 걸리는 문제가 있다. 향후에 S3에 서비스를 배포하는 방식으로 변경하면 EC2 안에서 다시 빌드하는 시간을 아낄 수 있을 것 같다. 스크립트는 아래와 같다.
- qa
Github Actions의 환경 변수들에 숨길 값들을 넣어주고 배포 과정에서 주입해준다. 프론트 배포 과정은 아래와 같다.
- 빌드, 테스트 코드가 병렬로 돌아감
- 둘 다 완료되면 S3, CodeDeploy를 통해서 EC2에 배포
- 배포 완료되면 deploy.sh 스크립트를 통해서 배포 진행
각 디펜던시를 설치하는 부분엔 캐싱이 적용되어 있다. yarn.lock 등의 파일에 변경 이력이 없으면 캐싱된 디펜던시를 가져온다.
name: CI-CD qa
on:
push:
branches: [qa]
env:
S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }}
CODE_DEPLOY_APPLICATION_NAME: ${{ secrets.AWS_CODEDEPLOY_NAME }}
CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: ${{ secrets.AWS_CODEDEPLOY_GROUP_NAME_QA }}
jobs:
# 의존성 빌드 테스트
build:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Cache dependencies
id: cache
uses: actions/cache@v3
with:
path: "**/node_modules"
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: yarn install --immutable --immutable-cache --check-cache
- name: Build
run: yarn build
vitest 테스트 코드 실행
test:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Cache dependencies
id: cache
uses: actions/cache@v3
with:
path: "**/node_modules"
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: yarn install --immutable --immutable-cache --check-cache
- name: Run Vitest
run: yarn vitest
# s3에 zip으로 업로드한 후 codedeploy로 ec2에 배포
deploy:
needs: [build] # build 후에 실행되도록 정의
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Make .env
run: |
touch ./.env
echo "${{ secrets.DOTENV_QA }}" > ./.env
shell: bash
- name: Make zip file
run: zip -r ./$GITHUB_SHA.zip .
shell: bash
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1-node16
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Upload to S3
run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip
- name: Code Deploy
run: |
aws deploy create-deployment \
--deployment-config-name CodeDeployDefault.AllAtOnce \
--application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
--deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
--s3-location bucket=$S3_BUCKET_NAME,bundleType=zip,key=$GITHUB_SHA.zip
run:
needs: deploy # build 후에 실행되도록 정의
runs-on: [mfc-mobile-app-qa] # AWS ./configure에서 사용할 label명
steps:
- uses: actions/checkout@v3
- name: Run deploy.sh
run: |
cd /home/ec2-user/mfc-mobile-app
sudo chmod 777 ./deploy.sh
sudo chmod 777 ./down.sh
./deploy.sh
- main
name: CI-CD main
on:
push:
branches: [main]
env:
S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }}
CODE_DEPLOY_APPLICATION_NAME: ${{ secrets.AWS_CODEDEPLOY_NAME }}
CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: ${{ secrets.AWS_CODEDEPLOY_GROUP_NAME }}
jobs:
# 의존성 빌드 테스트
build:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Cache dependencies
id: cache
uses: actions/cache@v3
with:
path: "**/node_modules"
key: ${{ runner.os }}-node-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node-
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: yarn install --immutable --immutable-cache --check-cache
- name: Build
run: yarn build
# vitest 테스트 코드 실행
test:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Dependencies
run: yarn install --immutable --immutable-cache --check-cache
- name: Run Vitest
run: yarn vitest
# s3에 zip으로 업로드한 후 codedeploy로 ec2에 배포
deploy:
needs: [build, test] # build 후에 실행되도록 정의
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Make .env
run: |
touch ./.env
echo "${{ secrets.DOTENV }}" > ./.env
shell: bash
- name: Make zip file
run: zip -r ./$GITHUB_SHA.zip .
shell: bash
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1-node16
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Upload to S3
run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip
- name: Code Deploy
run: |
aws deploy create-deployment \
--deployment-config-name CodeDeployDefault.AllAtOnce \
--application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
--deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
--s3-location bucket=$S3_BUCKET_NAME,bundleType=zip,key=$GITHUB_SHA.zip
run:
needs: deploy # build 후에 실행되도록 정의
runs-on: [mfc-mobile-app] # AWS ./configure에서 사용할 label명
steps:
- uses: actions/checkout@v3
- name: Run deploy.sh
run: |
cd /home/ec2-user/mfc-mobile-app
sudo chmod 777 ./deploy.sh
sudo chown 777 ./down.sh
./deploy.sh
- deploy.sh
블루-그린을 적용해 봤던지라 주석으로 코드를 남겨놓았다. 프론트에도 한 번 해보고 싶었는데 사실 프론트는 돌아가는 노드 서버에 빌드 파일을 던지면 되는거라 의미있는 삽질은 아니었다. 그래도 궁금증은 해결!
#!/bin/bash
cd /home/ec2-user/mfc-mobile-app
DOCKER_APP_NAME=vue
# 실행중인 blue가 있는지
EXIST_VUE=$(docker ps | grep vue)
# 실행중인 nginx가 있는지
EXIST_NGINX=$(docker ps | grep nginx)
# health_check() {
# RESPONSE=$(curl -s http://127.0.0.1:$2)
# # 헬스 체크
# echo "$3 health check: $IDLE_PORT count: $1... "
# echo "response: $RESPONSE"
# if [ -n "$RESPONSE" ]; then
# echo " down"
# docker-compose down
# docker image prune -af # 사용하지 않는 이미지 삭제
# echo "$3 down complete"
# exit
# fi
# sleep 1
# }
# nginx 콘테이너가 없으면 빌드
if [ -z "$EXIST_NGINX" ]; then
docker-compose -p nginx -f docker-compose.nginx.yml up --build -d
fi
# nginx 콘테이너가 없으면 빌드
if [ -z "$EXIST_VUE" ]; then
echo "docker up"
docker-compose -p vue -f docker-compose.yml up --build -d
echo "Done docker-compose up"
else
echo "build vite"
docker exec -i vue yarn deploy
fi
docker image prune -af # 사용하지 않는 이미지 삭제
- 각 .env 파일은 깃의 환경 변수를 통해서 분기해 주었다.
백엔드는 처음엔 위와 같이 구성했다. 물류 피킹을 하는데 사용하는 앱이므로 쿠버네티스나 EKS는 과하다고 생각 되어 블루 그린 방식으로 구성했다. 한 EC2의 구조이며 여러 EC2가 ALB를 통해서 로드밸런싱 된다. CICD에선 깃랩 + 젠킨스 혹은 Github Actions 구성 둘 중 하나를 고민했는데 깃랩 + 젠킨스는 구성 소요시간이 많이 걸리기도 하고, 리소스가 많이 드므로 Github Actions로 구성을 결정했다.
CodeDeploy의 그룹에서 배포를 여러개 올리면 이전 배포를 기다리는데 시간이 오래 걸려서 앱 안에 그룹도 각각 나눠주었다. 이전 배포가 진행 중이면 가끔 에러가 떠서 배포가 중단 되었는데 그런 문제도 방지되었다. 물론 같은 그룹 안에서 배포를 많이 하면 종종 발생하긴 해서 CICD에 로드밸런싱을 적용한 쿠팡의 게시글이 떠올랐었다. 아직 그 정도 규모로 설계된 프로젝트가 아니기에 적용되어 있진 않지만 배포 시에 문제가 자주 발생하게 된다면 적용할 예정이다.
application.yml, DB 비밀번호 등을 스크립트 안에서 분기해주었다. Github Actions의 환경 변수에 DB 비밀번호를 넣을 때 yml에서 특수 문자들에 제한이 있는 관계로 환경 변수에 ""(더블 쿼터)를 사용했는데 값을 주입할 때 보안상 더블 쿼터를 yml에 넣을 때 에러가 발생했다. 그래서 ''(싱글 쿼터)로 DB 비번을 감싸서 넣는 방법을 이용했더니 해결됐다.
- qa
application.yml, sql 파일 등을 깃 액션에서 주입해주는 방식으로 개발 환경을 분리했다.
name: CI-CD qa
on:
push:
branches: [qa]
env:
DB_PASSWORD: ${{ secrets.DB_PASSWORD_QA }}
S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }}
CODE_DEPLOY_APPLICATION_NAME: ${{ secrets.AWS_CODEDEPLOY_NAME }}
CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: ${{ secrets.AWS_CODEDEPLOY_GROUP_NAME_QA }}
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@master
- name: Set up JDK 19
uses: actions/setup-java@v3
with:
java-version: "19"
distribution: "temurin"
- name: Gradle Caching
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Make .env
run: |
touch ./.env
echo "${{ secrets.DOTENV }}" > ./.env
shell: bash
- name: Make application.yml
run: |
cd ./src/main/resources
touch ./application.yml
echo "${{ secrets.APPLICATION_YML_QA }}" > ./application.yml
shell: bash
- name: Make schema.sql
run: |
mkdir ./src/main/resources/db
cd ./src/main/resources/db
touch ./schema.sql
echo "${{ secrets.SCHEMA_SQL }}" > ./schema.sql
shell: bash
- name: Make data.sql
run: |
cd ./src/main/resources/db
touch ./data.sql
echo "${{ secrets.DATA_SQL }}" > ./data.sql
shell: bash
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: |
./gradlew build
# 수정
- name: Make zip file
run: zip -r ./$GITHUB_SHA.zip .
shell: bash
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1-node16
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Upload to S3
run: aws s3 cp --region $AWS_REGION ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip
# 추가
- name: Code Deploy
run: |
aws deploy create-deployment \
--deployment-config-name CodeDeployDefault.AllAtOnce \
--application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
--deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
--s3-location bucket=$S3_BUCKET_NAME,bundleType=zip,key=$GITHUB_SHA.zip
run:
needs: deploy # build 후에 실행되도록 정의
runs-on: [mfc-mobile-api-qa] # AWS ./configure에서 사용할 label명
steps:
- uses: actions/checkout@v3
- name: Run deploy.sh
run: |
cd /home/ec2-user/mfc-mobile-api
sudo chmod 777 ./deploy.sh
sudo chmod 777 ./down.sh
sudo chmod 777 ./logging.sh
./deploy.sh
- main
name: CI-CD prod
on:
push:
branches: [main]
env:
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }}
CODE_DEPLOY_APPLICATION_NAME: ${{ secrets.AWS_CODEDEPLOY_NAME }}
CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: ${{ secrets.AWS_CODEDEPLOY_GROUP_NAME }}
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout source code
uses: actions/checkout@master
- name: Set up JDK 19
uses: actions/setup-java@v3
with:
java-version: "19"
distribution: "temurin"
- name: Gradle Caching
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Make .env
run: |
touch ./.env
echo "${{ secrets.DOTENV }}" > ./.env
shell: bash
- name: Make application.yml
run: |
cd ./src/main/resources
touch ./application.yml
echo "${{ secrets.APPLICATION_YML }}" > ./application.yml
shell: bash
- name: Make schema.sql
run: |
mkdir ./src/main/resources/db
cd ./src/main/resources/db
touch ./schema.sql
echo "${{ secrets.SCHEMA_SQL }}" > ./schema.sql
shell: bash
- name: Make data.sql
run: |
cd ./src/main/resources/db
touch ./data.sql
echo "${{ secrets.DATA_SQL }}" > ./data.sql
shell: bash
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: |
./gradlew build
# 수정
- name: Make zip file
run: zip -r ./$GITHUB_SHA.zip .
shell: bash
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1-node16
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Upload to S3
run: aws s3 cp --region $AWS_REGION ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip
# 추가
- name: Code Deploy
run: |
aws deploy create-deployment \
--deployment-config-name CodeDeployDefault.AllAtOnce \
--application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
--deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
--s3-location bucket=$S3_BUCKET_NAME,bundleType=zip,key=$GITHUB_SHA.zip
run:
needs: deploy # build 후에 실행되도록 정의
runs-on: [mfc-mobile-api] # AWS ./configure에서 사용할 label명
steps:
- uses: actions/checkout@v3
- name: Run deploy.sh
run: |
cd /home/ec2-user/mfc-mobile-api
sudo chmod 777 ./deploy.sh
sudo chmod 777 ./down.sh
sudo chmod 777 ./logging.sh
./deploy.sh
- deploy.sh
블루-그린 방식이 적용됐다. 블루 업 - 헬스체크 - 그린 다운 방식이다.
#!/bin/bash
cd /home/ec2-user/mfc-mobile-api
DOCKER_APP_NAME=spring
# 실행중인 blue가 있는지
EXIST_BLUE=$(docker ps | grep spring-blue)
# 실행중인 nginx가 있는지
EXIST_NGINX=$(docker ps | grep nginx)
health_check() {
RESPONSE=$(curl -s http://127.0.0.1:$2)
# 헬스 체크
echo "$3 health check: $IDLE_PORT count: $1... "
echo "response: $RESPONSE"
if [ -n "$RESPONSE" ]; then
echo "$3 down"
docker-compose -p ${DOCKER_APP_NAME}-$3 -f docker-compose.green.yml down
docker image prune -af # 사용하지 않는 이미지 삭제
echo "$3 down complete"
exit
fi
sleep 1
}
# nginx 콘테이너가 없으면 빌드
if [ -z "$EXIST_NGINX" ]; then
docker-compose -p nginx -f docker-compose.nginx.yml up --build -d
fi
docker image prune -af # 사용하지 않는 이미지 삭제
# green이 실행중이면 blue up
if [ -z "$EXIST_BLUE" ]; then
echo "blue up"
docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml up -d --build
IDLE_PORT=8082
echo "blue up complete"
for RETRY_COUNT in {1..90}; do
health_check $RETRY_COUNT $IDLE_PORT "green"
done
echo "Failed to health check. Please check docker container is running."
# blue가 실행중이면 green up
else
echo "green up"
docker-compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yml up -d --build
IDLE_PORT=8081
echo "green up complete"
for RETRY_COUNT in {1..90}; do
health_check $RETRY_COUNT $IDLE_PORT "blue"
done
echo "Failed to health check. Please check docker container is running."
fi
스크립트에서 올리는 docker-compose 파일은 아래와 같다.
- docker-compose.blue.yml
# docker-compose.yml
version: "3.9"
#blue
services:
api:
container_name: spring-blue
# restart: always
build: .
ports:
- "8081:8080"
volumes:
- ./logs:/app/logs
environment:
- TZ=Asia/Seoul
- docker-compose.green.yml
# docker-compose.yml
version: "3.9"
#green
services:
api:
container_name: spring-green
# restart: always
build: .
ports:
- "8082:8080"
volumes:
- ./logs:/app/logs
- Dockerfile
FROM openjdk:19
WORKDIR /app
COPY ./build/libs/mfc-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-jar","app.jar"]
- filebeat
# docker-compose.yml
version: "3.9"
services:
filebeat:
container_name: filebeat
build:
context: ./filebeat/
dockerfile: Dockerfile
volumes:
- ./logs:/usr/share/filebeat/logs
파일비트를 이용해서 카프카에 토픽을 전송했다. 스프링의 로그는 logback-spring.xml 파일을 설정해서 이용했으며 로그가 작성되면 아래 설정 파일에 맞게 로그 토픽이 전송된다.
- Dockerfile
FROM docker.elastic.co/beats/filebeat:7.13.4
COPY ./filebeat.yml /usr/share/filebeat/filebeat.yml
USER root
RUN chown root:filebeat /usr/share/filebeat/filebeat.yml
USER filebeat
- filebeat.yml
filebeat.config.modules:
path: ${path.config}/modules.d/*.yml
reload.enabled: false
filebeat.inputs:
- type: log
enabled: true
paths:
- /usr/share/filebeat/logs/access.json
output.kafka:
# kafka 브로커 ip or host
hosts: ["xxx.xxx.xxx.xxx:29092", "xxx.xxx.xxx.xxx:39092"]
# codec.format:
# string: "%{[message]}"
topic: "log"
required_acks: 1
compression: gzip
max_message_bytes: 1000000
카프카를 구성하는 이미지는
이미지를 이용했다. 브로커는 2개만 구성했으며 추가 구성 하고 싶다면 kafka-n 부분을 복사해서 아래에 추가하면 된다. 물론 각 포트를 맞춰줘야 되는건 당연하다. kafka-manager 부분에서
GUI 어드민을 이용했고 이를 통해서 쉽게 kafka 토픽에 대한 대시보드를 보고 설정 값을 바꿀 수 있게 했다.
docker-compose.kafka.yml
version: "3.9"
services:
zookeeper:
container_name: zookeeper
image: confluentinc/cp-zookeeper:latest
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
ports:
- 22181:2181
volumes:
- ./logs/zookeeper/data:/data
- ./logs/zookeeper/datalog:/datalog
kafka-1:
container_name: kafka-1
image: confluentinc/cp-kafka:latest
user: root
depends_on:
- zookeeper
ports:
- 29092:29092
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka-1:9092,PLAINTEXT_HOST://{아이피}:29092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
volumes:
- ./logs/kafka1/data:/var/lib/kafka/data
kafka-2:
container_name: kafka-2
image: confluentinc/cp-kafka:latest
user: root
depends_on:
- zookeeper
ports:
- 39092:39092
environment:
KAFKA_BROKER_ID: 2
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka-2:9092,PLAINTEXT_HOST://{아이피}:39092
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
volumes:
- ./logs/kafka2/data:/var/lib/kafka/data
kafka-manager:
container_name: kafka-manager
image: sheepkiller/kafka-manager
ports:
- 9000:9000
environment:
ZK_HOSTS: zookeeper:2181
depends_on:
- zookeeper
카프카에 다른 서비스들을 연결하기 위해서 카프카 커넥트 플러그인을 설치했다. 설치는 스크립트를 작성해서 편하게 설치 할 수 있게 했다. 필요한 플러그인들을 아래 curl 부분에서 맞춰서 카프카에 보내주면 된다. 아래 예시는 엘라스틱 서치 플러그인을 설치하는 예다.
- connect.sh
#!/bin/bash
FILE="./connect-plugins/confluentinc-kafka-connect-elasticsearch-14.0.3"
if [ ! -e $FILE ]; then
curl -o ./connect-plugins/confluentinc-kafka-connect-elasticsearch-14.0.3.zip https://d1i4a15mxbxib1.cloudfront.net/api/plugins/confluentinc/kafka-connect-elasticsearch/versions/14.0.3/confluentinc-kafka-connect-elasticsearch-14.0.3.zip
unzip ./connect-plugins/confluentinc-kafka-connect-elasticsearch-14.0.3.zip
rm -f ./connect-plugins/confluentinc-kafka-connect-elasticsearch-14.0.3.zip
fi
curl -X POST http://localhost:8083/connectors -H 'Content-Type: application/json' -d \
'{
"name": "elasticsearch-sink",
"config": {
"connector.class": "io.confluent.connect.elasticsearch.ElasticsearchSinkConnector",
"tasks.max": "1",
"topics": "log",
"key.ignore": "true",
"schema.ignore": "true",
"connection.url": "http://{카프카 아이피}:9200",
"type.name": "_doc",
"name": "elasticsearch-sink",
"value.converter": "org.apache.kafka.connect.json.JsonConverter",
"value.converter.schemas.enable": "false"
}
}'
- check.sh
연결된 커텍터들의 목록을 볼 수 있다.
curl -X GET http://localhost:8083/connectors
전체적인 구성은 아래 구조도와 같다.
처음엔 로그 요청을 받는 API 서버, WAS 를 나눌려고 했는데 filebeat, 카프카, 카프카 커넥트를 적용하면서 구조는 아래와 같이 변경되었다.
스프링에서 디비에 직접 접속하는 것이 아닌, kafka에 로그, 디비 토픽을 전송하고 카프카를 구독하고 있는 서비스들에 토픽이 전달되도록 변경되었다. 위 인프라에서 각 서비스들을 직접 핸들링 하는 것 보다 AWS의 서비스를 이용한다면 서비스 안정성, 작업 리소스 관리 등에서 훨씬 큰 이점이 있다. 그래서 앞으로의 인프라 변경 계획은 아래와 같다.
위의 각 파트들 말고도 추천 서버, 테라폼 등의 구성이 더 포함되어 있지만 전체적인 구성이 완성된 다음에 추가로 글을 작성할 예정이다.
'회고' 카테고리의 다른 글
2023년 상반기 회고 (0) | 2024.03.11 |
---|