본문 바로가기

회고

DevOps가 될 수 없어서, 개발자로 입사해 직접 DevOps 환경을 구축하다.

1. 서론: 꿈과 현실의 간극

처음 IT 커리어를 시작했을 때 저는 소규모 조직의 서버 관리자 역할을 맡았습니다.

 

개발자와 서버관리자의 경계가 점점 허물어지면서 개발자로 이직하게 되었으나,  개발자들이 로컬 개발 환경이나, 서버 배포환경 세팅에 어려움을 겪는 것을 도우면서 DevOps 직군에 관심이 가게 되었습니다.

 

하지만 DevOps 엔지니어로 전환은 쉽지 않았습니다.

자격증을 취득하고 여러 포지션에 지원했지만, 실무 경험 부족으로 기회를 얻기 어려웠습니다.

 

입사 1달전 쿠버네티스 코리아 그룹에 작성 했던 글

 

그리고 현재 회사에 입사하여 1년이 지났습니다.

 

2. 문제 발견: DevOps 없는 조직의 현실

현재 재직 중인 회사에서는 프랜차이즈 가맹점 관리 프로그램을 개발하고 있으며,

입사 전에는 외주 개발사에서 서버 운영과 개발을 함께 담당했습니다.

 

온프레미스 기반으로, 개발자가 서버까지 직접 운영해야 했기 때문에 시간적 여유가 부족했고,

배포도 git pull을 이용한 수동 방식이었습니다.

root 계정으로 생성된 디렉토리를 git pull 하여 문제없이 사용하여야 했기 때문에  777 권한으로 설정이 되어있었고

해당 디렉토리로 웹서비스를 하고 있어 보안에 취약할 수밖에 없었습니다.

 

DB 연결 부분은 .gitignore를 통해 소스에서 제외되어 있었으나, 환경변수 처리가 되어있지 않았기에 로컬에 개발환경을 구축하기 위해서는. gitignore 된 파일을 복사해서 사용하는 문제점이 있었습니다.

또한 일부 환경변수는 하드코딩이 되어있는 경우도 있어  운영환경과 개발환경을 구축하기 어려운 상태였습니다.

개발환경을 구축하면서 하드코딩된 환경변수로 인해 운영서비스에 영향을 주게 되어 곤란한 상황이 종종 발생했었습니다.

 

디렉토리명등이나 서비스 구조를 한눈에 파악하기 어렵게 되어있었습니다.

서비스는 기능상으로는 웹 인터페이스와 외부 연동용 API로 나뉘어 있었지만, 아키텍처적으로는 분리되어 있지 않았습니다.
웹 서비스는 서버 내에서 직접 화면을 렌더링 하고 데이터베이스와 연결되는 전통적인 구조였고, API는 별도 프로세스를 통해 동작했지만 동일한 서비스 내부에서 함께 관리되고 있었습니다.

이로 인해 배포와 운영이 통합되지 않았고, 로그 수집이나 장애 대응도 각각 개별적으로 이루어졌습니다. 

같은 서비스를 여러 브랜드로 나눠서 사용하는 구조였으나, 코드부터, 서버 설정까지 일정한 부분이 없어서 통합이 필요하였습니다.

3. 행동: 직접 만든 DevOps 환경

전반적인 환경을 개선하기 위해 다음과 같이 작업을 진행하였습니다.

  1. 서버 및 서비스 현황 파악, 정리
    • 유휴 및 실제 운영 서버 확인
    • 서비스 구성 확인
    • 도메인 정리
  2. 소스 정리 및 배포 전략 수립
    • 신규 레포지터리 추가
    • 기존 레포지터리 브랜치 추가, 정리
    • 배포 전략 구성
    • 환경변수 정리
  3. 컨테이너 환경 전환
    • 개발 환경 컨테이너화 (Docker)
    • 운영 서비스 컨테이너로 마이그레이션( Docker compose)
    • 배포 자동화 (Jenkins)
    • 서버 모니터링 및 알림 체계 구축 (Zabbix)
  4. 컨테이너 오케스트레이션 도입
    • 쿠버네티스 클러스터 구축 (RKE2)
    • 스토리 클러스터 구축 (Rook-ceph)
    • 컨테이너 이미지 생성 (Kaniko)
    • GitOps 기반 CI/CD 파이프라인 구축 (Argocd)
    • 모니터링 및 알림 체계 구축 (Prometheus, Grafana)
    • 로그 시스템 구축 (Loki, Promtail)
  5. 온프레미스 -> 클라우드 마이그레이션 (예정)

먼저 보유서버 현황 및 서비스를 점검하고, 연결된 도메인과 경로를 정리하였습니다.

