Cluster Bootstrap?
일반적으로 K8s 클러스터를 생성한 다음, 바로 Application을 배포하기 보단 원활한 클러스터 운영을 위해 시스템 애플리케이션 및 스택(Argo CD, Prometheus, Grafana 등)을 먼저 설치하는 경우가 많다. 클러스터가 얼마 되지 않고 앞으로 추가되는 경우도 없다면 큰 문제가 없지만, 계속해서 운영해야할 클러스터가 늘어난다면 이런 시스템 애플리케이션을 일종의 Bootstrap처럼 기본 제공하는 방안을 고려할 필요가 있다.
이 페이지는 Terraform을 통해 K8s 리소스(ex: Deployment, Service)를 생성하는 방법에 대해 설명한다. 다만 클러스터를 생성하는 과정은 벤더나 Public/Private 여부 등 환경에 따라 큰 차이가 있으므로 다루지 않는다.
Option 1. 각 K8s 리소스에 대응하는 kubernetes Provider의 리소스로 컨버전
Manifest가 비교적 간단하고, CRD가 아닌 경우 선택 가능한 옵션이다. Provider 선언시, kubeconfig 파일을 읽어오거나 Cluster 인증서를 사용하는 방식을 선택할 수 있다.
#kubeconfig 파일 방식
provider "kubernetes" {
config_path = "~/.kube/config"
config_context = "my-context"
}
#인증서 기반
provider "kubernetes" {
host = "https://cluster_endpoint:port"
client_certificate = file("~/.kube/client-cert.pem")
client_key = file("~/.kube/client-key.pem")
cluster_ca_certificate = file("~/.kube/cluster-ca-cert.pem")
}
아래 코드는 test라는 namespace에 간단한 nginx 컨테이너를 Deployment 리소스로 배포하는 예시이다. 우리가 K8s에서 일반적으로 사용하는 YAML 포맷을 그대로 사용할 수 없기 때문에 HCL(Hashicorp Configuration Language)에 맞게 변경하는 작업이 필요하다. 대부분의 필드가 1:1로 매칭되기 때문에 큰 어려움은 없으나, YAML 파일이 여러개이거나, Manifest가 길고 복잡한 경우엔 코드가 길어지고 손이 많이 갈 수 밖에 없다.
resource "kubernetes_namespace" "test" {
metadata {
name = "test"
}
}
resource "kubernetes_deployment" "test" {
metadata {
name = "test"
namespace= kubernetes_namespace.test.metadata.0.name
}
spec {
replicas = 2
selector {
match_labels = {
app = "test"
}
}
template {
metadata {
labels = {
app = "test"
}
}
spec {
container {
image = "nginx:1.19.4"
name = "nginx"
resources {
limits = {
memory = "512M"
cpu = "1"
}
requests = {
memory = "256M"
cpu = "50m"
}
}
}
}
}
}
}
그리고 Kubernetes의 기본 리소스가 아닌 써드파티에서 정의한 CRD(ex: Istio VirtualService, DestinationRule 등)에 해당하는 리소스를 kubernetes Provider가 직접 제공할 수 없다는 문제가 있다. 이러한 점을 보완하기 위해 kubernetes_manifest 라는 리소스도 제공하고 있지만, 마찬가지로 YAML 파일을 그대로 사용할 수는 없으며, 아래와 같이 HCL 양식에 맞게 수정해야 한다.
resource "kubernetes_manifest" "test-crd" {
manifest = {
apiVersion = "apiextensions.k8s.io/v1"
kind = "CustomResourceDefinition"
metadata = {
name = "testcrds.hashicorp.com"
}
spec = {
group = "hashicorp.com"
names = {
kind = "TestCrd"
plural = "testcrds"
}
scope = "Namespaced"
versions = [{
name = "v1"
served = true
storage = true
schema = {
openAPIV3Schema = {
type = "object"
properties = {
data = {
type = "string"
}
refs = {
type = "number"
}
}
}
}
}]
}
}
}
다행히도 built-in function 'yamldecode()'나 tfk8s같은 Tool로 YAML -> HCL의 자동 변환을 지원하기 때문에 어느 정도의 수고는 덜 수 있지만, YAML 파일 내용이 Terraform 코드에 반영되는 것은 피할 수 없다.
Option 2. Helm chart로 재구성 후 Helm Provider를 통한 배포
kubernetes Provider 대신 helm Provider를 사용해서 Application을 배포할 수 있다. 우선 Provider를 선언하는 방식은 kubernetes와 거의 동일하다(사실 큰 차이가 날 수 없다).
#kubeconfig 파일 방식
provider "helm" {
kubernetes {
config_path = "~/.kube/config"
}
}
provider "helm" {
kubernetes {
host = "https://cluster_endpoint:port"
client_certificate = file("~/.kube/client-cert.pem")
client_key = file("~/.kube/client-key.pem")
cluster_ca_certificate = file("~/.kube/cluster-ca-cert.pem")
}
}
helm Provider가 지원하는 리소스는 단 하나, 'helm_release' 밖에 존재하지 않는다. 다음은 bitnami repo로부터 redis 차트를 설치하는 Terraform 코드의 예시이다. values.yaml 파일을 직접 읽어오거나, set 블록을 통해 설정 가능한 것을 알 수 있다.
resource "helm_release" "example" {
name = "my-redis-release"
repository = "https://charts.bitnami.com/bitnami"
chart = "redis"
version = "6.0.1"
values = [
"${file("values.yaml")}"
]
set {
name = "cluster.enabled"
value = "true"
}
set {
name = "metrics.enabled"
value = "true"
}
set {
name = "service.annotations.prometheus\\.io/port"
value = "9127"
type = "string"
}
}
이 방식은 기존 Helm Chart가 존재하는 경우 쉽게 사용할 수 있다는 장점이 있다. 그렇지 않다면 기존 YAML 파일들을 Helm chart로 전환하는데 많은 노력과 시간이 필요하며, Private Helm Repo도 구성해야 한다는 부담감이 다소 존재한다.
Option 3. null_resource의 local-exec Provisioner에서 kubectl 명령어 활용
null_resource는 특정 리소스에 묶이지 않고, 1회성의 프로비저닝 작업을 수행하기 위한 리소스 타입이다. 특히 local-exec나 remote-exec 같은 provisioner 블럭을 통해 사용자가 지정한 Shell Script를 실행할 수 있다. 이를 통해 Filesystem에 위치한 Manifest 파일(.yaml)을 kubectl 명령어로 배포할 수 있다. 아래 예시는 kubectl이 이미 설치되었고 kubeconfig 역시 <Home Directory>/.kube/config 파일에 위치한 상황을 가정한다.
resource "kubenetes_namespace" "test" {
metadata {
name = "test"
}
}
resource "null_resource" "k8s_cluster_bootstrap" {
provisioner "local-exec" {
command = "kubectl apply -n ${kubernetes_namespace.test.metadata[0].name} -f /tmp/nginx.yaml"
}
depends_on = [
kubernetes_namespace.test,
]
}
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
이 방식은 어떠한 K8s 리소스라도 쉽고 빠르게 생성할 수 있다는 장점이 있지만, 아래와 같은 단점 역시 있기 때문에 사용에 유의해야 한다.
- Shell Script나 명령어를 실행하는 방식이므로 선언형 프로그래밍(Delclarative Programming) 방식의 IaC 철학(Idempotency, Immutability)와는 다소 거리가 있다.
- Terraform 실행환경이 K8s API Server에 접근할 수 없는 경우(ex: Private Cluster), 사용이 어렵다.
Option 4. kubectl Provider 활용
기존 Manifest 파일을 그대로 사용할 수 있으면서, Terraform의 리소스로 관리할 수 있는 방식이다. 우선 아래와 같이 kubectl Provider를 선언한다. 기존 kubernetes, helm과 동일하게 kubeconfig, 인증서 방식 모두 사용 가능하다.
terraform {
required_version = ">=1.2.2"
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
version = ">= 2.0.0"
}
kubectl = {
source = "gavinbunney/kubectl"
version = ">= 1.14.0"
}
}
}
provider "kubernetes" {
host = "https://cluster_endpoint:port"
client_certificate = file("~/.kube/client-cert.pem")
client_key = file("~/.kube/client-key.pem")
cluster_ca_certificate = file("~/.kube/cluster-ca-cert.pem")
}
provider "kubectl" {
load_config_file = false
host = "https://cluster_endpoint:port"
client_certificate = file("~/.kube/client-cert.pem")
client_key = file("~/.kube/client-key.pem")
cluster_ca_certificate = file("~/.kube/cluster-ca-cert.pem"))
}
다음은 하나의 디렉토리에 존재하는 여러개의 YAML 파일로부터 K8s 리소스들을 생성하는 예시이다. 기존 kubernetes_namespace 리소스와 같이 사용할 수 있다.
resource "kubernetes_namespace" "test" {
metadata {
name = "test"
}
}
data "kubectl_filename_list" "test_yamls" {
pattern = "${path.module}/test/*.yaml"
}
resource "kubectl_manifest" "test_manifest" {
count = length(data.kubectl_filename_list.test_yamls.matches)
yaml_body = file(element(data.kubectl_filename_list.test_yamls.matches, count.index))
override_namespace = kubernetes_namespace.test.metadata[0].name
}
YAML 파일을 그대로 사용할 수 있다는 점에서 매우 유용하지만 Hashicorp에서 제공하는 Provider가 아니기 때문에, 앞으로도 지속적인 Maintain이 이루어질지 약간의 우려가 있다.
개인적인 의견
Terraform은 인프라를 관리하기 위한 최적의 툴 중 하나이며, 클러스터 최초 생성시 기본 제공해야하는 시스템 애플리케이션을 Bootstrap형식으로 설치하기 매우 용이하다. 다만 실제 서비스를 위한 애플리케이션 배포는 엄연히 CI/CD 파이프라인에서 이루어지는 프로세스이며, 이미 Jenkins, Argo CD와 같이 특화된 S/W가 존재하기 때문에 이 부분까지 Terraform을 사용할 필요는 없음에 유의하자.
참고
Kubernetes Provider for Terraform
'IaC' 카테고리의 다른 글
Terraform - Azure(3) (0) | 2022.03.14 |
---|---|
Terraform - Azure(2) (0) | 2022.03.13 |
Terraform - Azure(1) (0) | 2022.03.11 |
Terraform - (7) Advanced (1) | 2021.06.19 |
Terraform - (6) IAM (0) | 2021.06.18 |