이전에 포스팅에서 언급한 gRPC 코드를 GKE 환경에 Terraform으로 배포하였다.
https://ket0825.tistory.com/12
[gRPC] Go코드와 함께하는 gRPC
1. 관련 개념Static Linking: 정적 링킹 프로그래밍 언어가 컴파일링 과정 중에 코드가 기계어 (OBJ) 파일로 변환되고, 링킹 과정에서 사용하는 라이브러리를 합쳐 같이 실행 파일을 만드는 과정. 따
ket0825.tistory.com
시스템 아키텍처는 아래와 같다

이러한 시스템을 구현하기 위하여 쿠버네티스를 사용하였다.
쿠버네티스
쿠버네티스(Kubernetes, k8s)는 클러스터 환경에서 노드를 관리하고, 컨테이너를 배포하기 위한 컨테이너 오케스트레이션 오픈소스 플랫폼이다. 내부 내용이 매우 방대하고 복잡하기에 이번 포스팅에서는 새로 내가 알게 된 부분, 필요한 부분에 대하여 설명만 할 것이다.
쿠버네티스 기본 환경
Control Plane(Master Node), Worker Nodes로 나눠지며, Control Plane이 배포와 작업, 자원 관리 등을 담당하고 Worker Nodes에서 실질적인 배포와 작업을 담당한다. 대부분의 배포와 작업은 컨테이너 이미지를 바탕으로 실행된다. 그리고, 그 컨테이너를 Pod이라고 한다.
Deployment
클러스터 내부 범위에에서 컨테이너 이미지를 템플릿으로 사용하여 ReplicaSet을 통해 Pod들을 관리하고 배포할 수 있다. 특히, 버전이 달라졌을 때에 롤링 업데이트와 롤백을 통하여 애플리케이션을 배포하고 업데이트할 수 있다.
시스템 아키텍처에 있는 모든 애플리케이션은 모두 Deployment로 배포하였다.
Service
쿠버네티스 환경에서 각 Pod은 언제나 죽고 다시 생겨날 수 있다. 이 때문에 IP주소로 Pod을 식별하는 것은 바람직하지 않다. 이를 위하여 Service는 Pod들의 네트워크에서의 인터페이스가 되어줄 수 있다. 고정적인 IP와 DNS를 제공하므로 Pod의 변경 사항이 클라이언트에게 영향을 주지 않는다. 또한, 로드밸런싱의 역할 또한 해줄 수 있다.
시스템 아키텍처에 있는 모든 애플리케이션은 모두 Service를 ClusterIP type으로 적용하였다(사실 Client는 요청만 하기에 Service가 필요없다).
(조금 더 구체적인) 다른 Pod이 Service와 통신하는 과정
다른 Pod이 Service와 통신하는 과정
- Source Pod에서 dest IP(Service IP)로 요청을 전송한다(DNS는 사전에 일어났다고 가정).
- 패킷이 노드의 네트워크 스택(eth0)에 도달하면 netfilter(리눅스 커널 내부 네트워크 관련 프레임워크)에 의해 처리된다.
- netfilter는 해당 노드에 있는 iptables 규칙(kube-proxy가 관리)을 확인하여 Service IP를 실제 Pod IP로 변환(DNAT)한다.
- 변환된 목적지 IP를 가진 패킷이 해당 Pod으로 전달된다.

Kublet
Pod을 실질적으로 실행하는 Node 속의 관리자이다. Pod을 죽이고, 자원을 회수하고, 다시 살리고, Control Plane에 보고하는 등 노드 내부에서 Pod의 관리를 담당한다.
ConfigMap
ConfigMap을 이용하여 기밀(Secret)이 아닌 데이터를 저장하는 데 사용하는 오브젝트로, Pod은 이를 환경변수로 인식할 수 있다. 컨테이너 이미지에서 환경별 구성을 분리할 수 있다.
따라서 Service와 Deployment를 고려한 아키텍처는 아래와 같다.

Yaml 파일을 통한 배포

