들어가며
현재 봄봄 프로젝트에서는 CodePipeline을 이용해서 배포 자동화를 해두었다. 배포 파이프라인은 다음과 같이 구성되어 있다.
- main 브랜치에서 커밋을 푸시한다.
- CodePipeline의 파이프라인이 생성된다.
- 연결한 GitHub Branch에서 코드를 가져온다.
- AWS Code Build를 이용해 코드를 빌드한다.
- S3에 빌드 결과물을 업로드한다.
자세한 파이프라인 구현은 AWS로 React 배포 자동화 구축하기 (feat. S3, CloudFront, CodeBuild, CodePipeline) 에서 확인할 수 있습니다.
CodePipeline으로 자동화를 해두어서 이제 특정 브랜치에서 커밋을 푸시하면 자동으로 배포가 된다. 그런데 배포가 잘 되었는지, 배포가 언제 되었는지는 알 수가 없는 거다. 그래서 커밋을 푸시한 이후 AWS CodePipeline에 들어가서 새로고침을 하며 ‘배포가 되었나..’ 확인하는 나와 우리 팀원들의 모습을 볼 수 있었다. 매 번 이런 과정이 반복되는 모습을 지켜보며 상당한 병목이라는 생각이 들었고, ‘배포가 완료되면 알림으로 알려줄 수는 없을까?’하는 생각에 알림 봇 개발을 시작하게 되었다.
설계
이미 AWS CodePipeline을 사용하고 있으니, 이 파이프라인에 한 단계를 추가하는 방식으로 접근했다. 배포를 마치고 슬랙에 알림을 주는 과정을 추가하면 되는 것이다. 전체적인 흐름은 이렇게 설계했다.
- AWS CodePipeline에서 배포를 한다.
- 배포가 완료되면 AWS Lambda를 호출한다.
- AWS Lambda는 Slack Webhook을 통해 알림 메시지를 전달한다.
Lambda를 선택한 이유는 간단한 알림 로직을 구현하기에 서버리스 아키텍처가 적합했기 때문이다. 서버를 따로 관리할 필요도 없고 비용도 거의 안 든다.
구현
1. AWS Lambda 구현
- 핸들러 작성
먼저 Lambda의 진입점이 되는 핸들러를 작성했다.
import { CodePipelineClient, PutJobSuccessResultCommand, PutJobFailureResultCommand } from "@aws-sdk/client-codepipeline";
export const handler = async (event) => {
const jobId = event["CodePipeline.job"].id;
const cp = new CodePipelineClient({ region: "ap-northeast-2" });
await cp.send(new PutJobSuccessResultCommand({ jobId }));
await sendSlackNotification()
};
여기서 중요한 포인트는 CodePipeline에 작업 성공을 알려주는 부분이다. CodePipeline에서 Lambda를 호출하면 event 객체에 파이프라인 정보가 담겨 온다. 여기서 jobId를 꺼내서 PutJobSuccessResultCommand를 통해 "정상적으로 실행했어요"라고 CodePipeline에 알려줘야 한다. 이 과정을 빠뜨리면 파이프라인이 계속 대기 상태로 남아있게 되니 주의해야 한다.
region은 CodePipeline을 생성한 지역으로 설정하면 된다. 우리는 서울 리전(ap-northeast-2)을 사용하고 있어서 해당 값으로 설정했다.
- 슬랙 알림 함수 구현
이제 본격적으로 슬랙에 알림을 보내는 함수를 만들어보자.
async function sendSlackNotification() {
const payload = ..
await fetch(SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
}
로직은 정말 간단하다. 슬랙 웹훅 URL로 POST 요청만 보내면 끝이다. 슬랙 웹훅을 만드는 방법은 1) 슬랙 봇 만들기 글을 참고하면 된다.
웹훅을 간단히 설명하자면, 웹훅은 슬랙이 제공하는 고유한 URL이다. 이 URL로 정해진 형식의 JSON을 POST로 보내기만 하면 자동으로 슬랙 채널에 메시지가 전송된다. 별도의 인증 과정이 필요 없고, URL만 알면 누구나 메시지를 보낼 수 있어서 편하다.
- 메시지 포맷 설정
여기서 중요한 건 payload의 구조다. 슬랙 메시지를 예쁘게 꾸미기 위해서는 Block Kit이라는 슬랙의 메시지 구성 시스템을 사용해야 한다.
const payload = {
text: "✅ 배포 완료",
blocks: [
{
type: "header",
text: {
type: "plain_text",
text: "✅ 배포 완료",
emoji: true
}
},
{
type: "section",
text: {
type: "mrkdwn",
text: `🔗 *배포 URL:* <${deploymentUrl}|${deploymentUrl}>`
}
},
{
type: "context",
elements: [
{
type: "mrkdwn",
text: `배포 시간: ${new Date().toLocaleString('ko-KR', { timeZone: 'Asia/Seoul' })}`
}
]
},
]
};
여기서 text 필드는 알림이 왔을 때 미리보기로 보여줄 메시지다. 슬랙 데스크톱 앱이나 모바일에서 알림을 받으면 이 텍스트가 먼저 표시된다.
blocks는 실제 슬랙 채널에 렌더링될 메시지의 상세 내용이다. 블록 타입을 조합하면 메시지를 더 풍성하게 꾸민다. 위 코드에서는 헤더, 본문, 그리고 부가 정보를 담은 컨텍스트 블록으로 구성했다.

