생애 첫 해커톤으로 Unit에서 운영하는 10번째 Unithon에 참여했습니다. 비록 상은 받지 못했지만, 그 뒤에서 치열하게 수많은 피쳐를 만들어내고 안정적인 개발 프로세스를 만들어내기 위해 노력했던 이야기를 풀어보려 합니다.
DevOps 공개 레포지토리 보러 가기 : https://github.com/Unithon-10th-team8/devops
10th 유니톤에 대해 더 알아보고 싶다면 : https://bit.ly/U10_Dashboard
해커톤을 위한 DevOps
왜 DevOps인가?
해커톤은 그냥 로컬에서만 돌아가도 문제 없는거 아니야? 배포는 그냥 시간 나면 EC2에 간단하게 올리면 되는거고.
물론 아예 틀린 말은 아니라고 생각합니다. 그냥 로컬에서만 돌아가도 문제 없죠. 시연할 때도 그냥 보여지기만 하면 되니까요. 그런데 DevOps를 채택해서 실제로 적용해보면, 그냥 로컬에서만 돌아가는 것과 엄청난 간극이 존재한다는 걸 알 수 있습니다. 굳이 이런 것까지 필요할까? 생각이 들지만, 생각보다 DevOps가 Seamless하게 처리해주는 부분을 개발자 직접 수행할 때 발생하는 Context Switching과 이로 인한 비용이 상당하다는 걸 알 수 있습니다.
이번 해커톤 후기에서는 해커톤에 DevOps를 도입함으로 얻을 수 있었던 효과와 내부의 상세한 아키텍쳐까지 살펴보겠습니다.
DevOps 도입을 통한 성과 1 - CI/CD 파이프라인 (24시간동안 133번의 배포)
2박 3일 해커톤이었지만, 기획과 디자인이 나온 뒤에 기능개발 착수에 들어간 건 2번째 날 부터였습니다. 그래서 실제 개발을 진행한 24시간 가량동안 총 133번의 배포를 진행했습니다. (GitHub Actions CI 동작 횟수 기준)
해커톤에서 과하다고 느껴질 수 있지만 사실 CI/CD 파이프라인만 잘 구축되어 있으면 배포라는 가장 큰 고민 하나가 줄어듭니다. 그리고 배포가 두렵지 않은 팀이 되면, 그 때부터 개발 속도가 월등히 빨라집니다. 이건 비단 해커톤에만 적용되는 내용이 아니고, 실무에서도 100% 동일하게 적용되는 내용입니다. 웹 기반 서비스인데, 실서버 배포를 2주~1달에 한 번씩 날짜를 미리 정해서 공지를 올리고 해야한다? 배포 과정이 수동으로 이뤄지고 혹시나 문제가 발생하여 롤백했을 때 그 프로세스가 확립되어 있지 않고, 이런 환경들 때문에 배포 자체가 두려운 팀이다? 이런 환경에서 어떻게 개발자가 개발에만 전념하고 좋은 제품을 만들기 위한 고민을 같이 할 수 있을까요?
개발 환경에 적용된 사항이 빠르고 매끄럽게 서버에 배포되는 것, 여기서부터 각 펑션별로 유기적인 업무 flow가 자연스럽게 만들어집니다. 백엔드에서 API 엔드포인트를 하나 만들 때마다 바로 서버에 배포되고, 프론트는 완성된 API를 바로바로 가져다 쓸 수 있습니다. 문제가 생기면 바로 노티줄 수 있고 실시간 피드백이 가능해집니다. 프론트에서 페이지가 완성되면 역시 곧바로 서버에 배포되고, 기획/디자인 리뷰와 함께 QA도 곧바로 이뤄집니다. 이런 빠른 피드백이 왔다갔다 할수록 진정한 '애자일'이 되는거고, 밀도 있고 완성도 있는 서비스 개발이 가능해집니다.
DevOps 도입을 통한 성과 2 - 로그 모니터링
프론트엔드/백엔드 개발자 전체에게 Grafana 접근 권한을 부여했습니다. Grafana를 통해 실제 서버에 기록된 로그를 개발자가 직접 확인할 수 있어 버그와 문제 상황을 빠르게 확인할 수 있었습니다. 만일 로컬 환경에서만 개발하다 마지막에 서버에 올렸다면, 그 때부터 버그를 발견하고 트러블슈팅하는 과정을 거쳐야하니 비교적 시간이 오래 걸릴 수밖에 없습니다.
이걸 각 피쳐 개발이 끝날 때마다 서버에 올리고 이상 로그가 있는지 확인하면 동작의 불확실성을 줄여줄 뿐 아니라, 각 개발자가 서버 환경에서 동작하는 앱을 컨트롤할 수 있다는 점에서 심리적 편안함을 주고, 이를 통해 전반적인 개발시간 자체를 줄일 수 있게 됩니다.
DevOps 도입을 통한 성과 3 - Post 해커톤
해커톤이 끝나면 해커톤 당시 만든 서비스는 어떻게 될까요? 만일 배포까지 완료하지 못한 팀이라면 서비스는 코드 상태로만 남아있을 것이고, 설사 배포를 하더라도 EC2 인스턴스의 관리를 지속적으로 해주지 않는다면 서비스가 지속적으로 운영되지 못할 것입니다. 또 나중에 다시 배포를 하려고 해도, 배포 프로세스가 획일화되어 있지 않고 일일이 사람의 손을 타야한다면, 그 당시 환경 그대로 배포할 수도 없을 것입니다. 해커톤이 끝나면 서비스가 내려갈 수 있다는 것. 그게 해커톤 팀들이 가지는 어려움이라고 생각했습니다. 나중에 개인 포트폴리오로 활용하고, 다른 사람들에게 소개도 하고 하려면 계속 남아있어야하는데 말이죠.
이걸 IaC를 통해서 해결했습니다. 팀에서 배포한 모든 앱과 인프라 구성은 코드로 작성되어 깃허브를 통해 모두에게 공유되어 있습니다. 인프라 프로비저닝을 위한 Terraform 파일, 각 코드를 빌드/배포하기 위한 Dockerfile, Docker 이미지, GitHub Actions 배포 yaml 파일, 쿠버네티스에 배포되는 매니페스트 파일이 모두 공개되어 있습니다. 어떤 환경에서든 동일한 형태로 명령어 한 줄이면 배포할 수 있는 것입니다. 따라서 해커톤이 끝나더라도 의지만 있다면 서비스를 계속 유지할 수 있고, 서버에 올리는 작업도 정말 간단하게 수행할 수 있습니다.
DevOps의 존재 이유 - 개발자가 오로지 개발에만 집중할 수 있도록
DevOps는 전체 제품 개발 파이프라인에서 각 조직이 유기적으로 동작할 수 있도록 윤활유 역할을 해줍니다. 개발자가 만든 변경사항을 빠르게 배포해서 확인해볼 수 있도록 하고, 운영 조직이 그 내용을 바탕으로 필요한 작업을 수행할 수 있도록 지원합니다. 개발자 관점으로 좁혀보면, 개발자가 오로지 개발에만 집중할 수 있도록 개발 이외의 모든 것을 지원합니다.
인프라 프로비저닝, CI/CD 파이프라인 구축, 도메인 연결, DB서버 세팅, 로그 확인 / 배포 상태 노티, 환경변수 관리, 팀 내 보안 요소 점검 등... 만일 개발자가 해야했다면 정말 귀찮고 번거로운 작업들이겠지만, 이걸 다 해줍니다. 그게 DevOps가 존재하는 이유니까요.
다양한 툴 지원
이렇게 DevOps가 개발자들의 짐을 덜어준 덕분에, 개발자들은 부담없이 코드에만 집중할 수 있게 되었습니다. 지금껏 사용해본 적 없는 새로운 기술이더라도 부담없이 도입해보고, 함께 협업하며 진정한 성장을 이뤄낼 수 있었습니다.
아래 기술 스택 이미지와 같이 프론트엔드와 백엔드 팀원들이 정말 다양한 최신 기술스택을 사용했고, 해당 기술을 안정적인 시스템 위에서 동작할 수 있게 지원했습니다. 많은 분들이 어려움을 겪는 Next.js 앱 배포도 standalone 모드와 Cloudflare CDN의 조합으로 안정적인 서빙을 가능하게 했고, 백엔드도 다양한 요구사항을 인프라단 제약사항 없이 모두 지원했습니다.
세부 DevOps 시스템 구축하기
Kubernetes 환경
모든 앱 배포가 쿠버네티스 위에서 구동됩니다. Kubernetes는 Container Orchestration 툴로 컨테이너 환경에서 동작하는 앱들을 안정적으로 구동할 수 있도록 지원합니다. 프론트와 백엔드 앱들은 모두 컨테이너 환경에서 동작할 수 있도록 Dockerize하고, 이미지로 빌드해 배포하는 파이프라인이 세팅되었습니다. 이렇게 동작할 수 있도록 메인 클라우드 프로바이더를 선정했습니다.
클라우드 프로바이더 선정하기 - Vultr
AWS같은 다른 선택지도 있었지만 Vultr를 선택한 이유는 '익숙함'과 '비용' 때문이었습니다. 짧은 기간이니 AWS를 써도 비용 차이가 엄청 크진 않았겠지만, 이정도 규모에서 Vultr를 써도 사실 충분하긴 해서 채택했습니다. Vultr와 Vultr Kubernetes Engine에 대해 자세히 알아보고 싶다면 아래 포스트를 읽어보세요!
인프라 프로비저닝 - Terraform
이번에 Vultr Provider로 Terraform은 처음 사용해봤습니다. 아무래도 VPS 서비스 특성상 테라폼 지원은 제한적일 수밖에 없는데 Vultr는 그래도 지원 자체는 되어서 이번에 해봤습니다. 간단하게 만들 수 있어서 아래와 같이 설정했습니다. AWS는 Cluster Autoscaler가 ASG와 연동되는데 여기는 VKE 자체에서 컨트롤할 수 있어서 구조 자체는 좀 더 심플해보였습니다.
resource "vultr_kubernetes" "k8" {
region = "icn"
label = "unithon-cluster"
version = "v1.27.2+1"
node_pools {
label = "unithon-main-node"
node_quantity = 3
plan = "vc2-2c-4gb"
auto_scaler = true
min_nodes = 3
max_nodes = 5
}
}
각종 내부 애플리케이션 세팅하기 - Helm
쿠버네티스 클러스터 내부에서 동작해야하는 기본 애플리케이션들은 Helm으로 설치했습니다. 배포를 위한 ArgoCD, 인증서를 위한 Cert Manager, 파드배치와 스케줄링을 위한 descheduler, 메트릭 수집과 시각화를 위한 kube-prometheus-stack (prometheus, grafana), 로그 수집을 위한 loki-stack (Loki, promtail), 내부 메트릭 수집을 위한 metrics-server, Ingress를 위한 Nginx Ingress Controller, 백엔드 DB서버 postgresql, 환경변수 관리 및 주입을 위한 Sealed Secrets가 Helm을 통해 설치되었습니다.
CI/CD 파이프라인 구축하기 - GitHub Actions & ArgoCD & DockerHub
CI/CD 파이프라인은 GitHub Actions와 ArgoCD를 통해 구축했습니다. 쿠버네티스 환경에서 동작하기 때문에 GitHub Actions는 Dockerfile을 바탕으로 컨테이너 이미지로 빌드합니다. 빌드가 완료되면 이미지를 DockerHub에 올리고, devops 레포지토리에 이미지 태그를 변경합니다. 그러면 ArgoCD가 변경된 이미지 태그로 새롭게 파드를 배포합니다. 일반적으로 GitOps라고 불리는 패턴을 그대로 사용했습니다.
GitHub Actions에 들어갈 Secret은 GitHub Organization 전체 공통 시크릿으로 만들어 조직 내 어느 레포에서든 가져다 쓸 수 있도록 했고, Argo는 후술할 Cloudflare Access와의 연계를 위해 자체 Auth 기능을 해제했습니다. Docker Hub는 원래 Pro를 사용하고 있어 private으로 레포를 만들 수도 있었으나, public으로 만드는게 클러스터 내에 private registry 인증을 위한 secret을 넣지 않아도 돼 그냥 public으로 만들었습니다.
모니터링 시스템 - Prometheus & Grafana & Loki
kube-prometheus-stack Helm Chart를 이용해 Prometheus와 Grafana를 설정했고, loki-stack Helm Chart를 이용해 Loki와 Promtail을 설정했습니다. 로그와 메트릭을 Grafana 한 곳에서 모아서 볼 수 있기 때문에 전체 시스템의 가시성을 확보하기 용이하고, 이걸 활용하면 개발자들이 로그도 편하게 볼 수 있습니다. Grafana에서 로그를 보는 방법은 매우 간단하기 때문에 한 번만 알려줘도 다들 쉽게 사용하는 모습을 볼 수 있었습니다.
환경변수 관리 - Sealed Secrets
환경변수 관리는 Sealed Secrets를 이용했습니다. Sealed Secrets는 Bitnami에서 시크릿 관리를 위해 내놓은 툴로, GitOps에서 민감정보가 쉽게 노출된다는 문제점을 해결하기 위해, 공개 키로 Secret을 암호화하고 이를 Git에 포함시킨 뒤, 실제 클러스터 내에서는 이를 다시 복호화하여 쿠버네티스 Secret으로 만들어주는 방식을 사용합니다. 설명이 다소 복잡하지만, 암호화된 Secret은 외부에 공개되어도 상관 없고, 이를 복호화하기 위해서는 클러스터 내에 있는 인증서가 반드시 필요하다고 보면 됩니다.
클러스터 내부 접근 권한은 DevOps만 보유하고 있으므로 모든 환경변수 관리는 DevOps가 수행해야 합니다. 직접 환경변수를 받아서 Sealed Secrets 매니페스트로 만들고, 최종적으로 GitOps 레포에 포함시키는 것까지 직접 수행해야 하죠. Vault와 같은 툴을 사용하는 방법도 있겠으나, 안정적으로 Vault를 운영하기 위해서는 다양한 부가 기술들이 필요한 만큼 이번에는 시도해보지 못했습니다.
내부 접근제어 - Cloudflare Access
보통 Argo나 Grafana 등 내부 툴에 접근을 허용하기 위해서는 OAuth나 ID/PW 기반 인증 방식을 많이 사용합니다. 실제로 각 제품별로 인증을 처리하기 위한 모듈이 내부에 포함되어 있고, Helm values를 통해 인증에 관한 설정을 디테일하게 만질 수 있습니다. 하지만 이 경우 OAuth 인증에 필요한 Client ID와 Secret 등이 Git 레포에 포함될 수밖에 없는 문제가 있습니다. 이를 해결하려면 외부에서 Secret 영역은 주입을 해주거나 하는 프로세스가 필요한데, 구현하기 위한 방법이 꽤 까다롭습니다.
따라서 정말 간단하게 Cloudflare Access를 앞에 씌워서 인증 처리를 거치고, 인증에 통과되면 뒤에 있는 Argo나 Grafana는 내부 인증 없이 이용할 수 있도록 했습니다. 이 때 내부 인증 없이 모든 기능을 이용할 수 있기 때문에 유저별 권한 제어나 감사 로그 등이 제대로 동작하지 않는데요, 이 부분은 '해커톤이니까'라는 단어로 넘겼습니다.
한 걸음 더 나아가서...
Cloudflare Access에서 특정 Path 제외하고 인증 걸기
이거는 나중에 새로운 글로 포스팅을 쓰는게 나을 것 같아서 여기서는 간단히만 적고 넘어가겠습니다.
Cloudflare Access는 기본적으로 하나의 Application에서 특정 도메인과 path에 대한 Allow/Deny Rule을 함께 적용할 수 없습니다. 이 때문에 만일 도메인 전체에 차단을 걸지만, 특정 path에 대해서만 allow를 열고 싶다면 별도의 Bypass Application을 만드는 방식으로 대응할 수 있습니다. (아래 이미지처럼 설정해줘야 합니다)
여기에 실제 Bypass를 거는 부분은 Policy에서 설정해줘야 하니 해당 내용도 참고 바랍니다.
Database Sync를 위한 init container 만들기
백엔드 개발 중 파이썬 배포시 코드 상의 Schema와 실제 DB의 상태를 동기화하는 부분을 수행하도록 요청을 받았습니다. 이에 파드 initContainer 기능을 이용해 먼저 파드를 띄워 migrate 기능을 쉘로 실행시켰습니다.
spec:
initContainers:
- name: unithon-backend-main-init
image: hyeonwoo5342/unithon-backend-main:7
imagePullPolicy: Always
command: [ 'sh', '-c', '/app/.venv/bin/python manage.py migrate' ]
envFrom:
- secretRef:
name: unithon-backend-main-secret
resources:
requests:
memory: '200Mi'
cpu: '150m'
limits:
memory: '200Mi'
cpu: '150m'
그러면 Alembic이 코드 상의 스키마와 실제 DB의 상태를 동일하게 맞춰줍니다. (아래는 로그)
INFO [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO [alembic.runtime.migration] Will assume transactional DDL.
그래서, 해커톤 후기
그래도 해커톤을 치뤘으니 후기는 간단하게 남기는게 좋을 것 같아서 남겨봅니다.
간단한 프로덕트 소개
해커톤에서 만든 프로덕트는 '영업사원을 위한 인맥 관리 서비스' KAMY(까미) 입니다.
훌륭한 기획자, 디자이너, 그리고 프론트/백엔드 개발자분들과 함께 정말 열심히 만들었으니 관심 있는 분들은 링크를 참고해주세요!
프로덕트 사용해보기 : https://front.haenu.dev
결과는 아쉽지만 후회는 없어
해커톤 결과 자체는 아쉽지만 후회는 없습니다. 실망은 했지만, 상 받는게 목적은 아니었으니까요. 상 대신, 그보다 더 소중한 '성장', 그리고 비슷한 열정 수준을 가지고 있는 동료들을 만날 수 있었습니다.
특히 BackEnd로 참여하긴 했지만, DevOps를 프로젝트에 직접적으로 적용해보며 제로 베이스부터 체계적으로 인프라를 구성해보는 값진 경험을 얻었고, 그 과정 속에서 실제 제품 개발 전체 과정에서 효율성을 극도로 끌어올릴 수 있었던 것 같아 확실한 성장과 즐거움을 느꼈습니다. 또 다른 팀원들과 함께 참여한 분들에게 DevOps의 세계를 보여줄 수 있어서도 기쁘게 생각합니다.
생애 첫 해커톤, 열정 넘치는 그 무대 속에서 더 나은 개발 프로세스를 만들기 위해 노력한 그 순간을 기억하며, 이 글을 바탕으로 앞으로의 해커톤 세계에 DevOps를 접목하는 또 다른 케이스가 나오길 기대합니다!
'DevOps' 카테고리의 다른 글
Vultr Kubernetes Engine 사용기 (0) | 2023.07.25 |
---|