확인 결과 다수의 유휴서버를 활용할 수 있게 되었고, 개발환경을 구축할 수 있는 기반을 만들었습니다.

 

서버를 살펴보며 서비스를 확인하여보니, 일부 서비스는 서버에만 배포되어 있고 버전관리가 되지 않는 것을 확인하여

신규 Git 레포지터리를 생성하여 관리할 수 있도록 하였습니다. 이미 Git 레포지터리로 관리하고 있던 대상이 있었으나

dev, stage, prod 환경이 정해지지 않은 채, 관리되고 있는 것을 확인하고 Git 전략을 세워, 실제 환경에 맞게 브랜치를 정리하였습니다.

각각의 환경별로 정리하다 보니, 환경변수가 분리되지 않아 개발환경 구축 중 운영 서비스에 영향을 미치는 경우도 있었습니다.

환경별로 따로 변수를 지정하여 하드코딩을 지양할 수 있도록 소스 수정 또한 진행하였습니다.

 

개발 환경은 Docker를 사용하여 컨테이너화 하여 로컬에서 실행할 수 있도록 하였고, Jenkins를 통해 Git 커밋이 진행되면 

배포될 수 있도록 자동화를 개발 환경에 적용하고 Docker compose를 통해 순차적으로 영향이 적은 서비스부터 컨테이너 환경으로 전환하기 시작하였습니다. 이때까지는 Jenkins에서는 github webhook 이벤트가 발생하면 git fetch, git pull만 하는 아주 단순한 수준의 배포였습니다. 메인 서비스가 build가 필요 없는 PHP로 이뤄졌었기 때문에 신규 코드 변경 사항만 적용해도 충분하였습니다.

 

Docker compose를 통해 서비스를 운영 중 큰 이슈는 없었지만 다음과 같은 고민이 있었습니다.

'도메인만 다른 동일한 서비스를  template 화하여 운영할 수는 없을까?'

'특히 온프레미스 환경에서 고가용성 문제를 어떻게 해결할 수 있을까?'

'만약 클라우드로 이전하게 된다면?'

이 모든 것은 고려하면 컨테이너 오케스트레이션 도입이 필수라 느껴졌습니다. 

도커 스웜도 있었지만 쿠버네티스 자격증도 취득하였고 꼭 실무에서 사용해보고 싶었던 기술이라 사용하기로 마음먹었습니다.

 

쿠버네티스 도입에 앞서 다음과 같은 질문 또한 저에게 하고 계속 고민했습니다.

'규모에 비해 너무 오버엔지니어링은 아닌가?'

'혼자 인프라를 관리하고 있는 상황에서 학습 동시에 마이그레이션이 가능한가?'

'운영 중 이슈는 어떻게 대처할 것인가?'

'전통적인 환경에서도 충분하게 운영이 가능하진 않은가?'

 

이런 고민들을 AI 도구와 논의하면서, 1U로만 구매한 서버들을 어떻게 합쳐서 쓸 수 있을지, 어떻게 스토리지를 구축할 수 있을지 고민하였습니다. 

 

CKA 자격증 취득 후 직접 구축해 본 홈랩의 구성은 Kubespray기반에 control-plane 노드, worker 노드로 구성하였습니다.

구성 중에도 느꼈지만 'control-plane이 죽으면 리스크가 상당히 클 텐데 이걸 한대만 두는 게 맞을까?'라는 생각이 들었습니다.

홈랩에서 control-plane 노드의 시동을 끄고 시뮬레이션했을 때 POD 스케줄링 불가 외에 기존에 떠있던 서비스들은 문제가 없었습니다.

kubespray 경우 20-30분 정도 설치가 진행된 걸로 기억합니다. 이로 인해 쿠버네티스에 지식이 많지 않은 경우 장애 복구에 빠르게 대처하기가 어려울 것이라 판단하였습니다.

홈랩 구성도

 

검색 중에 RKE2를 알게 되었고, Kubespray보다 쿠버네티스 클러스터를 빠르고 쉽게 구축할 수 있는 것을 알게 되었습니다.

구축 속도도 빠르고, 설치 매뉴얼이 잘 되어있어서 문서를 잘 작성하면 제가 아니더라도 문서를 보고 복구할 수 있을 거라 판단하였습니다. 물론 Kubespray처럼 다양한 커스터마이징이 불가하지만 아직까진 한계를 느끼진 못했습니다. 

다음의 표를 보시면 Kubespray와 RKE2의 차이를 간략하게 보실 수 있습니다.

 

Kubespray vs RKE2