특히 mrkdwn 타입을 사용하면 마크다운 문법으로 텍스트를 꾸밀 수 있다. *텍스트*로 볼드 처리를 하거나, <URL|표시할 텍스트> 형식으로 링크를 만들 수 있다. 더 자세한 내용은 슬랙 공식문서에서 확인할 수 있다.
처음에는 슬랙 Block Kit이 복잡해 보였는데, Block Kit Builder라는 공식 도구를 사용하면 GUI로 메시지를 디자인하고 바로 코드를 생성해준다. 이걸 사용하면 훨씬 쉽게 원하는 형태의 메시지를 만들 수 있다.
- 배포 전 테스트
Lambda를 작성했다면 실제 배포 전에 꼭 테스트해보는 걸 추천한다. AWS Lambda 콘솔에서 "Test" 버튼을 눌러 미리 실행해볼 수 있다. 다만 테스트 시에는 event["CodePipeline.job"]가 없어서 에러가 발생할 것이다. 테스트할 때는 이 부분을 주석 처리하거나, 테스트용 이벤트를 직접 만들어서 사용하면 된다.
// 테스트 시 사용할 수 있는 더미 이벤트
const testEvent = {
"CodePipeline.job": {
"id": "test-job-id-123",
"data": {
"actionConfiguration": {
"configuration": {
"UserParameters": JSON.stringify({ environment: "production" })
}
}
}
}
};
2. AWS CodePipeline에 Lambda 단계 추가
이제 기존 CodePipeline에 슬랙 알림을 보내는 단계를 추가해보자.
위 이미지와 같이 Deploy Stage 다음에 Notify Stage를 추가할 것이다.
- CodePipeline 콘솔에서 해당 파이프라인을 선택하고 우측 상단의 "편집" 버튼을 클릭한다.
- Deploy Stage 아래쪽에 있는 "스테이지 추가" 버튼을 클릭한다.
- 스테이지 이름으로 "Notify"를 입력한다.
- 새로 생성된 Notify 스테이지 안에 "작업 추가" 버튼을 클릭한다.
- 작업 이름을 입력하고(예: NotifySlack), 작업 공급자에서 "AWS Lambda"를 선택한다.
여기서 한 가지 주의할 점이 있다. Lambda를 선택할 때 "배포"와 "호출" 두 가지 옵션이 있는데, 반드시 "호출"을 선택해야 한다. "배포"는 Lambda 함수 자체를 배포하는 옵션이고, 우리는 이미 배포된 Lambda 함수를 실행만 하면 되기 때문이다.
- 함수 이름에서 앞서 생성한 Lambda 함수를 선택한다.
- 입력 아티팩트는 선택하지 않아도 된다. 우리는 파이프라인의 산출물을 Lambda에 전달할 필요가 없기 때문이다.
3. 환경 분리하기
실무에서는 보통 개발 서버와 운영 서버를 별도로 운영한다. 각 환경마다 별도의 Lambda를 만들 수도 있지만, 그러면 관리 포인트가 두 배가 된다. 코드 수정이 필요할 때마다 두 곳을 다 고쳐야 한다.
그래서 하나의 Lambda를 쓰되, 환경 정보를 파라미터로 전달하기로 했다. CodePipeline의 사용자 파라미터 기능을 쓰면 된다.
- CodePipeline의 Notify 단계에서 방금 추가한 Lambda 작업을 선택한다.
- "편집" 버튼을 클릭한다.
- 사용자 파라미터 필드에 JSON 형식으로 환경 정보를 입력한다.
{"environment": "production"}
혹은 개발 서버라면
{"environment": "development"}
- 이렇게 설정한 파라미터는 Lambda의 event 객체를 통해 전달된다.
export const handler = async (event) => {
const userParameters = JSON.parse(
event["CodePipeline.job"]?.data?.actionConfiguration?.configuration?.UserParameters
);
const { environment } = userParameters;
// 환경에 따라 다른 처리
const webhookUrl = environment === 'production'
? PRODUCTION_WEBHOOK_URL
: DEVELOPMENT_WEBHOOK_URL;
const deploymentUrl = environment === 'production'
? 'https://production.bombom.com'
: 'https://dev.bombom.com';
// ... 나머지 로직
};
이렇게 하면 하나의 Lambda로 여러 환경을 처리할 수 있다. 심지어 나중에 스테이징 환경이 추가되더라도 Lambda 코드는 수정할 필요 없이 파라미터만 추가하면 된다.
결과
이제, CodePipeline에서 배포를 완료하면 아래와 같이 슬랙으로 알림 메시지가 전송된다.
개발 팁
CodePipeline 특정 Stage만 재실행하기
Lambda를 개발하다 보면 알림 기능만 여러 번 테스트해야 할 때가 있다. 그런데 CodePipeline은 기본적으로 특정 Stage만 재실행하는 기능을 제공하지 않는다. 파이프라인 전체를 처음부터 다시 돌려야 하는데, 이러면 빌드 시간까지 포함해서 매번 몇 분씩 기다려야 한다.
이럴 때 사용할 수 있는 꼼수가 있다. Notify Stage를 의도적으로 실패시키는 것이다.
Lambda 코드에서 PutJobSuccessResultCommand를 호출하지 않으면 CodePipeline은 Lambda로부터 응답을 받지 못한다. 이 상태에서 일정 시간이 지나면 타임아웃이 발생하는데, 기다리기 싫다면 CodePipeline 콘솔에서 "실행 중지" 버튼을 눌러 강제로 실패 처리할 수 있다.
export const handler = async (event) => {
// 테스트 중에는 이 부분을 주석 처리
// const jobId = event["CodePipeline.job"].id;
// const cp = new CodePipelineClient({ region: "ap-northeast-2" });
// await cp.send(new PutJobSuccessResultCommand({ jobId }));
await sendSlackNotification(); // 알림만 테스트
};
이렇게 하면 알림은 정상적으로 발송되지만 파이프라인은 실패 상태가 된다. 그러면 해당 Stage에 "재시도" 버튼이 활성화되고, 이 버튼을 클릭하면 Notify Stage만 다시 실행할 수 있다.
실패한 단계만 재시도할 수 있게 되면 개발 속도가 정말 빨라진다. 빌드나 배포 과정을 거치지 않고 알림 로직만 빠르게 반복 테스트할 수 있기 때문이다.
AWS CodePipeline 디버깅하기
CodePipeline에서 Lambda로 전달되는 event 객체의 구조를 파악하는 게 처음엔 까다로울 수 있다. AWS 공식 문서를 뒤져볼 수도 있지만, 더 확실한 방법은 실제 데이터를 직접 확인하는 것이다. 웹 개발할 때 console.log()를 찍듯이, Lambda에서도 비슷한 방법을 사용할 수 있다. event 객체를 그대로 슬랙으로 전송해보는 것이다.
export const handler = async (event) => {
await fetch(SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: "디버그 정보",
blocks: [{
type: "section",
text: {
type: "mrkdwn",
text: "```" + JSON.stringify(event, null, 2) + "```"
}
}]
})
});
};
이렇게 하면 슬랙 채널에 event 객체의 전체 구조가 포맷팅되어 출력된다. 거기서 필요한 값이 어느 경로에 있는지 확인하면 된다.
위 값을 복사해서 JavaScript 환경에 붙여넣으면 아래와 같이 구조화된 형태로 볼 수 있다.
여기서 내가 원하는 값을 찾으면 된다.
물론 공식문서를 찾아보면 어떤 형태로 값이 오는지 알 수도 있을 것이다. 하지만, 그것보다는 위와 같이 실제로 오는 값을 확인하는 것이 실제 데이터이기 떄문에 더 확실한 값이라 할 수 있다.
CloudWatch Logs 활용하기
Lambda는 기본적으로 모든 출력을 CloudWatch Logs에 기록한다. console.log()를 사용하면 CloudWatch에서 확인할 수 있다.
export const handler = async (event) => {
console.log('event:', JSON.stringify(event, null, 2));
const userParameters = event["CodePipeline.job"]?.data?.actionConfiguration?.configuration?.UserParameters;
console.log('User parameters:', userParameters);
// ... 나머지 로직
};
CloudWatch Logs는 Lambda 콘솔의 "모니터링" 탭에서 바로 접근할 수 있다. 로그를 보면 Lambda가 실행될 때마다 어떤 데이터를 받았는지 상세하게 확인할 수 있다.
개인적으로는 개발 초기에는 슬랙으로 빠르게 확인하고, 나중에는 CloudWatch를 사용하는 편이다. CloudWatch가 더 체계적으로 로그를 관리할 수 있고, 검색 기능도 강력하기 때문이다.
트러블슈팅
권한 설정
Lambda가 CodePipeline과 통신하려면 적절한 IAM 권한이 필요하다. Lambda를 생성할 때 자동으로 실행 역할이 생성되는데, 여기에 CodePipeline 관련 권한이 포함되어 있어야 한다.
필요한 권한 :
codepipeline:PutJobSuccessResultcodepipeline:PutJobFailureResult
보통 Lambda를 CodePipeline에서 호출하도록 설정하면 자동으로 권한이 부여되지만, 문제가 생기면 IAM 콘솔에서 확인해보는 게 좋다.
맺으며
항상 우선순위에서 밀려서 미루고 미루다가 추석 연휴가 되어서야 드디어 슬랙 알림 봇을 구현했다. 혹시나 시간이 오래 걸리지 않을까 하며 우려했던 것과 달리 정말 빨리 구현할 수 있었다. 특히 슬랙 메시지 포맷이나 Lambda 코드 같은 정형화된 부분은 AI가 너무 잘 짜주어서 수월하게 구현할 수 있었다. 구현은 1시간 가까이 걸렸는데, 생산성은 이전에 비해 기하급수적으로 올라갔다. 덕분에 삶의 만족도와 행복까지 덩달아 올라간 기분이다. 그 정도로 이전보다 편해졌다.
배포 알림 하나가 이렇게 큰 차이를 만들 줄은 몰랐다. 이제는 배포를 시작하고 다른 작업을 하다가 슬랙 알림만 확인하면 된다. AWS 콘솔을 열어볼 필요도 없고, 배포 상태를 궁금해하며 시간을 낭비할 일도 없어졌다. 팀원들도 이 기능을 엄청 좋아해줬다.
앞으로도 사용자의 불편함뿐 아니라 개발자의 불편함을 찾아서 해결하는 개발자가 되어야겠다는 생각을 했다. 도구로는 Slack Bot, Chrome Extension, GitHub Actions, n8n, 혹은 이번에 OpenAI DevDay에서 발표한 Agent Builder를 사용할 수도 있다. 새 기술이 계속 나오니 개발자가 편해질 여지도 많다. 사용자도 마찬가지고. 당근 앱만 봐도 사진을 찍으면 알아서 마켓에 올릴 글을 작성해준다.
요즘 생산성에 푹 빠진 나날이다. 다음 글도 아마 생산성 이야기가 될 것 같다.