위와 같이 yaml 파일을 작성하여 각각 apply를 해야 쿠버네티스 환경에서 배포할 수 있다.
다만 여기에는 큰 문제가 있다.
1. 중복되는 값이 많으며, 서로 같은 값이어야만 한다.
2. 이 중복되는 값은 같은 파일 내부에 있을 수도 있지만, 다른 파일에 있을 수도 있다.
이에 따라 하나를 수정할 때, 다른 하나를 수정하지 못하면 안된다는,
큰 Human Error의 위험성이 생겨난다.
따라서 Terraform을 이용하는 것이 큰 장점이 된다!
Terraform
terraform은 hashicorp에서 만든 IaC(Infrastructure As Code) 오픈소스 툴로, 이를 이용하여 인프라 관리를 자동화할 수 있다.
특정 클라우드에 종속적이지 않으며, 유연하게 구성을 변경할 수 있기에 위에 yaml로만 배포하는 것보다 Human Error를 줄일 수 있다!
파일 종류
main.tf
실제 인프라를 정의하는 부분이다. 추가적인 provider를 위하여 import를 할 수 있다. (yaml 파일의 문법을 terraform의 문법으로 바꿔야 하지만, 이는 GPT가 잘 해준다!)
[var.속성]으로 변수의 재사용이 가능하기에 변수 부분만 바꾼다면 관련된 모든 변수가 바뀐다!
# main.tf
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 4.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.0"
}
}
}
provider "google" {
project = var.project_id
region = var.region
}
# GKE Autopilot cluster
resource "google_container_cluster" "autopilot" {
name = var.cluster_name
location = var.region
# Enable Autopilot mode
enable_autopilot = true
# Network configuration
network = "default"
subnetwork = "default"
# IP allocation policy for VPC-native cluster
ip_allocation_policy {
cluster_ipv4_cidr_block = ""
services_ipv4_cidr_block = ""
}
}
# Configure kubernetes provider
data "google_client_config" "default" {}
provider "kubernetes" {
host = "https://${google_container_cluster.autopilot.endpoint}"
token = data.google_client_config.default.access_token
cluster_ca_certificate = base64decode(google_container_cluster.autopilot.master_auth[0].cluster_ca_certificate)
}
# ConfigMaps
resource "kubernetes_config_map" "grpc_client_config" {
metadata {
name = "grpc-client-config"
}
depends_on = [ google_container_cluster.autopilot ]
data = merge(var.grpc_client_config_defaults, var.grpc_client_config)
}
...
# Services
resource "kubernetes_service" "grpc_client_service" {
metadata {
name = "grpc-client-service"
}
depends_on = [ kubernetes_config_map.grpc_client_config ]
spec {
selector = {
app = "grpc-client"
}
port {
protocol = "TCP"
port = 5051
target_port = 50051
}
type = "ClusterIP"
}
}
...
# Deployments
resource "kubernetes_deployment" "grpc_client_deployment" {
metadata {
name = "grpc-client"
}
depends_on = [ kubernetes_config_map.grpc_client_config]
spec {
replicas = 3
selector {
match_labels = {
app = "grpc-client"
}
}
template {
metadata {
labels = {
app = "grpc-client"
}
}
spec {
container {
name = "grpc-client"
image = var.grpc_client_image
env_from {
config_map_ref {
name = kubernetes_config_map.grpc_client_config.metadata[0].name
}
}
port {
container_port = 50051
}
}
}
}
}
}
resource "kubernetes_deployment" "grpc_server_deployment" {
metadata {
name = "grpc-server"
}
depends_on = [ kubernetes_config_map.grpc_server_config]
spec {
replicas = 3
selector {
match_labels = {
app = "grpc-server"
}
}
template {
metadata {
labels = {
app = "grpc-server"
}
}
spec {
container {
name = "grpc-server"
image = var.grpc_server_image
env_from {
config_map_ref {
name = kubernetes_config_map.grpc_server_config.metadata[0].name
}
}
port {
container_port = kubernetes_config_map.grpc_server_config.data.SERVER_PORT
}
}
}
}
}
}
...
variables.tf
변수를 등록하며, 변수 type 지정과 여러 옵션 설정이 가능하다. main.tf는 이를 자동으로 인식한다. 잘 보면 ..._config와 ..._config_defaults가 있는데, 이는 main.tf에서 merge()를 통하여 기본값에 설정을 덮어씌울 수 있다!
# variables.tf
variable "project_id" {
description = "Google Cloud Project ID"
type = string
}
variable "region" {
description = "GCP region for the cluster"
type = string
default = "us-central1"
}
variable "cluster_name" {
description = "Name of the GKE cluster"
type = string
default = "grpc-cluster-autopilot"
}
variable "grpc_client_image" {
description = "Docker image for gRPC client"
type = string
}
...
variable "grpc_client_config" {
description = "gRPC Client ConfigMap data"
type = map(string)
default = {}
}
variable "grpc_internal_config_defaults" {
description = "Default gRPC Internal ConfigMap data"
type = map(string)
default = {
INTERNAL_PORT = "50053"
INTERNAL_HOST = "0.0.0.0"
OUT_DIR = "../../encoded_videos"
TEMP_DIR = "../../temp"
}
}
variable "grpc_internal_config" {
description = "gRPC Internal ConfigMap data"
type = map(string)
default = {}
}
variable "grpc_server_config_defaults" {
description = "Default gRPC Server ConfigMap data"
type = map(string)
default = {
SERVER_HOST = "0.0.0.0"
SERVER_PORT = "50052"
INTERNAL_PORT = "5053"
INTERNAL_HOST = "grpc-internal-service"
}
}
variable "grpc_server_config" {
description = "gRPC Server ConfigMap data"
type = map(string)
default = {}
}
outputs.tf
배포(apply) 이후 output으로 나오는 정보를 설정해준다.
# outputs.tf
output "cluster_endpoint" {
description = "Cluster endpoint"
value = google_container_cluster.autopilot.endpoint
}
output "cluster_name" {
description = "Cluster name"
value = google_container_cluster.autopilot.name
}
output "region" {
description = "GCP region"
value = var.region
}
terraform.tfvars
여기서 variables.tf의 값을 직접 넣어줄 수 있다!
region = "asia-northeast3"
...
grpc_client_config = {
VIDEO_URL = ...
}
명령어
terraform init
패키지 의존성 등에 따른 설치 및 환경을 시작한다.
terraform plan
실제 배포 시에 변수 설정이 어떻게 되었는지 확인이 가능하다.

