CDK8s를 이용하여 쿠버네티스 애플리케이션을 정의하고 관리하기
이 글은 쿠버네티스 애플리케이션을 개발하면서 사용하는 쿠버네티스 매니페스트를 YAML 파일로 작성하고 관리할 때의 어려움을 해결하고 그것들을 효율적으로 쿠버네티스 클러스터에 지속적으로 배포하기 위한 모범 사례를 제안하기 위해 쓰여졌습니다. 이 글에서는 AWS Cloud Development Kit (CDK), CDK for Kubernetes (CDK8s)를 이용하여 쿠버네티스 매니페스트를 정의하는 방법을 다룰 것이며 특히 쿠버네티스, 인프라로서 코드(Infrastructure as Code, IaC) 그리고 지속적 통합 및 배포(Continuous Integration/Continuous Delivery, CI/CD)에 대한 이해가 있는 독자들을 주요 대상으로 하고 있습니다.
쿠버네티스는 컨테이너화된 애플리케이션을 자동으로 배포, 스케일링 및 관리해주는 오픈소스 시스템으로 개발자와 운영자들에게 열렬한 지지를 받으며 발전을 하고 있는 컨테이너 오케스트레이션 도구입니다. 다른 오케스트레이션 도구들과 마찬가지로 쿠버네티스는 사용자가 원하는 어떤 의도를 담은 레코드, 즉 원하는 상태 (desired state)를 쿠버네티스 오브젝트로 정의하여 컨테이너 오케스트레이션의 메커니즘을 구현합니다. 쿠버네티스에서 오브젝트를 생성할 때, 오브젝트에 대한 기본적인 정보(이름과 같은)와 더불어, 의도한 상태를 기술한 오브젝트 스펙을 제시해야만 합니다. 대부분의 경우 정보를 YAML 파일로 쿠버네티스의 커맨드 라인 인터페이스(Comand-line Interface, CLI)인 kubectl에 제공하여 오브젝트를 생성하여 사용자가 원하는 애플리케이션을 쿠버네티스 클러스터에 배포하도록 합니다.
[컨테이너 오케스트레이션의 기본 동작]
YAML은 매우 대중적으로 쓰이고 있는 데이터 직렬화(Data Serialization) 언어입니다. 특히 YAML의 장점은 사용자의 의도를 담은 desired state를 선언적 ( declarative )으로 기술을 하는데 굉장히 적합하기 때문에 선언적으로 상태를 표현해야 하는 많은 시스템에서 사용이 되고 있습니다. YAML은 쿠버네티스의 일부분이라 말할 수 있을 정도로 선호가 되고 있습니다. 다시 말하면 쿠버네티스 애플리케이션을 정의한다는 말은 곧 YAML로 원하는 애플리케이션의 상태 (desired state)를 반드시 표현을 해야 한다는 말이기도 합니다.
YAML의 장점이자 단점은 선언적으로 표현을 하는데 최적화된 언어라는 점입니다. YAML을 통해서 쿠버네티스의 오브젝트를 정의하는 것은 매우 간단합니다. 하지만 사용하는 규모가 커지고 조직이 커지면 이를 관리하는 것이 어려워지기 시작합니다. DevOps라는 개념이 개발/운영에 도입이 되면서 쿠버네티스 클러스터와 애플리케이션의 정의와 관리가 운영팀(Ops)에서 개발팀(Dev)으로 넘어가는 경향이 보이기 시작했습니다. 개발자가 클러스터의 관리를 맡기 시작하면서 개발자들은 프로그래밍 언어로 코드를 작성하는 방식이 아닌 직관적이지 않은 많은 일을 해야 한다는 것을 알게 되었습니다.
그래서 개발팀은 표준이나 템플릿을 만들고 그를 통해서 YAML을 작성하거나 특별한 툴 – “쿠버네티스의 패키지 인스톨러인 kustomize 나 helm ”- 같이 템플릿팅을 이용하는 방법을 사용합니다. 또한 개발자들은 이런 쿠버네티스 오브젝트를 정의하는 YAML파일들이 적합한 API 버전을 사용하는지, 메타 데이터를 제대로 정의를 했는지도 다 확인을 해야 하고 태그나 라벨(Tag/Label) 들로 쿠버네티스의 다른 컴포넌트들을 다 연결도 해야 하고 최종적으로는 이런 것들을 손쉽게 공유를 하는 방법도 고민을 해야 합니다. 또한 helm 나 kustomize 같은 다른 툴을 써도 기본적으로 쿠버네티스를 기본 수준부터 이해하고 앞서 말했던 작업, 즉 YAML 구현을 그대로 해야 합니다. 이런 접근 방법들은 관리해야 하는 애플리케이션의 규모가 커질수록 기존에 사용하던 프로그래밍 언어에 비해서 복잡도 역시 증가하는 경향을 보입니다.
이런 어려움을 해결하기 위한 오픈소스 소프트웨어들을 AWS는 오래전부터 준비하고 있었습니다. AWS Cloud Development Kit (CDK) 은 익숙한 프로그래밍 언어를 사용하여 클라우드 애플리케이션 리소스를 모델링 및 프로비저닝할 수 있는 오픈 소스 소프트웨어 개발 프레임워크입니다. 특히 AWS의 프로비저닝 엔진으로 만들어진 AWS CloudFormation을 사용하면서 겪었던 문제들 – “CloudFormation 템플릿 작성이 복잡하고 어려움“ – 을 프로그래밍 언어를 통해서 해결을 할 수 있다는 점에서 굉장히 유용한 프로젝트입니다.
CDK8s 소개 및 특징
오늘 소개를 할 CDK8s는 CDK 프로젝트와 마찬가지로 익숙한 프로그래밍 언어와 풍부한 객체 지향 API를 사용하여 쿠버네티스 애플리케이션 및 재사용 가능한 추상화를 정의하기 위한 소프트웨어 개발 프레임워크입니다. CDK8s는 순수한 쿠버네티스 YAML을 생성합니다. CDK8s 는 다음과 같은 장점을 가지고 있습니다.
- 친숙한 프로그래밍 언어로 쿠버네티스 애플리케이션을 정의할 수 있습니다 – JavaScript, TypeScript. Python, Java를 지원하면 Go, .NET과 같은 언어들을 지원할 예정입니다.
- 쿠버네티스 애플리케이션을 코드로 정의하고 그것들을 npm, maven과 같은 코드 라이브러리로 공유하고 재사용이 가능합니다 – 라이브러리는 템플릿보다 업데이트하고 유지보수를 하기가 쉽습니다. 여러분은 이런 라이브러리를 통해서 사용자가 어떻게 쿠버네티스 애플리케이션들을 정의하는지 표준화할 수 있습니다.
- 애플리케이션을 개발하는 언어와 같은 툴을 사용하여 CDK8s 코드를 정의하고 빌드할 수 있습니다 – CDK8s를 GitOps 같은 배포 방법론과 함께 사용하면 그것들을 코드로 관리하고 CI/CD 파이프라인을 통해서 여러분의 쿠버네티스 클러스터에 배포가 가능합니다.
- CDK8s로 만들어진 쿠버네티스 매니페스트 파일들은 어디서든 실행 가능합니다. 클라우드 환경 혹은 온 프레미스 등 어디서나 실행되는 쿠버네티스 클러스터에 대한 애플리케이션 정의가 가능하기 때문에 표준화의 도구로도 사용이 가능합니다.
CDK 와 CDK8s의 목표는 개발자와 운영자로 하여금 인프라와 시스템을 코드로 표현함에 있어 객체 지향 프로그래밍 ( Object Oriented Programming ) 기반으로 작성을 할 수 있게 하는 것입니다. 객체 지향 프로그래밍은 시스템 모델링을 하는 굉장히 강력한 방법입니다. 객체 지향 프로그래밍의 특성을 가지는 프로그래밍 언어들을 IaC에서 사용을 할 수가 없었고 YAML과 JSON과 같은 선언적(declarative) 언어들을 통해서 코드를 관리 해왔습니다. CDK와 CDK8s는 Construct Programming Model – “Construct를 레고 블록을 조립하듯이 조합하여 사용하는 프로그래밍 모델”을 통해서 선언적(declarative) 언어들과 명령적(imperative) 언어의 간극을 메꿀 수 있는 강력한 프레임워크입니다.
CDK8s의 워크플로우 알아보기
[CDK8s의 워크플로우]
CDK8s의 워크플로우는 매우 간단합니다. 개발자는 자기가 원하는 언어로 코드를 작성하고 CDK8s CLI를 통해서 코드를 쿠버네티스 매니페스트 YAML 파일로 전환하고 그것들을 kubectl CLI를 통해서 쿠버네티스 클러스터에 배포하거나 이를 형상에 저장함으로써 GitOps의 a sinlge source of truth로 활용을 할 수도 있습니다. 이어서 어떻게 코드를 작성하는지 간단한 예제를 통해서 알아보도록 하겠습니다.
CDK8s으로 쿠버네티스의 Pod 오브젝트 정의하기
import { Construct } from 'constructs';
import { App, Chart } from 'cdk8s';
import { KubePod } from './imports/k8s';
export class MyChart extends Chart {
constructor(scope: Construct, name: string) {
super(scope, name);
new KubePod(this, 'podinfo', {
spec: {
containers: [
{
name: 'hello',
image: 'stefanprodan/podinfo',
ports: [
{ containerPort: 9898 }
]
}
]
}
});
}
}
const app = new App();
new MyChart(app, 'cdk8s-demo');
app.synth();
위는 CDK8s의 typescript-app 템플릿으로 작성한 쿠버네티스 Pod 오브젝트 정의 예제입니다. 예제 코드의 아랫부분에서 App 클래스를 인스턴스화하고 있는데 이 App 클래스가 이 애플리케이션의 Base 혹은 Root 라고 할 수 있습니다. App과 Chart는 모두 Construct라고 하는 클래스를 상속하여 추상화된 클래스입니다. MyChart가 상속하고 있는 ‘Chart’라는 클래스의 이름은 여러분이 잘 아시는 Helm Chart에서 따온 표현입니다. Chart는 단일 쿠버네티스 매니페스트를 합성하는 컨테이너이자 CDK8s에서의 하나의 최소 배포 단위입니다.
예제에서는 이렇게 생성된 Construct 클래스 중 KubePod라고 하는 쿠버네티스의 Pod 오브젝트에 매핑이 되는 Construct를 인스턴스화 함으로써 Pod 오브젝트를 작성하는 스펙을 작성하고 있습니다. 위의 코드를 cdk8s synth
명령어로 쿠버네티스 오브젝트를 생성한 결과는 다음과 같습니다.
apiVersion: v1
kind: Pod
metadata:
name: cdk8s-demo-podinfo-9a02d8a6
spec:
containers:
- image: stefanprodan/podinfo
name: hello
ports:
- containerPort: 9898
Construct 자세히 살펴보기
Construct는 AWS CDK 앱의 기본 빌딩 블록으로 EC2 등의 단일 자원일 수도 있고, 여러 자원을 묶어 추상화해놓은 것일 수도 있습니다. CDK는 이렇게 정의된 Construct들을 이용해 AWS 리소스들의 CloudFormation 템플릿을 생성합니다. 하지만 CDK8s의 세계에서는 이런 Construct를 AWS 리소스를 정의하는데 사용하는 것이 아니고 쿠버네티스 API 오브젝트들을 정의하는 Construct를 이용하여 쿠버네티스 리소스를 정의합니다. cdk8s init
명령을 통해 프로젝트를 초기화하면 웹에서 쿠버네티스 API 오브젝트의 스펙을 읽어와 이를 Construct 클래스 파일들로 생성하는데, 이렇게 생성된 Construct들은 쿠버네티스 오브젝트들과 1:1로 매핑이 되는 낮은 레벨의 객체 정의 Construct 입니다. ( CDK8s 개발자들은 L1 Construct라고 명칭합니다. )
객체지향 프로그래밍적인 접근해보기
CDK8s에서 Construct는 트리 구조 형태로 Construct의 자식으로 또 다른 자식 Construct를 구성할 수 있게 되어있는데 우리는 이를 이용하여 MyChart 안에 쿠버네티스 API 오브젝트 Construct들을 정의하여 쿠버네티스 애플리케이션들을 객체 지향 프로그래밍적인 방법으로 정의할 수 있습니다.
앞서 설명해 드린 코드에 이어 이제는 쿠버네티스의 Horizontal Pod Autoscaler 객체를 추가하여 Pod이 오토스케일링이 되도록 한다고 가정해봅시다. 그전에 했던 것과 마찬가지로 코드를 작성하여 HPA를 추가하면 됩니다. 하지만 개발자들이라면 뭔가 아쉽고 불편함을 느낄 수 있습니다. 애플리케이션을 배포할 때마다 항상 Pod, Deployment, Service, HPA, Ingress 객체 등을 정의할 텐데 이런 반복적인 작업이 불편하지 않나요? 맞습니다. TypeScript로 코딩을 하고 있어서 IDE의 자동완성이나 일부 리팩토링을 통해서 편하게 코딩을 했지만, 지금까지의 경험은 TypeScript를 YAML 대신 사용한 것과 크게 다르지 않은 개발 경험이라고 말할 수 있습니다.
반복적인 작업을 좀 더 쉽고 간단하게 할 수 있는 방법이 있습니다. 힌트는 바로 앞서 말씀드린 Construct의 특징 즉 Construct는 트리구조 형태로 Construct의 자식으로 또 다른 Construct를 구성할 수 있게 되어있다는 점입니다. 다시 말하자면 쿠버네티스 객체와 1:1로 매핑되어 있는 낮은 레벨의 Construct 클래스들을 활용하는 새로운 Construct 클래스를 만들면 하나의 Construct 클래스로 여러가지 쿠버네티스 객체를 생성하는 것이 가능하다는 이야기입니다. 다음에서 소개해 드리는 앱은 쿠버네티스 객체와 1:1로 매핑되는 낮은 레벨의 Construct가 아니고 Pod, Deployment, Service, HPA 등의 객체를 코드 몇 줄로 다 선언할 수 있는 높은 레벨의 Construct입니다.
CDK8s-debore app으로 쿠버네티스 애플리케이션 생성하기
ckd8s-debore 앱은 아주 간단 코드 작성만으로 손쉽게 쿠버네티스 애플리케이션을 개발할 수 있는 샘플 프로젝트입니다. 다음은 debore app으로 앞서 했던 작업을 대체하는 코드입니다. npm install -s cdk8s-debore
명령어를 실행하면 여러분의 프로젝트에 npm을 이용해서 debore app 설치가 가능합니다. 간단하지요?
import { Construct } from 'constructs';
import { App, Chart } from 'cdk8s';
import { DeboredApp } from 'cdk8s-debore';
export class MyChart extends Chart {
constructor(scope: Construct, name: string) {
super(scope, name);
new DeboredApp(this, "podinfo", {
image: 'stefanprodan/podinfo',
containerPort: 9898,
port: 80,
defaultReplicas: 3,
autoScale: true
});
}
}
const app = new App();
new MyChart(app, 'cdk8s-demo');
app.synth();
이 앱이 얼마나 간단하게 쿠버네티스 매니페스트 파일을 작성하는지 보여드리도록 하겠습니다. 위와 같이 작성한 앱을 cdk8s synth
명령어로 생성한 YAML 파일은 아래와 같습니다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: cdk8s-demo-podinfo-deployment-58478e65
namespace: default
spec:
selector:
matchLabels:
app: cdk8sdemopodinfoE4E5409E
template:
metadata:
labels:
app: cdk8sdemopodinfoE4E5409E
spec:
containers:
- image: stefanprodan/podinfo
imagePullPolicy: Always
name: app
ports:
- containerPort: 9898
resources:
limits:
cpu: 400m
memory: 512Mi
requests:
cpu: 200m
memory: 256Mi
---
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: cdk8s-demo-podinfo-deployment-hpa-71f9d32a
namespace: default
spec:
maxReplicas: 30
metrics:
- resource:
name: cpu
target:
averageUtilization: 85
type: Utilization
type: Resource
- resource:
name: memory
target:
averageUtilization: 75
type: Utilization
type: Resource
minReplicas: 3
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: cdk8s-demo-podinfo-deployment-58478e65
---
apiVersion: v1
kind: Service
metadata:
name: cdk8s-demo-podinfo-exposable-service-4ee2399d
namespace: default
spec:
ports:
- port: 80
targetPort: 9898
selector:
app: cdk8sdemopodinfoE4E5409E
type: LoadBalancer
이 코드는 constructor 안에 7줄의 추가적인 코드 작성만으로 60여 줄의 쿠버네티스 YAML 파일을 아주 손쉽게 생성합니다. Pod과 Service 간의 연동을 위하여 selector를 직접 연결해줘야 하는 귀찮은 작업도 하지 않았습니다. 심지어 라이브러리는 손쉽게 npm 레포지토리를 통해서 내려받았고 IDE의 자동 완성 기능을 통해서 손쉽게 코딩을 했습니다. 이것이 바로 CDK8s에서 가장 중요한 부분이자 CDK8s 프로젝트가 지향하는 발전 방향입니다. 여러분이 사용하던 프로그래밍 언어는 기존의 선언적인 언어에서 가질 수 없었던 높은 생산성과 재활용성을 가질 수 있게 도와줍니다.
CDK8s-plus 프로젝트 소개
앞서 말씀드린 CDK8s-debore과 개발자가 손쉽게 이런 라이브러리들을 사용할 수 있는 방향으로 CDK8s 프로젝트는 발전 중입니다. CDK8s 프로젝트는 기본적으로 이런 에코시스템의 첫 번째 레이어라고 할 수 있는 기초를 다지는 작업을 해왔습니다. 이런 생태계의 첫 번째 레이어는 바로 이런 코어 쿠버네티스 오브젝트를 그대로 객체를 사용하는 것이었고 이제는 좀 더 높은 레벨의 추상화를 할 수 있는 라이브러리를 개발해 나가고 있습니다. 그것은 바로 cdk8s+라는 프로젝트입니다. cdk8s+ 는 쿠버네티스 애플리케이션 작성을 위한 높은 수준의 추상화를 제공하는 소프트웨어 개발 프레임워크입니다. cdk8s에서 제공하는 자동 생성 빌딩 블록 위에 구축된 이 라이브러리에는 각 네이티브 쿠버네티스 객체에 대해 수작업으로 제작된 구성이 포함되어있어 복잡성이 감소된 풍부한 API를 노출합니다. 더 상세한 내용을 알기 원하시거나 이 오픈소스 프로젝트에 기여를 하고 싶으신 분은 GitHub의 저장소와 로드맵을 참조하시면 됩니다. 또한 AWS의 컨테이너 블로그에 cdk8s+에 관련된 포스팅이 올라와 있으니 참고를 부탁드립니다.
CDK8s 시작하기
CDK8s를 시작하는 가장 빠른 방법은 https://cdk8s.io/ 에 방문하여 가이드를 참조하셔서 TypeScript 나 Python으로 간단한 예제를 만들어 보는 것입니다. 튜토리얼을 통해서 어떻게 CDK8s를 설치하는지 이것들을 이용하여 어떻게 쿠버네티스 API를 이용해서 여러분의 커스터마이징된 Construct를 만들 수 있는지 배우실 수 있습니다.
마치며
이번 블로그에서는 쿠버네티스에서 널리 쓰이고 있는 YAML을 여러분이 사용하는 익숙한 프로그래밍 언어로 대체할 수 있는 프레임워크인 CDK8s에 대해서 알아보았습니다. CDK8s를 이용하면 개발자 친화적인 방법과 친숙한 프로그래밍 언어로 쿠버네티스 애플리케이션의 관리를 효율적으로 할 수 있으며 이를 기존에 사용하던 애플리케이션의 CI/CD 파이프라인이 통합도 가능합니다. 여러분의 개발 및 운영 환경에 CDK8s를 활용하여 쿠버네티스 애플리케이션 개발을 가속화 하세요.
AWS는 또한 여러분과 커뮤니케이션을 하는 것을 기다리고 있습니다.
– 김광영, AWS 솔루션즈 아키텍트