항목 Kubespray RKE2
목적 Ansible 기반의 유연한 Kubernetes 설치/구성 자동화 도구 Rancher에서 만든 보안 강화형 Kubernetes 배포판
설치 방식 Ansible 플레이북 실행 → SSH로 노드에 접근 후 설정 적용 단일 바이너리(rke2-server, rke2-agent) 실행
구성 자유도 매우 높음 (네트워크 플러그인, CRI, 버전, OS 설정 등 세세히 지정 가능) 비교적 제한적 (RKE2 설계 범위 내에서 옵션 제공)
초기 진입 난이도 높음 (Ansible, Kubernetes 구조 이해 필요) 낮음 (단일 설치 명령, 기본값 안전)
네트워크 플러그인 Calico, Flannel, Cilium 등 다양하게 선택 가능 기본 Canal(Calico+Flannel), Cilium 선택 가능
운영/업데이트 Ansible로 롤링 업데이트 가능 Rancher CLI, systemd 서비스로 업데이트
보안 기능 사용자가 직접 설정 (SELinux, PSP 등) CIS 벤치마크 기본 준수, SELinux 활성화, 암호화 스토리지, 자동 보안 정책 적용
고가용성(HA) 설정 가능하나 직접 구성해야 함 기본적으로 HA 구성 지원
적합한 환경 커스터마이징이 많은 환경, 특정 네트워크/CRI 요구사항 있는 곳 보안이 중요한 온프레미스·엣지 환경, Rancher와 통합 운영

 

RKE2를 이용하여 고가용성 클러스터를 구축하기 위해 Control-plane 노드로 3대 구성하게 되었습니다.

혹시 모를 장비 Down에 대비하여 keepalived를 통해 VIP를 설정하여 고가용성을 보장할 수 있도록 하였습니다.

RKE2는 Control-plane 노드에도 POD 스케쥴링이 가능하게 되어있습니다.

 

자세한 내용은 다음에서 확인해보실 수 있습니다.

https://1ocate.tistory.com/4

 

쿠버네티스로 이전하는 과정에서 가장 큰 걸림돌은 Volume 마운트 이슈였습니다.

기존에는 Docker Compose를 사용해 베어메탈 1대에서 서비스를 운영했으며, 컨테이너의 Volume을 호스트 경로에 직접 마운트 하여 사용했습니다. 그러나 이 방식은 서버 장애 시 데이터 유실 위험이 있어 개선이 필요했습니다.

쿠버네티스에서는 Pod가 스케줄링될 때 임의의 노드(호스트)에 배치되므로, 호스트 경로를 Volume 마운트로 사용하는 방식이 불가능했습니다.

일반적으로 온프레미스를 운영하는 환경에서는 스토리지가 있어 구성해 문제를 해결하지만, 저희 서버 환경에서는 별도의 스토리지를 운영할 여유가 없었습니다. 또한 고가용성을 위해 FreeNAS 등으로 이중화(Standard-Active)를 구성할 경우 서버 자원 소모와 디스크 추가가 필요하다고 판단해 적용이 어려웠습니다.

AI와 많은 대화와 고심 끝에 ceph를 도입하게 되었습니다.

 

쿠버네티스에서 함께 관리하기 위해 rook-ceph를 설치하였습니다.

참 감사하였던 것은 Control-plane 노드 설치 시 각 서버에 SSD 1TB가 Raid 1로 구성되어 있었던 점입니다.

그래서 각 Control-plane 설치 시 OS용 Volume과, Ceph에서 사용할 osd를 각각 하나씩 사용할 수 있었습니다.

총 3대의 Control-plane 노드에서 구성된 osd를 클러스터화 하여 Ceph 스토리지를 구성하였습니다.

 

다음으로 쿠버네티스 배포 자동화 및 설정의 버전을 관리하기 위해 Argocd를 설치하고 Gitops를 도입하였습니다.

완전한 IaC는 아니었지만, 이동 중에 git commit만으로 서비스 환경을 유지 보수 할 수 있고, 작업 히스토리가 남게 되어

다른 팀원도 학습 후 유연하게 사용할 수 있게 되었습니다.

초기에는 helm을 통한 Arogcd application 정의를 하였지만 동일한 서비스를 일관성 있게 환경을 유지하기 위해 kustomize를 추가 도입 하였습니다.

 

인프라가 쿠버네티스로 마이그레이션 되는 시기에 메인 서비스의 주언어가 PHP에서 Node.js로 변경되어 신규 서비스가 배포되었습니다. 

이에 맞춰 Jenkins에서 ssh로 접근하여 git fetch, git pull 하는 단순 배포 적용 방식이 아닌  Docker image build -> image update (Argocd Image Updater) 방식으로 변경하였습니다.

 