terraform apply
실제로 배포를 진행한다.
terraform destroy
배포한 인프라를 삭제한다.
그 외 다양한 명령어가 더 존재한다.
결론적으로 Terraform을 이용하여 GKE에 배포하였다.
GKE Autopilot
GKE(Google Kubernetes Engine)은 GCP에서 제공하는 완전관리형 쿠버네티스 서비스이다.
여기에는 Standard.모드와 Autopilot 모드가 있다.
Standard 모드는 빌릴 수 있는 노드 수, 스펙 등을 완전히 설정해야 하고, 이에 대한 비용을 노드 별로 지불해야 한다. 수동으로 쿠버네티스 클러스터를 관리해야 한다.
Autopilot 모드는 노드를 빌리는 개념이 아닌 Pod을 빌리는 개념이다. Pod이 점유하는 자원에 대해서만 비용을 지불해도 되기에 비용이 Standard 모드에 비해 저렴하고, 관리 또한 GCP에서 완전히 해주기에 편리하다. 로깅과 모니터링 또한 GCP에서 제공하기에 더욱 편리하다.
따라서 GKE Autopilot을 더욱 추천한다.
위의 Terraform 코드를 통하여 GKE를 autopilot으로 배포하였다.
아래 코드에서 resource "google_container_cluster" "이름" {
enable_autopilot = true
}
이 부분만 한다면 Autopilot 모드가 된다!
# main.tf
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 4.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.0"
}
}
}
provider "google" {
project = var.project_id
region = var.region
}
# GKE Autopilot cluster
resource "google_container_cluster" "autopilot" {
name = var.cluster_name
location = var.region
# Enable Autopilot mode
enable_autopilot = true
# Network configuration
network = "default"
subnetwork = "default"
# IP allocation policy for VPC-native cluster
ip_allocation_policy {
cluster_ipv4_cidr_block = ""
services_ipv4_cidr_block = ""
}
}
주의: Autopilot 모드에서 Pod에서의 임시 스토리지의 최대치는 10Gi이므로, 더 필요하다면 pvc, pvc-claim 등을 사용하여 영구 스토리지를 사용하자!
이렇게, Go 코드로 gRPC를 이용한 애플리케이션을 마이크로서비스 형태로 GKE에서 terraform에 배포하였다!
이후 배포에서 GKE Autopilot 사용을 고려해봐야겠다.
참고:
쿠버네티스(Kubernetes) 네트워크 정리
Kubernetes Network
medium.com
https://kubernetes.io/ko/docs/concepts/configuration/configmap/
컨피그맵(ConfigMap)
컨피그맵은 키-값 쌍으로 기밀이 아닌 데이터를 저장하는 데 사용하는 API 오브젝트이다. 파드는 볼륨에서 환경 변수, 커맨드-라인 인수 또는 구성 파일로 컨피그맵을 사용할 수 있다. 컨피그맵
kubernetes.io
https://cloud.google.com/kubernetes-engine/docs/concepts/autopilot-resource-requests?hl=ko
Autopilot에서 리소스 요청 | Google Kubernetes Engine (GKE) | Google Cloud
이 페이지에서는 GKE Autopilot 워크로드의 리소스 요청에 대한 기본값, 최솟값, 최댓값을 설명합니다.
cloud.google.com
https://github.com/GDG-on-Campus-KHU/1st-BE-team3-DeployMSA?tab=readme-ov-file
GitHub - GDG-on-Campus-KHU/1st-BE-team3-DeployMSA: grpc and streaming server
grpc and streaming server. Contribute to GDG-on-Campus-KHU/1st-BE-team3-DeployMSA development by creating an account on GitHub.
github.com
'Data Engineering > Micro Service' 카테고리의 다른 글
| [gRPC] Go코드와 함께하는 gRPC (2) | 2024.12.03 |
|---|