QueryPie 라는 접근제어 플랫폼에 쿠버네티스 접근제어(Kubernetes Access Control; KAC) 제품을 약 5개월이 안되는 시간동안 만들었고, 이제 릴리즈를 앞두고 있다. 뻔한 boilerplate 멘트는 없이 타임라인을 짚으며 시작해보고자 한다.
1월에는 요구사항 분석과 설계를 했다.
KAC 프로젝트는 사내에서 최초로 DDD로 진행한 프로젝트다.
Domain Driven Development가 아닌 Developer Driven Development다. 😂
기획이 먼저 기획서를 제공하고 개발자들이 개발을 하는 플로우와 다르게, 나를 포함한 개발자 3명이서 요구사항 분석부터 기획 그리고 개발까지 모두 시행했다. 그 흐름속에서 Role context, PaC(Policy as Code)를 포함한 기존에 존재하지 않았던 다양한 아이디어들이 나왔고, 동시에 PoC까지 할 수 있었다.
사용자들이 왜 KAC를 필요로 할지 분석한 내용을 적자면...
쿠버네티스 API 서버에는 이미 RBAC 시스템이 있다. 하지만 이러한 RBAC 시스템은 기본적으로 Cluster 단위로 동작하며, 많은 회사들이 하나의 클러스터만 운용하지 않는다.
또, 매 클러스터마다 사내에서 사용중인 IdP를 연동시켜주는 과정은 생각보다 귀찮고 관리되기 어렵다.
첫번째로, 사용자 A에게 namespace/ns1
와 namespace/ns2
하위의 리소스들에만 접근할 수 있도록 정책을 정의한다고 가정했을 때, 사용자는 kubectl get pods --all-namespaces
명령어를 실행하면 403 응답을 받게 된다.
쿼리파이는 우아하게 namespace/ns1
과 namespace/ns2
하위의 리소스들만 필터링하여 제공할 수 있도록 개발했다.
두번째로, 사용자 A에게 namespace/*
하위 리소스들에 대한 접근을 허용하지만 namespace/ns1
하위 리소스들은 접근을 막고 싶다면 어떻게 해야할까?
An RBAC Role or ClusterRole contains rules that represent a set of permissions. Permissions are purely additive (there are no "deny" rules).
안타깝게도 쿠버네티스에는 deny rule을 제공하지 않기 때문에, 존재하는 namespace들을 모두 일일히 등록해줘야만한다. 이는 namespace등의 리소스를 추가할 때 마다 정책을 수정해야줘야만 하는 불편함을 낳는다. 변경이 잦은 클라우드상의 리소스들을 정책을 연계하여 지속적으로 관리해줘야한다는 것은 사실상 이루어지기 불가능한 영역이다.
KAC는 allow와 deny를 모두 제공하며, 리소스 이름에 대한 wildcard 뿐만 아니라 정규식 형태의 필터링 또한 제공한다.
또한 IdP로 부터 제공받은 User Attribute들을 정책에 활용하려면 Admission WebHook등을 사용해야만 했지만, 단일의 PaC에서 한꺼번에 관리가 가능하다. (K8s 1.30 부터 CEL for Admission Control이 등장하긴 했다)
데이터베이스 접근제어와 서버 접근제어가 필요하고 사용되는 이유와 같다.
데이터베이스의 스키마 또는 서버내의 자산이나 구성정보들이 어떻게 변경되었는지 히스토리 또는 해당 작업을 담당자를 찾을 수 없는 경우들을 보완해준다. 또한 이는 법률적으로 관리되어야한다고 명시되는 부분이다. 쿠버네티스상에 있는 무형의 자산들 또한 관리되어야하는 영역이다.
Deployment의 replicaSet이 1로 변경되어있는데, 누가 변경했는지 추적이 불가능하다면 정말 골치아플 것이다.
기존의 DAC, SAC 요구사항과 크게 다르지는 않았기에 설계 자체는 크게 어렵지 않았다. 쿼리파이 제품의 핵심인 사용자가 가장 편하게 사용할 수 있는 형태를 중심으로 기능들을 구성했다.
쿼리파이에서 사용자라고 하면 두 분류로 나뉜다. 정책을 구성하는 보안관리자와 정책을 적용받는 일반사용자.
프로젝트 언어로는 Go를 채택했다.
현재 사내에서 백엔드로 사용하던 언어는 Java/Kotlin과 C# 그리고 일부 TypeScript가 있었다. C#은 여러가지 이유로 인해 앞으로 사내에서 제거해나가고자 하는 언어였기에, Kotlin 기반으로 개발하고자 연구를 시작했다. 짧은 리서치 기간이었지만 JVM 생태계에서 HTTP Proxy를 만들기 어렵다고 판단했고, Kubernetes 관련 모델 및 로직들을 모두 손수 만들어야하는 부담감이 있었다. 또한, K8s에서 사용되는 SPDY라는 비공식 프로토콜이 큰 발목을 잡았다.
보통 프로토콜들은 잘 테스팅된 구현체를 사용하는게 안정적이기에 손수 작성하고 싶지 않았으며, 프레임워크에 따라 저수준의 프로토콜을 지원하기 까다로운 경우도 있다.
Go언어를 사용하는 것은, 유지보수해야하는 언어의 종류가 많아 C#을 걷어내고자하는 사내의 정책과 반대되는 방향이었다. 하지만 Kubernetes 라이선스가 Apache 2.0 이기에 많은 로직들을 재사용할 수 있다는 점은, 안정적이고 빠른 속도로 제품을 만들어 낼 수 있다는 장점이 있었다. 또한 Cloud Platform 이라고하는 DevOps 팀에서 사용하는 언어이기 때문에 새로운 언어를 사내에 추가하는 부담감을 조금은 덜 수 있었다.
요구사항 분석과 기획을 하면서, 처음 접하는 GoLang과 많이 사용해보지 않은 쿠버네티스를 공부하는 걸 병렬로 진행했다. 거의 일주일 동안은 쿠버네티스 공식 홈페이지에 있는 문서들을 읽고 정리하는데에만 시간을 쏟았다. 문서만 읽다보니 눈이 빠질 것 같은 경험을 했고, 한 달이 넘는 시간동안 기획과 공부 그리고 회의만 하다보니 코드가 얼른 짜고 싶었다.. 🫠
본격적으로 코드를 작성하기 시작했다.
2월 1일은 main 코드를 작성하는 PR이 머지된 날이다.
복잡한 Proxy 코드를 먼저 작성했기 때문에 (정책을 서빙하는 API서버와 Front-end 코드는 4월부터 작성하기 시작) Proxy 서버 혼자서도 독립적으로 동작 및 테스트할 수 있도록 했으며, Proxy에 대한 기본적인 기능들이 완성된 3월 말에는 타운홀에서 데모 시연을 진행했다.
KAC라는 기존에 없던, 그리고 생소한 제품을 기획, 영업 그리고 마케팅 부서 등등에게 설명할 수 있었던 시간이었다.
타운홀 데모를 하기 전까지는 내가 요즘 뭘 개발하고 있는지 다른 팀원분들이 많이 물어보셨다. 개발자 3명을 중심으로만 개발했었기 때문에 특히나 더더욱 베일에 싸여있었다.
기획자와 API, Front-end 개발자가 합류하여 디자인이나 유저 시나리오 기반의 기획서를 본격적으로 뽑아내기 시작했다. 기존에 리서치한 내용들을 공유하고, 조금 더 나은 제품을 만들기 위해 시간을 쏟았다. 코드적으로는 MVP에서 MSP로 넘어갈 수 있도록 내부적으로 부족한 구현들을 챙겨나가는 시간이었다.
3rd Party tool을 통한 테스팅, 부하 및 에이징 테스팅 그리고 유닛 테스트 코드와 E2E 테스트 코드들 작성을 통해 기존에 발견하지 못했던 문제점들을 찾고 고쳐나가는 시간을 가졌다. 또한 본격적으로 각 컴포넌트들(Proxy <-> API <-> Front)간의 연동이 이루어지는 기간이었다.
그리고 지금은 또 다른 새로운 제품 출시를 위해 KAC 제품을 위해 준비했던 이터레이션의 첫 번쨰 과정으로 돌아온 상태이다. 회고의 목적에 맞게 기존 이터레이션에서 얻은 KPT(Keep, Problem, Try)를 도출해보면..
음.. 생각해보면 적지 못한 좋았던 부분들만 계속해서 생각나고, 문제라던가 시도해야할 점들이 딱히 떠오르지 않는다. (시도해야할 점이 생각나지 않는건 조금 아쉽다)
정식적인 릴리즈를 마치고 CS를 받고난 이후에 프로젝트의 성공과 실패 여부를 판가름 할 수 있겠지만.. KEP를 작성하는걸 포함하여 회고를 했을 때 좋은 점들이 훨씬 많이 생각나는걸 보면 잘 진행했던 프로젝트였다고 정의해도 괜찮지 않을까?