쿠버네티스 클러스터를 통해 서비스가 배포되면서 다양한 지표에 대해 모니터링이 필요하였습니다.

Docker compose로 운영시 사용하였던 Zabbix는 쿠버네티스를 지원하였지만, 쿠버네티스의 모든 메트릭을 모니터링하기에는 조금 부족한 부분이 있었습니다. 빈틈을 메꾸기 위해 Prometeus, Aletmanager를 추가하여 리소스 사용량, 장애등을 감지하여 안정적인 서비스를 유지할 수 있도록 하였습니다.

 

어느 정도 구축이 다 진행된 줄 알았으나, 컨테이너 로그를 볼 수 있는 방법이 상당히 불편해졌습니다.

이전에는 Docker compose로 배포된 서버에 접근하여 docker logs 등으로 바로 확인할 수 있었으나, 쿠버네티스 도입 이후에는 노드에 접근해서  kubectl 명령등으로 확인 수밖에 없게 되었습니다. 초기에는 제가 직접 로그를 확인 해주었으나, 제가 부재시에 로그를 보기가 상당히 번거로울 수 밖에 없었습니다.  Loki, Promtail를 통해 Grafana와 연동하여 개발자분들이 로그를 보기 편하게 개선하였습니다. 특히 LogQL을 활용할 수 있어서 다양하게 조회해볼 수 있는 것은 큰 장점이라고 생각합니다.

 

4. 결과: 변화와 성장

초기에는 서버와 서비스 구성이 뒤죽박죽 얽혀 있었으나, 동일한 서비스는 템플릿 화하여 신규 서비스 추가 시 설정 누락을 방지하고, 한 번의 설정 수정으로 전체에 일괄 적용이 가능해져 운영 효율성이 크게 향상되었습니다.

 

온프레미스 환경에서 필수적인 고가용성을 확보하여 안정적인 서비스 제공이 가능해졌습니다.

 

향후 클라우드 전환을 보다 수월하게 진행할 수 있는 기반을 마련하였습니다.

IaC(Infrastructure as Code) 도입을 통해 서버 설정을 코드로 관리하고, 변경 사항을 추적할 수 있도록 개선하였습니다.

 

유휴 서버 활용과 불필요한 IDC 서비스 제거를 통해 월 20% 이상의 비용 절감을 달성하였습니다.

 

배포 자동화로 개발자가 본연의 개발 업무에 온전히 집중할 수 있는 환경을 조성하였습니다.

 

DevOps를 어디서부터 시작해야 할지 감을 잡지 못하던 저에게, 이를 간접적으로 체험하고 이해할 수 있는 소중한 기회가 되었습니다.

 

5. 회고: 배운 점과 앞으로의 다짐

DevOps로 커리어 전환을 꿈꾸었지만, 현실은 녹록지 않았습니다. 대부분의 채용 공고에서는 클라우드 경험을 요구했으나, 저는 온프레미스 환경의 서버 관리자 경력을 충분히 살리지 못했고, 커리어 전환은 쉽지 않았습니다.

 

결국 제가 할 수 있는 것은 스스로 경험을 만들어 가는 것이었습니다.

특히 AI 도구를 활용해 주어진 환경의 문제를 개선한 경험이 인상 깊었습니다.

 

가장 기억에 남는 부분은 쿠버네티스 도입입니다.
Docker Compose를 사용할 때는 쿠버네티스로의 전환이 막연히 쉬울 것이라 생각했지만, 실제 적용 과정에서는 양날의 검과 같은 부분이 많았습니다.

출처 : https://www.reddit.com/r/kubernetes/comments/1l0i2un/procrastination_of_a_kubernetes_admin/

 

Docker Compose 환경에서 쉽게 가능했던 일이 쿠버네티스에서는 비슷하지만 다른 개념으로 작동하는 경우가 많았고, 성능 문제도 있었습니다.

 

마이그레이션을 진행하는 매일매일이 긴장의 연속이었고, 지금도 고군분투하며 개선을 이어가고 있습니다. 현재 시점에서는 전환이 옳은 결정이었다고 생각하지만, 앞으로 생각이 바뀔 가능성도 배제할 수 없습니다.

 

하지만 한 가지 분명한 것은, 블록을 조립하듯 체계적으로 구성하고, 잘못된 부분은 빼서 다시 맞추는 체계적이면서도 유연한 운영 경험을 쌓을 수 있었다는 점입니다.

 

이제야 DevOps 세계에 맛만 조금 본 것 같습니다.

개선하고 발전시켜 나아가는 가운데, 대량의 트래픽이 발생하는 상황에서도 안정적으로 서비스를 제공하는 경험을 언젠간 할 수 있도록 더욱 노력하겠습니다.