From a4b1e16204c7d5b89d98469fd85d2d3ca6fb2beb Mon Sep 17 00:00:00 2001 From: yoonseopkim Date: Sat, 21 Dec 2024 21:31:28 +0900 Subject: [PATCH 01/12] =?UTF-8?q?fix:=20=EB=8F=84=EC=BB=A4=ED=97=88?= =?UTF-8?q?=EB=B8=8C->ECR=20=EB=A1=9C=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 +- Jenkinsfile | 269 ++++++++++++++++++++++---------------------- docker-compose.yaml | 4 +- 3 files changed, 139 insertions(+), 136 deletions(-) diff --git a/Dockerfile b/Dockerfile index 60e7ece..fe1d199 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.12-alpine +FROM --platform=linux/amd64 python:3.12-alpine # 작업 디렉토리 설정 WORKDIR /app diff --git a/Jenkinsfile b/Jenkinsfile index a405ecb..7b61a1d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,133 +1,136 @@ -// pipeline { -// agent any -// -// environment { -// // 환경 변수 파일 경로 설정 -// ENV_FILE = '/var/lib/jenkins/environments/.env.ai' -// // Docker 이미지 정보 -// DOCKER_IMAGE = 'aida0/gitfolio_ai:test' -// // AWS 리전 -// AWS_REGION = 'ap-northeast-2' -// } -// -// stages { -// stage('Load Environment Variables') { -// steps { -// script { -// // .env.ai 파일에서 환경 변수 로드 -// def envContent = readFile(ENV_FILE).trim() -// envContent.split('\n').each { line -> -// def (key, value) = line.split('=', 2) -// env."${key}" = value -// } -// } -// } -// } -// -// // Git 저장소 체크아웃 단계 -// stage('Checkout') { -// steps { -// // Git 저장소 URL을 직접 지정하여 체크아웃 -// git branch: 'develop', -// url: 'https://github.com/KTB-Sixmen/gitfolio_AI.git' -// } -// } -// -// stage('Docker Build & Push') { -// steps { -// script { -// // Docker Hub 로그인 -// withCredentials([usernamePassword(credentialsId: 'docker-credentials', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) { -// sh """ -// docker login -u ${DOCKER_USER} -p ${DOCKER_PASS} -// -// # Docker 이미지 빌드 -// docker build \ -// --build-arg OPENAI_API_KEY=${env.OPENAI_API_KEY} \ -// --build-arg GH_TOKEN=${env.GH_TOKEN} \ -// --build-arg HOST=${env.HOST} \ -// --build-arg PORT=${env.PORT} \ -// -t ${DOCKER_IMAGE} . -// -// # Docker 이미지 푸시 -// docker push ${DOCKER_IMAGE} -// """ -// } -// } -// } -// } -// -// stage('Deploy to EC2') { -// steps { -// script { -// withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', -// credentialsId: 'aws-credentials', -// accessKeyVariable: 'AWS_ACCESS_KEY_ID', -// secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) { -// -// // EC2 인스턴스 ID 조회 -// def instanceIds = sh( -// script: """ -// aws ec2 describe-instances \ -// --region ${AWS_REGION} \ -// --filters 'Name=tag:Service,Values=ai' 'Name=instance-state-name,Values=running' \ -// --query 'Reservations[].Instances[].InstanceId' \ -// --output text -// """, -// returnStdout: true -// ).trim() -// -// if (instanceIds) { -// // docker-compose.yaml 파일 인코딩 -// def dockerComposeContent = sh( -// script: "base64 docker-compose.yaml | tr -d '\n'", -// returnStdout: true -// ).trim() -// -// // SSM 명령 실행 - JSON 형식 수정 -// def commandId = sh( -// script: """ -// aws ssm send-command \ -// --instance-ids "${instanceIds}" \ -// --document-name "AWS-RunShellScript" \ -// --comment "Deploying AI Server" \ -// --parameters '{"commands":["cd /home/ec2-user","echo '\\''${dockerComposeContent}'\\'' | base64 -d > docker-compose.yaml","echo '\\''OPENAI_API_KEY=${env.OPENAI_API_KEY}'\\'' > .env","echo '\\''GH_TOKEN=${env.GH_TOKEN}'\\'' >> .env","echo '\\''HOST=${env.HOST}'\\'' >> .env","echo '\\''PORT=${env.PORT}'\\'' >> .env","chmod 600 .env","docker-compose down -v --rmi all","docker-compose pull","docker-compose up -d"]}' \ -// --timeout-seconds 600 \ -// --region ${AWS_REGION} \ -// --output text \ -// --query 'Command.CommandId' -// """, -// returnStdout: true -// ).trim() -// -// // 명령 실행 완료 대기 -// sh """ -// aws ssm wait command-executed \ -// --command-id ${commandId} \ -// --instance-id ${instanceIds} \ -// --region ${AWS_REGION} -// """ -// -// // 실행 결과 확인 -// sh """ -// aws ssm get-command-invocation \ -// --command-id ${commandId} \ -// --instance-id ${instanceIds} \ -// --region ${AWS_REGION} -// """ -// } else { -// error "No running EC2 instances found with the specified tags" -// } -// } -// } -// } -// } -// } -// -// post { -// always { -// // 작업 완료 후 정리 -// cleanWs() -// } -// } -// } \ No newline at end of file +pipeline { + agent any + + environment { + AWS_REGION = 'ap-northeast-2' + ECR_REGISTRY = credentials('ecr-registry') + DISCORD_CI_WEBHOOK = credentials('ai-dev-discord-ci-webhook') + DISCORD_CD_WEBHOOK = credentials('ai-dev-discord-cd-webhook') + DOCKER_TAG = 'dev' + ENV_FILE = '/var/lib/jenkins/environments/.env.ai' + } + + stages { + stage('소스코드 체크아웃') { + steps { + script { + deleteDir() + git branch: 'feature/ai-cicd', + url: 'https://github.com/KTB-Sixmen/gitfolio_AI.git' + } + } + } + + stage('환경 설정') { + steps { + script { + // 환경 변수 파일 복사 + if (fileExists(ENV_FILE)) { + sh """ + cp ${ENV_FILE} .env + echo '환경 파일 복사 완료: ${ENV_FILE}' + """ + } else { + error "환경 파일을 찾을 수 없습니다: ${ENV_FILE}" + } + + // ECR 로그인 + withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', + credentialsId: 'aws-credentials', + accessKeyVariable: 'AWS_ACCESS_KEY_ID', + secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) { + sh """ + aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${ECR_REGISTRY} + echo 'ECR 로그인 완료' + """ + } + } + } + } + + stage('Docker 이미지 빌드 및 푸시') { + steps { + script { + def imageTag = "${ECR_REGISTRY}/gitfolio/ai:${DOCKER_TAG}" + + sh """ + docker build \ + -f Dockerfile \ + -t ${imageTag} \ + --platform linux/amd64 \ + --build-arg OPENAI_API_KEY=${env.OPENAI_API_KEY} \ + --build-arg GH_TOKEN=${env.GH_TOKEN} \ + --build-arg HOST=${env.HOST} \ + --build-arg PORT=${env.PORT} \ + . + + docker push ${imageTag} + """ + } + } + } + + stage('EC2 배포') { + steps { + script { + withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', + credentialsId: 'aws-credentials', + accessKeyVariable: 'AWS_ACCESS_KEY_ID', + secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) { + + def instanceIds = sh( + script: """ + aws ec2 describe-instances \ + --region ${AWS_REGION} \ + --filters 'Name=tag:Service,Values=ai' \ + 'Name=tag:Environment,Values=dev' \ + 'Name=tag:Type,Values=ec2' \ + 'Name=instance-state-name,Values=running' \ + --query 'Reservations[].Instances[].InstanceId' \ + --output text + """, + returnStdout: true + ).trim() + + if (instanceIds) { + sh """ + aws ssm send-command \ + --instance-ids "${instanceIds}" \ + --document-name "AWS-RunShellScript" \ + --comment "AI 서버 배포" \ + --parameters commands=' + cd /home/ec2-user + export AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} + export AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} + export AWS_DEFAULT_REGION=${AWS_REGION} + aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${ECR_REGISTRY} + docker-compose down -v --rmi all + docker builder prune -f --filter until=24h + docker image prune -f + docker-compose pull + docker-compose up -d + ' \ + --timeout-seconds 600 \ + --region ${AWS_REGION} + """ + } else { + error "실행 중인 AI 서비스 EC2 인스턴스를 찾을 수 없습니다." + } + } + } + } + } + } + + post { + always { + script { + sh """ + docker builder prune -f --filter until=24h + docker image prune -f + rm -f .env + """ + } + } + } +} \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 9f37a2a..1d7c194 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,7 +1,7 @@ services: ai: platform: linux/amd64 - image: aida0/gitfolio_ai:test + image: 727646500036.dkr.ecr.ap-northeast-2.amazonaws.com/gitfolio/ai:dev container_name: gitfolio_ai ports: - target: 8000 @@ -11,7 +11,7 @@ services: published: 443 protocol: tcp env_file: - - .env # 현재 디렉토리의 .env 파일을 참조 + - .env networks: - ai From c251685341203a8bb00405242b3e2eae518b4ace Mon Sep 17 00:00:00 2001 From: yoonseopkim Date: Mon, 23 Dec 2024 14:16:27 +0900 Subject: [PATCH 02/12] =?UTF-8?q?fix:=20k8s=20prod=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=84=A4=EC=A0=95=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 6 ++ Jenkinsfile | 292 +++++++++++++++++++++++++++++++++++----------------- 2 files changed, 203 insertions(+), 95 deletions(-) diff --git a/Dockerfile b/Dockerfile index fe1d199..7d2a14b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,12 @@ WORKDIR /app # GitPython 라이브러리가 git 실행파일에 의존해서 도커 컨테이너에 git 을 실행해줘야 한다. RUN apk update && apk add git +# 환경변수 설정 +ENV OPENAI_API_KEY=${OPENAI_API_KEY} +ENV GH_TOKEN=${GH_TOKEN} +ENV HOST=${HOST} +ENV PORT=8000 + # 필요 파일 복사 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt diff --git a/Jenkinsfile b/Jenkinsfile index 7b61a1d..4e73d2c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,135 +2,237 @@ pipeline { agent any environment { + // 환경 설정을 위한 변수들 + ENV_FILE = '/var/lib/jenkins/environments/.env.ai' + DOCKER_IMAGE = 'aida0/gitfolio_ai:prod' // prod 태그로 변경 AWS_REGION = 'ap-northeast-2' - ECR_REGISTRY = credentials('ecr-registry') + + // Discord 웹훅 URL (credentials로 관리하는 것이 더 안전합니다) DISCORD_CI_WEBHOOK = credentials('ai-dev-discord-ci-webhook') DISCORD_CD_WEBHOOK = credentials('ai-dev-discord-cd-webhook') - DOCKER_TAG = 'dev' - ENV_FILE = '/var/lib/jenkins/environments/.env.ai' } stages { - stage('소스코드 체크아웃') { + // 파이프라인 시작 알림 + stage('Pipeline Start Notification') { steps { script { - deleteDir() - git branch: 'feature/ai-cicd', - url: 'https://github.com/KTB-Sixmen/gitfolio_AI.git' + // Discord로 파이프라인 시작 알림 전송 + def message = """ + { + "embeds": [{ + "title": "🚀 파이프라인 시작", + "description": "빌드 #${env.BUILD_NUMBER}가 시작되었습니다.\\n브랜치: ${env.GIT_BRANCH ?: 'develop'}\\n깃트폴리오 AI 서버 빌드 프로세스를 시작합니다.", + "color": 16776960, + "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}" + }] + }""".trim().replaceAll("\n\\s*", " ") + + // Discord로 알림 전송 + sh """ + curl -H "Content-Type: application/json" \ + -d '${message}' \ + ${env.DISCORD_CI_WEBHOOK} + """ } } } - stage('환경 설정') { + // 환경 변수 로드 + stage('Load Environment Variables') { steps { script { - // 환경 변수 파일 복사 - if (fileExists(ENV_FILE)) { - sh """ - cp ${ENV_FILE} .env - echo '환경 파일 복사 완료: ${ENV_FILE}' - """ - } else { - error "환경 파일을 찾을 수 없습니다: ${ENV_FILE}" - } + // Discord로 환경 변수 설정 시작 알림 + def message = """ + { + "embeds": [{ + "title": "⚙️ 환경 변수 설정", + "description": "환경 설정 파일을 로드하고 있습니다...", + "color": 65280, + "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}" + }] + }""".trim().replaceAll("\n\\s*", " ") - // ECR 로그인 - withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', - credentialsId: 'aws-credentials', - accessKeyVariable: 'AWS_ACCESS_KEY_ID', - secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) { - sh """ - aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${ECR_REGISTRY} - echo 'ECR 로그인 완료' - """ + sh """ + curl -H "Content-Type: application/json" \ + -d '${message}' \ + ${env.DISCORD_CI_WEBHOOK} + """ + + // 환경 변수 파일에서 변수들을 읽어와 설정 + def envContent = readFile(ENV_FILE).trim() + envContent.split('\n').each { line -> + if (line.trim()) { + def (key, value) = line.split('=', 2) + env."${key}" = value + } } } } } - stage('Docker 이미지 빌드 및 푸시') { + // 소스 코드 체크아웃 + stage('Checkout') { steps { script { - def imageTag = "${ECR_REGISTRY}/gitfolio/ai:${DOCKER_TAG}" + // Discord로 체크아웃 시작 알림 + def message = """ + { + "embeds": [{ + "title": "📥 소스 코드 체크아웃", + "description": "깃허브 저장소에서 소스 코드를 가져오고 있습니다.\\n저장소: KTB-Sixmen/gitfolio_AI\\n브랜치: develop", + "color": 65280, + "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}" + }] + }""".trim().replaceAll("\n\\s*", " ") sh """ - docker build \ - -f Dockerfile \ - -t ${imageTag} \ - --platform linux/amd64 \ - --build-arg OPENAI_API_KEY=${env.OPENAI_API_KEY} \ - --build-arg GH_TOKEN=${env.GH_TOKEN} \ - --build-arg HOST=${env.HOST} \ - --build-arg PORT=${env.PORT} \ - . - - docker push ${imageTag} + curl -H "Content-Type: application/json" \ + -d '${message}' \ + ${env.DISCORD_CI_WEBHOOK} """ + + // Git 체크아웃 수행 + git branch: 'develop', + url: 'https://github.com/KTB-Sixmen/gitfolio_AI.git' } } } - stage('EC2 배포') { + // Docker 이미지 빌드 + stage('Docker Build & Push') { steps { script { - withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', - credentialsId: 'aws-credentials', - accessKeyVariable: 'AWS_ACCESS_KEY_ID', - secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) { - - def instanceIds = sh( - script: """ - aws ec2 describe-instances \ - --region ${AWS_REGION} \ - --filters 'Name=tag:Service,Values=ai' \ - 'Name=tag:Environment,Values=dev' \ - 'Name=tag:Type,Values=ec2' \ - 'Name=instance-state-name,Values=running' \ - --query 'Reservations[].Instances[].InstanceId' \ - --output text - """, - returnStdout: true - ).trim() - - if (instanceIds) { - sh """ - aws ssm send-command \ - --instance-ids "${instanceIds}" \ - --document-name "AWS-RunShellScript" \ - --comment "AI 서버 배포" \ - --parameters commands=' - cd /home/ec2-user - export AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} - export AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} - export AWS_DEFAULT_REGION=${AWS_REGION} - aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${ECR_REGISTRY} - docker-compose down -v --rmi all - docker builder prune -f --filter until=24h - docker image prune -f - docker-compose pull - docker-compose up -d - ' \ - --timeout-seconds 600 \ - --region ${AWS_REGION} - """ - } else { - error "실행 중인 AI 서비스 EC2 인스턴스를 찾을 수 없습니다." - } + // Discord로 Docker 빌드 시작 알림 + def message = """ + { + "embeds": [{ + "title": "🐳 도커 이미지 빌드", + "description": "도커 이미지 빌드 작업을 시작합니다.\\n이미지: ${DOCKER_IMAGE}", + "color": 65280, + "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}" + }] + }""".trim().replaceAll("\n\\s*", " ") + + sh """ + curl -H "Content-Type: application/json" \ + -d '${message}' \ + ${env.DISCORD_CI_WEBHOOK} + """ + + // Docker 빌드 및 푸시 + withCredentials([usernamePassword(credentialsId: 'docker-credentials', + usernameVariable: 'DOCKER_USER', + passwordVariable: 'DOCKER_PASS')]) { + // Docker Hub 로그인 + sh "echo ${DOCKER_PASS} | docker login -u ${DOCKER_USER} --password-stdin" + + // Dockerfile 내용 생성 - 환경변수를 이미지에 포함 + writeFile file: 'Dockerfile', text: """ + FROM node:18 + + WORKDIR /app + + COPY package*.json ./ + + RUN npm install + + COPY . . + + # 환경변수를 이미지에 포함 + ENV OPENAI_API_KEY=${env.OPENAI_API_KEY} \\ + GH_TOKEN=${env.GH_TOKEN} \\ + HOST=${env.HOST} \\ + PORT=${env.PORT} + + EXPOSE ${env.PORT} + + CMD ["npm", "start"] + """ + + // Docker 이미지 빌드 및 푸시 + sh """ + docker build -t ${DOCKER_IMAGE} . + docker push ${DOCKER_IMAGE} + """ } } } } - } + } - post { - always { - script { - sh """ - docker builder prune -f --filter until=24h - docker image prune -f - rm -f .env - """ + // 파이프라인 완료 후 작업 + post { + success { + script { + // 빌드 성공 알림 전송 + def message = """ + { + "embeds": [{ + "title": "✅ 빌드 성공", + "description": "빌드 #${env.BUILD_NUMBER}가 성공적으로 완료되었습니다.\\n깃트폴리오 AI 서버 빌드가 정상적으로 완료되었습니다.", + "color": 65280, + "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}", + "fields": [ + { + "name": "빌드 번호", + "value": "#${env.BUILD_NUMBER}", + "inline": true + }, + { + "name": "이미지", + "value": "${DOCKER_IMAGE}", + "inline": true + } + ] + }] + }""".trim().replaceAll("\n\\s*", " ") + + sh """ + curl -H "Content-Type: application/json" \ + -d '${message}' \ + ${env.DISCORD_CI_WEBHOOK} + """ + } + } + + failure { + script { + // 빌드 실패 알림 전송 + def message = """ + { + "embeds": [{ + "title": "❌ 빌드 실패", + "description": "빌드 #${env.BUILD_NUMBER}가 실패했습니다.\\nJenkins 로그를 확인해주세요.", + "color": 16711680, + "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}", + "fields": [ + { + "name": "실패 단계", + "value": "${currentBuild.result}", + "inline": true + } + ] + }] + }""".trim().replaceAll("\n\\s*", " ") + + sh """ + curl -H "Content-Type: application/json" \ + -d '${message}' \ + ${env.DISCORD_CI_WEBHOOK} + """ + } + } + + always { + // 작업 디렉토리 정리 + cleanWs() + + // Docker 이미지 정리 + sh """ + docker image prune -f + docker builder prune -f --filter until=24h + """ + } } - } - } -} \ No newline at end of file + } \ No newline at end of file From 728bfba37711407debd4cff0b3745909f5a789c2 Mon Sep 17 00:00:00 2001 From: yoonseopkim Date: Mon, 23 Dec 2024 14:18:26 +0900 Subject: [PATCH 03/12] =?UTF-8?q?fix:=20k8s=20prod=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=84=A4=EC=A0=95=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 4e73d2c..42b55ed 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -22,7 +22,7 @@ pipeline { { "embeds": [{ "title": "🚀 파이프라인 시작", - "description": "빌드 #${env.BUILD_NUMBER}가 시작되었습니다.\\n브랜치: ${env.GIT_BRANCH ?: 'develop'}\\n깃트폴리오 AI 서버 빌드 프로세스를 시작합니다.", + "description": "빌드 #${env.BUILD_NUMBER}가 시작되었습니다.\\n브랜치: ${env.GIT_BRANCH ?: 'feature/cicd'}\\n깃트폴리오 AI 서버 빌드 프로세스를 시작합니다.", "color": 16776960, "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}" }] @@ -93,7 +93,7 @@ pipeline { """ // Git 체크아웃 수행 - git branch: 'develop', + git branch: 'feature/cicd', url: 'https://github.com/KTB-Sixmen/gitfolio_AI.git' } } From 97c380a505cb7921f2ccbfdd946c402d492f2e94 Mon Sep 17 00:00:00 2001 From: yoonseopkim Date: Mon, 23 Dec 2024 14:27:38 +0900 Subject: [PATCH 04/12] =?UTF-8?q?fix:=20k8s=20prod=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=84=A4=EC=A0=95=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 10 +- Jenkinsfile | 322 +++++++++++++++++++++++----------------------------- 2 files changed, 153 insertions(+), 179 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7d2a14b..cb0630a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,22 @@ FROM --platform=linux/amd64 python:3.12-alpine +# build-arg 선언 - 빌드 시 전달받을 환경변수들 +ARG OPENAI_API_KEY +ARG GH_TOKEN +ARG HOST +ARG PORT + # 작업 디렉토리 설정 WORKDIR /app # GitPython 라이브러리가 git 실행파일에 의존해서 도커 컨테이너에 git 을 실행해줘야 한다. RUN apk update && apk add git -# 환경변수 설정 +# ARG로 받은 값들을 ENV로 설정하여 이미지에 포함 ENV OPENAI_API_KEY=${OPENAI_API_KEY} ENV GH_TOKEN=${GH_TOKEN} ENV HOST=${HOST} -ENV PORT=8000 +ENV PORT=${PORT} # 필요 파일 복사 COPY requirements.txt . diff --git a/Jenkinsfile b/Jenkinsfile index 42b55ed..bc34ade 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,14 +2,11 @@ pipeline { agent any environment { - // 환경 설정을 위한 변수들 - ENV_FILE = '/var/lib/jenkins/environments/.env.ai' - DOCKER_IMAGE = 'aida0/gitfolio_ai:prod' // prod 태그로 변경 AWS_REGION = 'ap-northeast-2' - - // Discord 웹훅 URL (credentials로 관리하는 것이 더 안전합니다) + ECR_REGISTRY = credentials('ecr-registry') DISCORD_CI_WEBHOOK = credentials('ai-dev-discord-ci-webhook') - DISCORD_CD_WEBHOOK = credentials('ai-dev-discord-cd-webhook') + DOCKER_IMAGE = 'aida0/gitfolio_ai:prod' // prod 태그로 변경 + ENV_FILE = '/var/lib/jenkins/environments/.env.ai' } stages { @@ -17,18 +14,16 @@ pipeline { stage('Pipeline Start Notification') { steps { script { - // Discord로 파이프라인 시작 알림 전송 def message = """ { "embeds": [{ "title": "🚀 파이프라인 시작", - "description": "빌드 #${env.BUILD_NUMBER}가 시작되었습니다.\\n브랜치: ${env.GIT_BRANCH ?: 'feature/cicd'}\\n깃트폴리오 AI 서버 빌드 프로세스를 시작합니다.", + "description": "빌드 #${env.BUILD_NUMBER}가 시작되었습니다.\\n브랜치: feature/cicd\\n깃트폴리오 AI 서버 빌드 프로세스를 시작합니다.", "color": 16776960, "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}" }] }""".trim().replaceAll("\n\\s*", " ") - // Discord로 알림 전송 sh """ curl -H "Content-Type: application/json" \ -d '${message}' \ @@ -38,49 +33,14 @@ pipeline { } } - // 환경 변수 로드 - stage('Load Environment Variables') { + stage('소스코드 체크아웃') { steps { script { - // Discord로 환경 변수 설정 시작 알림 - def message = """ - { - "embeds": [{ - "title": "⚙️ 환경 변수 설정", - "description": "환경 설정 파일을 로드하고 있습니다...", - "color": 65280, - "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}" - }] - }""".trim().replaceAll("\n\\s*", " ") - - sh """ - curl -H "Content-Type: application/json" \ - -d '${message}' \ - ${env.DISCORD_CI_WEBHOOK} - """ - - // 환경 변수 파일에서 변수들을 읽어와 설정 - def envContent = readFile(ENV_FILE).trim() - envContent.split('\n').each { line -> - if (line.trim()) { - def (key, value) = line.split('=', 2) - env."${key}" = value - } - } - } - } - } - - // 소스 코드 체크아웃 - stage('Checkout') { - steps { - script { - // Discord로 체크아웃 시작 알림 def message = """ { "embeds": [{ "title": "📥 소스 코드 체크아웃", - "description": "깃허브 저장소에서 소스 코드를 가져오고 있습니다.\\n저장소: KTB-Sixmen/gitfolio_AI\\n브랜치: develop", + "description": "깃허브 저장소에서 소스 코드를 가져오고 있습니다.\\n저장소: KTB-Sixmen/gitfolio_AI\\n브랜치: feature/cicd", "color": 65280, "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}" }] @@ -92,147 +52,155 @@ pipeline { ${env.DISCORD_CI_WEBHOOK} """ - // Git 체크아웃 수행 + deleteDir() git branch: 'feature/cicd', url: 'https://github.com/KTB-Sixmen/gitfolio_AI.git' } } } - - // Docker 이미지 빌드 - stage('Docker Build & Push') { - steps { - script { - // Discord로 Docker 빌드 시작 알림 - def message = """ - { - "embeds": [{ - "title": "🐳 도커 이미지 빌드", - "description": "도커 이미지 빌드 작업을 시작합니다.\\n이미지: ${DOCKER_IMAGE}", - "color": 65280, - "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}" - }] - }""".trim().replaceAll("\n\\s*", " ") - - sh """ - curl -H "Content-Type: application/json" \ - -d '${message}' \ - ${env.DISCORD_CI_WEBHOOK} - """ - - // Docker 빌드 및 푸시 - withCredentials([usernamePassword(credentialsId: 'docker-credentials', - usernameVariable: 'DOCKER_USER', - passwordVariable: 'DOCKER_PASS')]) { - // Docker Hub 로그인 - sh "echo ${DOCKER_PASS} | docker login -u ${DOCKER_USER} --password-stdin" - - // Dockerfile 내용 생성 - 환경변수를 이미지에 포함 - writeFile file: 'Dockerfile', text: """ - FROM node:18 - - WORKDIR /app - - COPY package*.json ./ - - RUN npm install - - COPY . . - - # 환경변수를 이미지에 포함 - ENV OPENAI_API_KEY=${env.OPENAI_API_KEY} \\ - GH_TOKEN=${env.GH_TOKEN} \\ - HOST=${env.HOST} \\ - PORT=${env.PORT} - - EXPOSE ${env.PORT} - - CMD ["npm", "start"] - """ - - // Docker 이미지 빌드 및 푸시 - sh """ - docker build -t ${DOCKER_IMAGE} . - docker push ${DOCKER_IMAGE} - """ + stage('환경 설정') { + steps { + script { + def message = """ + { + "embeds": [{ + "title": "⚙️ 환경 변수 설정", + "description": "환경 설정 파일을 로드하고 있습니다...", + "color": 65280, + "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}" + }] + }""".trim().replaceAll("\n\\s*", " ") + + sh """ + curl -H "Content-Type: application/json" \ + -d '${message}' \ + ${env.DISCORD_CI_WEBHOOK} + """ + + // 환경 변수 파일에서 변수들을 읽어와 설정 + def envContent = readFile(ENV_FILE).trim() + envContent.split('\n').each { line -> + if (line.trim()) { + def (key, value) = line.split('=', 2) + env."${key}" = value + } + } + } } } - } - } - } - // 파이프라인 완료 후 작업 - post { - success { - script { - // 빌드 성공 알림 전송 - def message = """ - { - "embeds": [{ - "title": "✅ 빌드 성공", - "description": "빌드 #${env.BUILD_NUMBER}가 성공적으로 완료되었습니다.\\n깃트폴리오 AI 서버 빌드가 정상적으로 완료되었습니다.", - "color": 65280, - "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}", - "fields": [ - { - "name": "빌드 번호", - "value": "#${env.BUILD_NUMBER}", - "inline": true - }, - { - "name": "이미지", - "value": "${DOCKER_IMAGE}", - "inline": true - } - ] - }] - }""".trim().replaceAll("\n\\s*", " ") - - sh """ - curl -H "Content-Type: application/json" \ - -d '${message}' \ - ${env.DISCORD_CI_WEBHOOK} - """ + stage('Docker 이미지 빌드 및 푸시') { + steps { + script { + def message = """ + { + "embeds": [{ + "title": "🐳 도커 이미지 빌드", + "description": "도커 이미지 빌드 작업을 시작합니다.\\n이미지: ${DOCKER_IMAGE}", + "color": 65280, + "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}" + }] + }""".trim().replaceAll("\n\\s*", " ") + + sh """ + curl -H "Content-Type: application/json" \ + -d '${message}' \ + ${env.DISCORD_CI_WEBHOOK} + """ + + // Docker Hub 로그인 및 빌드/푸시 + withCredentials([usernamePassword(credentialsId: 'docker-credentials', + usernameVariable: 'DOCKER_USER', + passwordVariable: 'DOCKER_PASS')]) { + sh "echo ${DOCKER_PASS} | docker login -u ${DOCKER_USER} --password-stdin" + + // Docker 이미지 빌드 - 환경변수를 build-arg로 전달 + sh """ + docker build \ + -f Dockerfile \ + -t ${DOCKER_IMAGE} \ + --platform linux/amd64 \ + --build-arg OPENAI_API_KEY=${env.OPENAI_API_KEY} \ + --build-arg GH_TOKEN=${env.GH_TOKEN} \ + --build-arg HOST=${env.HOST} \ + --build-arg PORT=${env.PORT} \ + . + + docker push ${DOCKER_IMAGE} + """ + } + } } } - - failure { - script { - // 빌드 실패 알림 전송 - def message = """ - { - "embeds": [{ - "title": "❌ 빌드 실패", - "description": "빌드 #${env.BUILD_NUMBER}가 실패했습니다.\\nJenkins 로그를 확인해주세요.", - "color": 16711680, - "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}", - "fields": [ - { - "name": "실패 단계", - "value": "${currentBuild.result}", - "inline": true - } - ] - }] - }""".trim().replaceAll("\n\\s*", " ") - - sh """ - curl -H "Content-Type: application/json" \ - -d '${message}' \ - ${env.DISCORD_CI_WEBHOOK} - """ - } } - always { - // 작업 디렉토리 정리 - cleanWs() + // 파이프라인 완료 후 작업 + post { + success { + script { + def message = """ + { + "embeds": [{ + "title": "✅ 빌드 성공", + "description": "빌드 #${env.BUILD_NUMBER}가 성공적으로 완료되었습니다.\\n깃트폴리오 AI 서버 빌드가 정상적으로 완료되었습니다.", + "color": 65280, + "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}", + "fields": [ + { + "name": "빌드 번호", + "value": "#${env.BUILD_NUMBER}", + "inline": true + }, + { + "name": "이미지", + "value": "${DOCKER_IMAGE}", + "inline": true + } + ] + }] + }""".trim().replaceAll("\n\\s*", " ") + + sh """ + curl -H "Content-Type: application/json" \ + -d '${message}' \ + ${env.DISCORD_CI_WEBHOOK} + """ + } + } - // Docker 이미지 정리 - sh """ - docker image prune -f - docker builder prune -f --filter until=24h - """ - } - } - } \ No newline at end of file + failure { + script { + def message = """ + { + "embeds": [{ + "title": "❌ 빌드 실패", + "description": "빌드 #${env.BUILD_NUMBER}가 실패했습니다.\\nJenkins 로그를 확인해주세요.", + "color": 16711680, + "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}", + "fields": [ + { + "name": "실패 단계", + "value": "${currentBuild.result}", + "inline": true + } + ] + }] + }""".trim().replaceAll("\n\\s*", " ") + + sh """ + curl -H "Content-Type: application/json" \ + -d '${message}' \ + ${env.DISCORD_CI_WEBHOOK} + """ + } + } + + always { + cleanWs() + sh """ + docker builder prune -f --filter until=24h + docker image prune -f + """ + } + } + } \ No newline at end of file From 64d5d6aee96b19e1d35eac060d2931bbafc45756 Mon Sep 17 00:00:00 2001 From: yoonseopkim Date: Mon, 23 Dec 2024 14:47:00 +0900 Subject: [PATCH 05/12] =?UTF-8?q?fix:=20k8s=20prod=20=ED=99=98=EA=B2=BD?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EA=B2=8C=20=EC=84=A4=EC=A0=95=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 8 +++++--- docker-compose.yaml | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index bc34ade..6716cc5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -18,7 +18,9 @@ pipeline { { "embeds": [{ "title": "🚀 파이프라인 시작", - "description": "빌드 #${env.BUILD_NUMBER}가 시작되었습니다.\\n브랜치: feature/cicd\\n깃트폴리오 AI 서버 빌드 프로세스를 시작합니다.", + echo "ECR Registry: ${ECR_REGISTRY}", + echo "Docker Image Tag: ${DOCKER_IMAGE}", + "description": "빌드 #${env.BUILD_NUMBER}가 시작되었습니다.\\n브랜치: feature/ai-cicd\\n깃트폴리오 AI 서버 빌드 프로세스를 시작합니다.", "color": 16776960, "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}" }] @@ -40,7 +42,7 @@ pipeline { { "embeds": [{ "title": "📥 소스 코드 체크아웃", - "description": "깃허브 저장소에서 소스 코드를 가져오고 있습니다.\\n저장소: KTB-Sixmen/gitfolio_AI\\n브랜치: feature/cicd", + "description": "깃허브 저장소에서 소스 코드를 가져오고 있습니다.\\n저장소: KTB-Sixmen/gitfolio_AI\\n브랜치: feature/ai-cicd, "color": 65280, "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}" }] @@ -53,7 +55,7 @@ pipeline { """ deleteDir() - git branch: 'feature/cicd', + git branch: 'feature/ai-cicd', url: 'https://github.com/KTB-Sixmen/gitfolio_AI.git' } } diff --git a/docker-compose.yaml b/docker-compose.yaml index 1d7c194..4ec8d43 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,7 +1,7 @@ services: ai: platform: linux/amd64 - image: 727646500036.dkr.ecr.ap-northeast-2.amazonaws.com/gitfolio/ai:dev + image: 727646500036.dkr.ecr.ap-northeast-2.amazonaws.com/gitfolio/ai:prod container_name: gitfolio_ai ports: - target: 8000 From 9846fd79862445f193c673338da917d04e11872e Mon Sep 17 00:00:00 2001 From: yoonseopkim Date: Tue, 24 Dec 2024 15:17:30 +0900 Subject: [PATCH 06/12] =?UTF-8?q?fix:=20=EC=A0=81=EC=9A=A9=20=EB=B8=8C?= =?UTF-8?q?=EB=9E=9C=EC=B9=98=20develop=20=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 261 ++++++++++++++++------------------------------------ 1 file changed, 80 insertions(+), 181 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 6716cc5..94f979e 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,204 +5,103 @@ pipeline { AWS_REGION = 'ap-northeast-2' ECR_REGISTRY = credentials('ecr-registry') DISCORD_CI_WEBHOOK = credentials('ai-dev-discord-ci-webhook') - DOCKER_IMAGE = 'aida0/gitfolio_ai:prod' // prod 태그로 변경 + DOCKER_TAG = 'prod' // dev -> prod로 변경 ENV_FILE = '/var/lib/jenkins/environments/.env.ai' } stages { - // 파이프라인 시작 알림 - stage('Pipeline Start Notification') { - steps { - script { - def message = """ - { - "embeds": [{ - "title": "🚀 파이프라인 시작", - echo "ECR Registry: ${ECR_REGISTRY}", - echo "Docker Image Tag: ${DOCKER_IMAGE}", - "description": "빌드 #${env.BUILD_NUMBER}가 시작되었습니다.\\n브랜치: feature/ai-cicd\\n깃트폴리오 AI 서버 빌드 프로세스를 시작합니다.", - "color": 16776960, - "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}" - }] - }""".trim().replaceAll("\n\\s*", " ") - - sh """ - curl -H "Content-Type: application/json" \ - -d '${message}' \ - ${env.DISCORD_CI_WEBHOOK} - """ - } - } - } - stage('소스코드 체크아웃') { steps { script { - def message = """ - { - "embeds": [{ - "title": "📥 소스 코드 체크아웃", - "description": "깃허브 저장소에서 소스 코드를 가져오고 있습니다.\\n저장소: KTB-Sixmen/gitfolio_AI\\n브랜치: feature/ai-cicd, - "color": 65280, - "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}" - }] - }""".trim().replaceAll("\n\\s*", " ") - - sh """ - curl -H "Content-Type: application/json" \ - -d '${message}' \ - ${env.DISCORD_CI_WEBHOOK} - """ - deleteDir() - git branch: 'feature/ai-cicd', + git branch: 'develop', url: 'https://github.com/KTB-Sixmen/gitfolio_AI.git' } } } - stage('환경 설정') { - steps { - script { - def message = """ - { - "embeds": [{ - "title": "⚙️ 환경 변수 설정", - "description": "환경 설정 파일을 로드하고 있습니다...", - "color": 65280, - "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}" - }] - }""".trim().replaceAll("\n\\s*", " ") - sh """ - curl -H "Content-Type: application/json" \ - -d '${message}' \ - ${env.DISCORD_CI_WEBHOOK} - """ - - // 환경 변수 파일에서 변수들을 읽어와 설정 - def envContent = readFile(ENV_FILE).trim() - envContent.split('\n').each { line -> - if (line.trim()) { - def (key, value) = line.split('=', 2) - env."${key}" = value + stage('환경 설정') { + steps { + script { + // 환경 변수 파일 복사 + if (fileExists(ENV_FILE)) { + sh """ + cp ${ENV_FILE} .env + echo '환경 파일 복사 완료: ${ENV_FILE}' + + # .env 파일에서 환경변수를 추출하여 Jenkins 환경에 설정 + export OPENAI_API_KEY=\$(grep OPENAI_API_KEY .env | cut -d '=' -f2) + export GH_TOKEN=\$(grep GH_TOKEN .env | cut -d '=' -f2) + export HOST=\$(grep HOST .env | cut -d '=' -f2) + export PORT=\$(grep PORT .env | cut -d '=' -f2) + + # 환경변수를 Jenkins 환경에 설정 + echo "OPENAI_API_KEY=\${OPENAI_API_KEY}" >> env.properties + echo "GH_TOKEN=\${GH_TOKEN}" >> env.properties + echo "HOST=\${HOST}" >> env.properties + echo "PORT=\${PORT}" >> env.properties + """ + + // env.properties 파일을 Jenkins 환경변수로 로드 + def props = readProperties file: 'env.properties' + env.OPENAI_API_KEY = props.OPENAI_API_KEY + env.GH_TOKEN = props.GH_TOKEN + env.HOST = props.HOST + env.PORT = props.PORT + } else { + error "환경 파일을 찾을 수 없습니다: ${ENV_FILE}" } - } - } - } - } - - stage('Docker 이미지 빌드 및 푸시') { - steps { - script { - def message = """ - { - "embeds": [{ - "title": "🐳 도커 이미지 빌드", - "description": "도커 이미지 빌드 작업을 시작합니다.\\n이미지: ${DOCKER_IMAGE}", - "color": 65280, - "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}" - }] - }""".trim().replaceAll("\n\\s*", " ") - sh """ - curl -H "Content-Type: application/json" \ - -d '${message}' \ - ${env.DISCORD_CI_WEBHOOK} - """ - - // Docker Hub 로그인 및 빌드/푸시 - withCredentials([usernamePassword(credentialsId: 'docker-credentials', - usernameVariable: 'DOCKER_USER', - passwordVariable: 'DOCKER_PASS')]) { - sh "echo ${DOCKER_PASS} | docker login -u ${DOCKER_USER} --password-stdin" - - // Docker 이미지 빌드 - 환경변수를 build-arg로 전달 - sh """ - docker build \ - -f Dockerfile \ - -t ${DOCKER_IMAGE} \ - --platform linux/amd64 \ - --build-arg OPENAI_API_KEY=${env.OPENAI_API_KEY} \ - --build-arg GH_TOKEN=${env.GH_TOKEN} \ - --build-arg HOST=${env.HOST} \ - --build-arg PORT=${env.PORT} \ - . - - docker push ${DOCKER_IMAGE} - """ - } - } + // ECR 로그인 + withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', + credentialsId: 'aws-credentials', + accessKeyVariable: 'AWS_ACCESS_KEY_ID', + secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) { + sh """ + aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${ECR_REGISTRY} + echo 'ECR 로그인 완료' + """ } } - } - - // 파이프라인 완료 후 작업 - post { - success { - script { - def message = """ - { - "embeds": [{ - "title": "✅ 빌드 성공", - "description": "빌드 #${env.BUILD_NUMBER}가 성공적으로 완료되었습니다.\\n깃트폴리오 AI 서버 빌드가 정상적으로 완료되었습니다.", - "color": 65280, - "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}", - "fields": [ - { - "name": "빌드 번호", - "value": "#${env.BUILD_NUMBER}", - "inline": true - }, - { - "name": "이미지", - "value": "${DOCKER_IMAGE}", - "inline": true - } - ] - }] - }""".trim().replaceAll("\n\\s*", " ") - - sh """ - curl -H "Content-Type: application/json" \ - -d '${message}' \ - ${env.DISCORD_CI_WEBHOOK} - """ - } - } + } + } - failure { - script { - def message = """ - { - "embeds": [{ - "title": "❌ 빌드 실패", - "description": "빌드 #${env.BUILD_NUMBER}가 실패했습니다.\\nJenkins 로그를 확인해주세요.", - "color": 16711680, - "timestamp": "${new Date().format("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", TimeZone.getTimeZone('UTC'))}", - "fields": [ - { - "name": "실패 단계", - "value": "${currentBuild.result}", - "inline": true - } - ] - }] - }""".trim().replaceAll("\n\\s*", " ") + stage('Docker 이미지 빌드 및 푸시') { + steps { + script { + def imageTag = "${ECR_REGISTRY}/gitfolio/ai:${DOCKER_TAG}" - sh """ - curl -H "Content-Type: application/json" \ - -d '${message}' \ - ${env.DISCORD_CI_WEBHOOK} - """ - } - } + sh """ + docker build \ + -f Dockerfile \ + -t ${imageTag} \ + --platform linux/amd64 \ + --build-arg OPENAI_API_KEY=${env.OPENAI_API_KEY} \ + --build-arg GH_TOKEN=${env.GH_TOKEN} \ + --build-arg HOST=${env.HOST} \ + --build-arg PORT=${env.PORT} \ + . + + # 빌드된 이미지의 환경변수 확인 + echo "===== 이미지 환경변수 확인 =====" + docker run --rm ${imageTag} env | grep -E "OPENAI_API_KEY|GH_TOKEN|HOST|PORT" + + docker push ${imageTag} + """ + } + } + } + } - always { - cleanWs() - sh """ - docker builder prune -f --filter until=24h - docker image prune -f - """ - } - } - } \ No newline at end of file + post { + always { + script { + sh """ + docker builder prune -f --filter until=24h + docker image prune -f + rm -f .env + """ + } + } + } +} \ No newline at end of file From 22ce8c45876aff65e793aba7cc7c21e2a80f9927 Mon Sep 17 00:00:00 2001 From: yoonseopkim Date: Tue, 24 Dec 2024 23:09:49 +0900 Subject: [PATCH 07/12] =?UTF-8?q?fix:=20dev=20=ED=99=98=EA=B2=BD=EC=97=90?= =?UTF-8?q?=20=EB=A7=9E=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 25 ++++++++- Jenkinsfile-prod | 128 ++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yaml | 2 +- 3 files changed, 152 insertions(+), 3 deletions(-) create mode 100644 Jenkinsfile-prod diff --git a/Jenkinsfile b/Jenkinsfile index 94f979e..cf4bb47 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,7 +5,7 @@ pipeline { AWS_REGION = 'ap-northeast-2' ECR_REGISTRY = credentials('ecr-registry') DISCORD_CI_WEBHOOK = credentials('ai-dev-discord-ci-webhook') - DOCKER_TAG = 'prod' // dev -> prod로 변경 + DOCKER_TAG = 'dev' // dev -> prod로 변경 ENV_FILE = '/var/lib/jenkins/environments/.env.ai' } @@ -14,7 +14,7 @@ pipeline { steps { script { deleteDir() - git branch: 'develop', + git branch: 'feature/ai-cicd', url: 'https://github.com/KTB-Sixmen/gitfolio_AI.git' } } @@ -103,5 +103,26 @@ pipeline { """ } } + success { + discordSend description: "Dev AI 빌드 및 배포 성공", + footer: "Jenkins Pipeline Success", + link: env.BUILD_URL, + result: currentBuild.currentResult, + title: JOB_NAME, + webhookURL: DISCORD_CI_WEBHOOK + } + + failure { + discordSend description: "Dev AI 빌드 및 배포 실패", + footer: "Jenkins Pipeline Failed", + link: env.BUILD_URL, + result: currentBuild.currentResult, + title: JOB_NAME, + webhookURL: DISCORD_CI_WEBHOOK + } + } + + + } \ No newline at end of file diff --git a/Jenkinsfile-prod b/Jenkinsfile-prod new file mode 100644 index 0000000..0933b3b --- /dev/null +++ b/Jenkinsfile-prod @@ -0,0 +1,128 @@ +pipeline { + agent any + + environment { + AWS_REGION = 'ap-northeast-2' + ECR_REGISTRY = credentials('ecr-registry') + DISCORD_CI_WEBHOOK = credentials('ai-dev-discord-ci-webhook') + DOCKER_TAG = 'prod' // dev -> prod로 변경 + ENV_FILE = '/var/lib/jenkins/environments/.env.ai' + } + + stages { + stage('소스코드 체크아웃') { + steps { + script { + deleteDir() + git branch: 'develop', + url: 'https://github.com/KTB-Sixmen/gitfolio_AI.git' + } + } + } + + stage('환경 설정') { + steps { + script { + // 환경 변수 파일 복사 + if (fileExists(ENV_FILE)) { + sh """ + cp ${ENV_FILE} .env + echo '환경 파일 복사 완료: ${ENV_FILE}' + + # .env 파일에서 환경변수를 추출하여 Jenkins 환경에 설정 + export OPENAI_API_KEY=\$(grep OPENAI_API_KEY .env | cut -d '=' -f2) + export GH_TOKEN=\$(grep GH_TOKEN .env | cut -d '=' -f2) + export HOST=\$(grep HOST .env | cut -d '=' -f2) + export PORT=\$(grep PORT .env | cut -d '=' -f2) + + # 환경변수를 Jenkins 환경에 설정 + echo "OPENAI_API_KEY=\${OPENAI_API_KEY}" >> env.properties + echo "GH_TOKEN=\${GH_TOKEN}" >> env.properties + echo "HOST=\${HOST}" >> env.properties + echo "PORT=\${PORT}" >> env.properties + """ + + // env.properties 파일을 Jenkins 환경변수로 로드 + def props = readProperties file: 'env.properties' + env.OPENAI_API_KEY = props.OPENAI_API_KEY + env.GH_TOKEN = props.GH_TOKEN + env.HOST = props.HOST + env.PORT = props.PORT + } else { + error "환경 파일을 찾을 수 없습니다: ${ENV_FILE}" + } + + // ECR 로그인 + withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', + credentialsId: 'aws-credentials', + accessKeyVariable: 'AWS_ACCESS_KEY_ID', + secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) { + sh """ + aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${ECR_REGISTRY} + echo 'ECR 로그인 완료' + """ + } + } + } + } + + stage('Docker 이미지 빌드 및 푸시') { + steps { + script { + def imageTag = "${ECR_REGISTRY}/gitfolio/ai:${DOCKER_TAG}" + + sh """ + docker build \ + -f Dockerfile \ + -t ${imageTag} \ + --platform linux/amd64 \ + --build-arg OPENAI_API_KEY=${env.OPENAI_API_KEY} \ + --build-arg GH_TOKEN=${env.GH_TOKEN} \ + --build-arg HOST=${env.HOST} \ + --build-arg PORT=${env.PORT} \ + . + + # 빌드된 이미지의 환경변수 확인 + echo "===== 이미지 환경변수 확인 =====" + docker run --rm ${imageTag} env | grep -E "OPENAI_API_KEY|GH_TOKEN|HOST|PORT" + + docker push ${imageTag} + """ + } + } + } + } + + post { + always { + script { + sh """ + docker builder prune -f --filter until=24h + docker image prune -f + rm -f .env + """ + } + } + success { + discordSend description: "AI 서비스 CI 파이프라인 성공", + footer: "Jenkins Pipeline Success", + link: env.BUILD_URL, + result: currentBuild.currentResult, + title: JOB_NAME, + webhookURL: DISCORD_CI_WEBHOOK + } + + failure { + discordSend description: "AI 서비스 CI 파이프라인 실패", + footer: "Jenkins Pipeline Failed", + link: env.BUILD_URL, + result: currentBuild.currentResult, + title: JOB_NAME, + webhookURL: DISCORD_CI_WEBHOOK + } + + } + + + +} \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 4ec8d43..1d7c194 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,7 +1,7 @@ services: ai: platform: linux/amd64 - image: 727646500036.dkr.ecr.ap-northeast-2.amazonaws.com/gitfolio/ai:prod + image: 727646500036.dkr.ecr.ap-northeast-2.amazonaws.com/gitfolio/ai:dev container_name: gitfolio_ai ports: - target: 8000 From f831622b3ad1d5de9bcfea6d6bd13a1508db7c39 Mon Sep 17 00:00:00 2001 From: yoonseopkim Date: Tue, 24 Dec 2024 23:17:15 +0900 Subject: [PATCH 08/12] =?UTF-8?q?fix:=20=EB=8F=84=EC=BB=A4=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/Dockerfile b/Dockerfile index cb0630a..60e7ece 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,4 @@ -FROM --platform=linux/amd64 python:3.12-alpine - -# build-arg 선언 - 빌드 시 전달받을 환경변수들 -ARG OPENAI_API_KEY -ARG GH_TOKEN -ARG HOST -ARG PORT +FROM python:3.12-alpine # 작업 디렉토리 설정 WORKDIR /app @@ -12,12 +6,6 @@ WORKDIR /app # GitPython 라이브러리가 git 실행파일에 의존해서 도커 컨테이너에 git 을 실행해줘야 한다. RUN apk update && apk add git -# ARG로 받은 값들을 ENV로 설정하여 이미지에 포함 -ENV OPENAI_API_KEY=${OPENAI_API_KEY} -ENV GH_TOKEN=${GH_TOKEN} -ENV HOST=${HOST} -ENV PORT=${PORT} - # 필요 파일 복사 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt From e3c3c46d72b7eb500834501eafc495c52fd653d9 Mon Sep 17 00:00:00 2001 From: yoonseopkim Date: Tue, 24 Dec 2024 23:35:23 +0900 Subject: [PATCH 09/12] =?UTF-8?q?fix:=20=EB=8F=84=EC=BB=A4=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=88=98=EC=A0=95=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 12 +++++ Jenkinsfile | 126 ++++++++++++++++++++++++++++++++-------------------- 2 files changed, 90 insertions(+), 48 deletions(-) diff --git a/Dockerfile b/Dockerfile index 60e7ece..22cade6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,17 @@ FROM python:3.12-alpine +# 빌드 인자 선언 +ARG OPENAI_API_KEY +ARG GH_TOKEN +ARG HOST +ARG PORT + +# 환경변수 설정 +ENV OPENAI_API_KEY=$OPENAI_API_KEY +ENV GH_TOKEN=$GH_TOKEN +ENV HOST=$HOST +ENV PORT=$PORT + # 작업 디렉토리 설정 WORKDIR /app diff --git a/Jenkinsfile b/Jenkinsfile index cf4bb47..a198ce5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -5,7 +5,8 @@ pipeline { AWS_REGION = 'ap-northeast-2' ECR_REGISTRY = credentials('ecr-registry') DISCORD_CI_WEBHOOK = credentials('ai-dev-discord-ci-webhook') - DOCKER_TAG = 'dev' // dev -> prod로 변경 + DISCORD_CD_WEBHOOK = credentials('ai-dev-discord-cd-webhook') + DOCKER_TAG = 'dev' ENV_FILE = '/var/lib/jenkins/environments/.env.ai' } @@ -25,32 +26,13 @@ pipeline { script { // 환경 변수 파일 복사 if (fileExists(ENV_FILE)) { - sh """ - cp ${ENV_FILE} .env - echo '환경 파일 복사 완료: ${ENV_FILE}' - - # .env 파일에서 환경변수를 추출하여 Jenkins 환경에 설정 - export OPENAI_API_KEY=\$(grep OPENAI_API_KEY .env | cut -d '=' -f2) - export GH_TOKEN=\$(grep GH_TOKEN .env | cut -d '=' -f2) - export HOST=\$(grep HOST .env | cut -d '=' -f2) - export PORT=\$(grep PORT .env | cut -d '=' -f2) - - # 환경변수를 Jenkins 환경에 설정 - echo "OPENAI_API_KEY=\${OPENAI_API_KEY}" >> env.properties - echo "GH_TOKEN=\${GH_TOKEN}" >> env.properties - echo "HOST=\${HOST}" >> env.properties - echo "PORT=\${PORT}" >> env.properties - """ - - // env.properties 파일을 Jenkins 환경변수로 로드 - def props = readProperties file: 'env.properties' - env.OPENAI_API_KEY = props.OPENAI_API_KEY - env.GH_TOKEN = props.GH_TOKEN - env.HOST = props.HOST - env.PORT = props.PORT - } else { - error "환경 파일을 찾을 수 없습니다: ${ENV_FILE}" - } + sh """ + cp ${ENV_FILE} .env + echo '환경 파일 복사 완료: ${ENV_FILE}' + """ + } else { + error "환경 파일을 찾을 수 없습니다: ${ENV_FILE}" + } // ECR 로그인 withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', @@ -82,15 +64,80 @@ pipeline { --build-arg PORT=${env.PORT} \ . - # 빌드된 이미지의 환경변수 확인 - echo "===== 이미지 환경변수 확인 =====" - docker run --rm ${imageTag} env | grep -E "OPENAI_API_KEY|GH_TOKEN|HOST|PORT" - docker push ${imageTag} """ } } } + + stage('EC2 배포') { + steps { + script { + withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', + credentialsId: 'aws-credentials', + accessKeyVariable: 'AWS_ACCESS_KEY_ID', + secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) { + + def instanceIds = sh( + script: """ + aws ec2 describe-instances \ + --region ${AWS_REGION} \ + --filters 'Name=tag:Service,Values=ai' \ + 'Name=tag:Environment,Values=dev' \ + 'Name=tag:Type,Values=ec2' \ + 'Name=instance-state-name,Values=running' \ + --query 'Reservations[].Instances[].InstanceId' \ + --output text + """, + returnStdout: true + ).trim() + + if (instanceIds) { + // 먼저 .env 파일을 EC2로 복사하는 명령을 추가합니다 + sh """ + aws ssm send-command \ + --instance-ids "${instanceIds}" \ + --document-name "AWS-RunShellScript" \ + --comment "환경 파일 복사" \ + --parameters commands=' + cd /home/ec2-user + cat > .env << 'EOL' + $(cat ${ENV_FILE}) + EOL + chmod 600 .env + ' \ + --timeout-seconds 600 \ + --region ${AWS_REGION} + """ + + if (instanceIds) { + sh """ + aws ssm send-command \ + --instance-ids "${instanceIds}" \ + --document-name "AWS-RunShellScript" \ + --comment "AI 서버 배포" \ + --parameters commands=' + cd /home/ec2-user + export AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} + export AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} + export AWS_DEFAULT_REGION=${AWS_REGION} + aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${ECR_REGISTRY} + docker-compose down -v --rmi all + docker builder prune -f --filter until=24h + docker image prune -f + docker-compose pull + docker-compose up -d + ' \ + --timeout-seconds 600 \ + --region ${AWS_REGION} + """ + } else { + error "실행 중인 AI 서비스 EC2 인스턴스를 찾을 수 없습니다." + } + } + } + } + } } post { @@ -103,23 +150,6 @@ pipeline { """ } } - success { - discordSend description: "Dev AI 빌드 및 배포 성공", - footer: "Jenkins Pipeline Success", - link: env.BUILD_URL, - result: currentBuild.currentResult, - title: JOB_NAME, - webhookURL: DISCORD_CI_WEBHOOK - } - - failure { - discordSend description: "Dev AI 빌드 및 배포 실패", - footer: "Jenkins Pipeline Failed", - link: env.BUILD_URL, - result: currentBuild.currentResult, - title: JOB_NAME, - webhookURL: DISCORD_CI_WEBHOOK - } } From 17d09a71ec5a744823f5d9e72e81e7d4edf09187 Mon Sep 17 00:00:00 2001 From: yoonseopkim Date: Wed, 25 Dec 2024 00:08:17 +0900 Subject: [PATCH 10/12] =?UTF-8?q?fix:=20=EB=8F=84=EC=BB=A4=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=88=98=EC=A0=95=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 4 -- Jenkinsfile | 171 +++++++++++++++++++++++----------------------------- 2 files changed, 74 insertions(+), 101 deletions(-) diff --git a/Dockerfile b/Dockerfile index 22cade6..91b404a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,13 @@ FROM python:3.12-alpine - -# 빌드 인자 선언 ARG OPENAI_API_KEY ARG GH_TOKEN ARG HOST ARG PORT -# 환경변수 설정 ENV OPENAI_API_KEY=$OPENAI_API_KEY ENV GH_TOKEN=$GH_TOKEN ENV HOST=$HOST ENV PORT=$PORT - # 작업 디렉토리 설정 WORKDIR /app diff --git a/Jenkinsfile b/Jenkinsfile index a198ce5..546cbe5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,75 +2,64 @@ pipeline { agent any environment { - AWS_REGION = 'ap-northeast-2' - ECR_REGISTRY = credentials('ecr-registry') - DISCORD_CI_WEBHOOK = credentials('ai-dev-discord-ci-webhook') - DISCORD_CD_WEBHOOK = credentials('ai-dev-discord-cd-webhook') - DOCKER_TAG = 'dev' + // 환경 변수 파일 경로 설정 ENV_FILE = '/var/lib/jenkins/environments/.env.ai' + // Docker 이미지 정보 + DOCKER_IMAGE = '727646500036.dkr.ecr.ap-northeast-2.amazonaws.com/gitfolio/ai:dev' + // AWS 리전 + AWS_REGION = 'ap-northeast-2' } stages { - stage('소스코드 체크아웃') { + stage('Load Environment Variables') { steps { script { - deleteDir() - git branch: 'feature/ai-cicd', - url: 'https://github.com/KTB-Sixmen/gitfolio_AI.git' + // .env.ai 파일에서 환경 변수 로드 + def envContent = readFile(ENV_FILE).trim() + envContent.split('\n').each { line -> + def (key, value) = line.split('=', 2) + env."${key}" = value + } } } } - stage('환경 설정') { - steps { - script { - // 환경 변수 파일 복사 - if (fileExists(ENV_FILE)) { - sh """ - cp ${ENV_FILE} .env - echo '환경 파일 복사 완료: ${ENV_FILE}' - """ - } else { - error "환경 파일을 찾을 수 없습니다: ${ENV_FILE}" - } - - // ECR 로그인 - withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', - credentialsId: 'aws-credentials', - accessKeyVariable: 'AWS_ACCESS_KEY_ID', - secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) { - sh """ - aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${ECR_REGISTRY} - echo 'ECR 로그인 완료' - """ + // Git 저장소 체크아웃 단계 + stage('Checkout') { + steps { + // Git 저장소 URL을 직접 지정하여 체크아웃 + git branch: 'feature/ai-cicd', + url: 'https://github.com/KTB-Sixmen/gitfolio_AI.git' } } - } - } - stage('Docker 이미지 빌드 및 푸시') { + stage('Docker Build & Push') { steps { script { - def imageTag = "${ECR_REGISTRY}/gitfolio/ai:${DOCKER_TAG}" - - sh """ - docker build \ - -f Dockerfile \ - -t ${imageTag} \ - --platform linux/amd64 \ - --build-arg OPENAI_API_KEY=${env.OPENAI_API_KEY} \ - --build-arg GH_TOKEN=${env.GH_TOKEN} \ - --build-arg HOST=${env.HOST} \ - --build-arg PORT=${env.PORT} \ - . - - docker push ${imageTag} - """ + // Docker Hub 로그인 + withCredentials([usernamePassword(credentialsId: 'docker-credentials', + usernameVariable: 'DOCKER_USER', + passwordVariable: 'DOCKER_PASS')]) { + sh """ + sh "echo ${DOCKER_PASS} | docker login -u ${DOCKER_USER} --password-stdin" + + # Docker 이미지 빌드 + docker build \ + --build-arg OPENAI_API_KEY=${env.OPENAI_API_KEY} \ + --build-arg GH_TOKEN=${env.GH_TOKEN} \ + --build-arg HOST=${env.HOST} \ + --build-arg PORT=${env.PORT} \ + -t ${DOCKER_IMAGE} . + + # Docker 이미지 푸시 + docker push ${DOCKER_IMAGE} + """ + } } } } - stage('EC2 배포') { + stage('Deploy to EC2') { steps { script { withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', @@ -78,14 +67,12 @@ pipeline { accessKeyVariable: 'AWS_ACCESS_KEY_ID', secretKeyVariable: 'AWS_SECRET_ACCESS_KEY']]) { + // EC2 인스턴스 ID 조회 def instanceIds = sh( script: """ aws ec2 describe-instances \ --region ${AWS_REGION} \ - --filters 'Name=tag:Service,Values=ai' \ - 'Name=tag:Environment,Values=dev' \ - 'Name=tag:Type,Values=ec2' \ - 'Name=instance-state-name,Values=running' \ + --filters 'Name=tag:Service,Values=ai' 'Name=instance-state-name,Values=running' \ --query 'Reservations[].Instances[].InstanceId' \ --output text """, @@ -93,46 +80,45 @@ pipeline { ).trim() if (instanceIds) { - // 먼저 .env 파일을 EC2로 복사하는 명령을 추가합니다 + // docker-compose.yaml 파일 인코딩 + def dockerComposeContent = sh( + script: "base64 docker-compose.yaml | tr -d '\n'", + returnStdout: true + ).trim() + + // SSM 명령 실행 - JSON 형식 수정 + def commandId = sh( + script: """ + aws ssm send-command \ + --instance-ids "${instanceIds}" \ + --document-name "AWS-RunShellScript" \ + --comment "Deploying AI Server" \ + --parameters '{"commands":["cd /home/ec2-user","echo '\\''${dockerComposeContent}'\\'' | base64 -d > docker-compose.yaml","echo '\\''OPENAI_API_KEY=${env.OPENAI_API_KEY}'\\'' > .env","echo '\\''GH_TOKEN=${env.GH_TOKEN}'\\'' >> .env","echo '\\''HOST=${env.HOST}'\\'' >> .env","echo '\\''PORT=${env.PORT}'\\'' >> .env","chmod 600 .env","docker-compose down -v --rmi all","docker-compose pull","docker-compose up -d"]}' \ + --timeout-seconds 600 \ + --region ${AWS_REGION} \ + --output text \ + --query 'Command.CommandId' + """, + returnStdout: true + ).trim() + + // 명령 실행 완료 대기 sh """ - aws ssm send-command \ - --instance-ids "${instanceIds}" \ - --document-name "AWS-RunShellScript" \ - --comment "환경 파일 복사" \ - --parameters commands=' - cd /home/ec2-user - cat > .env << 'EOL' - $(cat ${ENV_FILE}) - EOL - chmod 600 .env - ' \ - --timeout-seconds 600 \ + aws ssm wait command-executed \ + --command-id ${commandId} \ + --instance-id ${instanceIds} \ --region ${AWS_REGION} """ - if (instanceIds) { + // 실행 결과 확인 sh """ - aws ssm send-command \ - --instance-ids "${instanceIds}" \ - --document-name "AWS-RunShellScript" \ - --comment "AI 서버 배포" \ - --parameters commands=' - cd /home/ec2-user - export AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} - export AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} - export AWS_DEFAULT_REGION=${AWS_REGION} - aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${ECR_REGISTRY} - docker-compose down -v --rmi all - docker builder prune -f --filter until=24h - docker image prune -f - docker-compose pull - docker-compose up -d - ' \ - --timeout-seconds 600 \ + aws ssm get-command-invocation \ + --command-id ${commandId} \ + --instance-id ${instanceIds} \ --region ${AWS_REGION} """ } else { - error "실행 중인 AI 서비스 EC2 인스턴스를 찾을 수 없습니다." + error "No running EC2 instances found with the specified tags" } } } @@ -142,17 +128,8 @@ pipeline { post { always { - script { - sh """ - docker builder prune -f --filter until=24h - docker image prune -f - rm -f .env - """ - } + // 작업 완료 후 정리 + cleanWs() } - } - - - } \ No newline at end of file From 18b60297db84127a3f67d8d37dd1ed1a4278035c Mon Sep 17 00:00:00 2001 From: yoonseopkim Date: Wed, 25 Dec 2024 00:11:18 +0900 Subject: [PATCH 11/12] =?UTF-8?q?fix:=20=EB=8F=84=EC=BB=A4=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=20=EC=88=98=EC=A0=95=204?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docker-compose.yaml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 1d7c194..1c78f43 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,8 +1,15 @@ +version: "3.8" + services: ai: platform: linux/amd64 image: 727646500036.dkr.ecr.ap-northeast-2.amazonaws.com/gitfolio/ai:dev container_name: gitfolio_ai + + # (1) .env 파일로 환경변수 주입 + env_file: + - .env + ports: - target: 8000 published: 80 @@ -10,8 +17,7 @@ services: - target: 8000 published: 443 protocol: tcp - env_file: - - .env + networks: - ai From e81b13691003b6f3334719cc62b396e528105b5a Mon Sep 17 00:00:00 2001 From: yoonseopkim Date: Thu, 26 Dec 2024 00:57:35 +0900 Subject: [PATCH 12/12] =?UTF-8?q?fix:=20prod=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Jenkinsfile | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 546cbe5..2d0d1d9 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -24,36 +24,34 @@ pipeline { } } - // Git 저장소 체크아웃 단계 - stage('Checkout') { - steps { - // Git 저장소 URL을 직접 지정하여 체크아웃 - git branch: 'feature/ai-cicd', - url: 'https://github.com/KTB-Sixmen/gitfolio_AI.git' - } - } + stage('Checkout') { + steps { + // Git 저장소 URL을 직접 지정하여 체크아웃 + git branch: 'develop', + url: 'https://github.com/KTB-Sixmen/gitfolio_AI.git' + } + } stage('Docker Build & Push') { steps { script { - // Docker Hub 로그인 withCredentials([usernamePassword(credentialsId: 'docker-credentials', - usernameVariable: 'DOCKER_USER', - passwordVariable: 'DOCKER_PASS')]) { - sh """ - sh "echo ${DOCKER_PASS} | docker login -u ${DOCKER_USER} --password-stdin" + usernameVariable: 'DOCKER_USER', + passwordVariable: 'DOCKER_PASS')]) { + sh ''' + echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin # Docker 이미지 빌드 docker build \ - --build-arg OPENAI_API_KEY=${env.OPENAI_API_KEY} \ - --build-arg GH_TOKEN=${env.GH_TOKEN} \ - --build-arg HOST=${env.HOST} \ - --build-arg PORT=${env.PORT} \ + --build-arg OPENAI_API_KEY="$OPENAI_API_KEY" \ + --build-arg GH_TOKEN="$GH_TOKEN" \ + --build-arg HOST="$HOST" \ + --build-arg PORT="$PORT" \ -t ${DOCKER_IMAGE} . # Docker 이미지 푸시 docker push ${DOCKER_IMAGE} - """ + ''' } } }