Traefik 첫 PR 기여 회고
클라우드 네이티브 리버스 프록시로 유명한 Traefik 프로젝트에 기여하고 최종 머지까지 성공한 경험을 공유해보려 합니다.
왜 Traefik 이었나?

클라우드 네이티브 프로젝트를 탐험하던 중, Traefik이라는 라이브러리를 알게되었습니다. Traefik 저장소에서 "good first issue" 레이블이 달린 이슈를 발견하게 되었고, 코멘트에 기여할당을 요청해 진행하게 되었습니다.
또한, go lang을 공부하고 싶던 차에 실시간으로 리뷰를 받으며 기여해볼 수 있다는 점에서 매력을 느껴 재미있게 기여했습니다.
해당 이슈의 주제는 Kubernetes 환경에서 NGINX Ingress를 사용하던 유저들이 Traefik으로 넘어올 때 가장 불편한 점 중 하나는 NGINX에서 쓰던 어노테이션이 안먹힌다는 것이었습니다. 이 간극을 메우기 위해 server-alias 기능을 구현하는 이슈에 도전하게 되었습니다.
server-alias 구현하기
server-alias는 하나의 서비스에 여러 호스트 이름을 붙여주는 기능입니다. 예를 들어 메인 호스트가 example.com 일 때, alias.com으로 들어와도 같은 서비스로 연결해줘야 하죠.
🚩난관 1: NGINX Ingress Rule 준수

단순히 이름을 추가하는게 끝이 아니었습니다. NGINX 공식 문서에 따르면 "기존에 존재하는 호스트 이름과 별칭(Alias)이 충돌하면, 별칭을 무시해야 한다" 는 까다로운 조건이 있었습니다.
즉, 서버 별칭을 호스트 목록에 추가하기 전에 해당 별칭 도메인이 인그레스 규칙에 존재하는지 확인해야 한다는 뜻입니다.
🚩난관 2: Go 언어의 벽
Go의 문법조차 생소했던 저에게는 모든 게 도전이었습니다.
- 슬라이스(Slice)는 어떻게 순회하지?
- 대소문자 구분 없이 문자열을 비교하려면 어떻게 해야 할까?
🚩난관 3: 오픈소스의 문서 포맷
제가 사용한 IDE 환경에서는 해당 레포에서 사용하는 Lint와 싱크가 안맞는지 계속 포맷이 깨져서 커밋이 올라가는게 힘들었습니다.
코드 리뷰
메인테이너분께 매번 호스트 목록을 새로 변수를 지정해서 추가하지 않고, hosts 라는 맵이 모든 인그레스 규칙에 구성된 호스트를 담고 있는 변수를 재사용하는 방법을 피드백 받았습니다.
저는 추가적으로 대소문자 구분을 하지 않고 비교 검사를 진행해야 한다고 하며, 충돌 방지를 위해 strings.ToLower() 로 처리하여 대소문자 구분하는 검사를 추가하는 것을 제안했습니다. 또한, 별칭이 무시될 경우 사용자에게 경고 로그를 통해 전달하는 것을 제안했습니다.
메인테이너분께서는 동의하셨고, 곧바로 리뷰 작업을 진행했습니다.
처음 제가 작성한 코드는 단순히 모든 호스트 리스트를 루프돌며 하나씩 비교하는 방식으로 O(n) 의 복잡도를 가지고 있었습니다. 메인테이너님에 성능 최적화에 대한 피드백을 받았습니다.
- 성능 최적화 (O(n) -> O(1))
- 루프를 도는 대신, 전체 호스트를 Map 구조에 담아 즉시 조회하는 방식으로 변경했습니다. Go의
Map인덱싱을 활용해 성능과 가독성을 모두 잡았습니다.
- 루프를 도는 대신, 전체 호스트를 Map 구조에 담아 즉시 조회하는 방식으로 변경했습니다. Go의
- 대소문자 정규화
- DNS 호스트 네임은 대소문자를 구분하지 않기 때문에, 모든 키를
strings.ToLower()로 소문자화하여 저장하고 비교하는 로직을 적용했습니다.
- DNS 호스트 네임은 대소문자를 구분하지 않기 때문에, 모든 키를
- 리스크 관리
- 제가 건드린 부분은 잘못하면 시스템 전체의 에러 처리 로직에 영향을 줄 수 있는 민감한 곳이었습니다. 메인테이너분께서 직접 코드를 다듬어주며 사이드 이펙트를 최소화하는 방향을 제시해주었습니다
go lang 실력이 많이 부족하다고 느낀게, host 판별 부분이었습니다. 저는 단순 루프를 돌리며 작업한 로직을 메인테이너분께서 성능적인 면에서 인덱싱 연산으로 변경하여 최적화 해주셨습니다.
리뷰를 받으며 어떤 구조로 작업하는지, 명령형 프로그래밍의 기초적인 부분들을 많이 배울 수 있는 시간이었습니다. 기본적으로 코드 베이스 이해도가 다르기 때문에 월등히 높은 퀄리티의 코드를 작성하시는걸 직접 리딩할 수 있는 좋은 기회였습니다.
저의 부족한 실력을 채워나가기 위해 리뷰 받은 코드를 통해 추후에 계속 학습해야 겠다는 생각이 들었습니다.
이번 기여로 배운 Go의 맛
기여 과정에서 직접 코드를 뜯어보며 배운 Go의 핵심 개념들입니다.
- Map의 OK Idiom :
if _, ok := allHosts[alias]; ok구문을 통해 값이 존재하는지 안전하게 확인하는 법을 배웠습니다. - Contextual Logging:
log.Ctx(ctx).Debug()를 사용해 어떤 요청에서 발생한 로그인지 추적 가능하게 남기는 법을 익혔습니다. - Pointer & Deference:
*config.ServerAlias와 같이 포인터 데이터가nil인 경우를 안전하게 처리하는 방식을 체득했습니다.
추후에 배우게 된 개념들을 차근차근히 포스트할 생각입니다.
마치며

이번 기여를 통해 오픈소스는 함께하는 것 이라는 것을 배웠습니다.
Go 언어를 제대로 모른다는 사실이 처음에는 큰 장벽처럼 느껴졌습니다. 하지만
모르는 건 당당하게 묻고,
리뷰어의 피드백을 스펀지처럼 흡수하며,
공식 문서를 기반으로 논리적으로 소통하니
결국 제 코드가 v3.7 마일스톤에 포함되어 전 세계 유저들이 사용하는 기능이 되었습니다.
"완벽해서 시작한게 아니라, 시작했기에 완벽에 가까워질 수 있었습니다."
저와 같은 고민을 하는 분들이 있다면, 일단 PR부터 날려보시라고 말씀드리고 싶습니다!
다음 글에서는 기여활동을 하며 Go를 학습하게 된 개념들에 대해서 예제와 함께 정리해보겠습니다.