Kubernetes 관리 툴 K9s

Kubernetes 클러스터를 효과적으로 관리하는 일은 매우 복잡할 수 있습니다. 그러나 K9s라는 터미널 기반 UI 툴을 사용하면 이러한 복잡성을 크게 줄일 수 있습니다. 이번 블로그 포스트에서는 K9s의 주요 기능과 설치 방법, 사용법에 대해 자세히 알아보겠습니다.

K9s

K9s는 Kubernetes 클러스터와 상호 작용하기 위해 설계된 CLI(명령줄 인터페이스) 도구입니다. 실시간으로 클러스터의 리소스를 관리, 모니터링, 문제 해결할 수 있도록 직관적인 인터페이스를 제공합니다.

주요 기능

  • 클러스터 리소스의 실시간 모니터링
  • Pod, 서비스, 배포 등을 보기 위한 인터랙티브 UI
  • 사용자 정의 레이아웃 및 뷰 지원
  • 빠른 탐색과 작업을 위한 단축키
  • 고급 필터링 및 검색 기능

 이 기능들을 몇개의 단축키로 쉽게 사용할 수 있습니다.

K9s 설치 방법

먼저 k9s를 설치하기 전에 kubectl이 설치되어있어야 합니다.

 

kubectl 설치방법은 kubectl - 쿠버네티스 클러스터를 관리해보자! 문서를 참조해주세요

Linux:

curl명령어를 사용해 바이너리 파일 다운로드 합니다.

curl -sS https://github.com/derailed/k9s/releases/latest/download/k9s_Linux_x86_64.tar.gz | tar -xz
sudo mv k9s /usr/local/bin/

macOS:

brew 명령어를 사용해 설치합니다.

brew install k9s

Windows:

방법 1 : Windows용 바이너리를 K9s GitHub releases 페이지에서 다운로드하고 PATH에 추가합니다.

방법 2 : chocolatey를 사용해 설치합니다. 

choco install k9s

K9s 시작하기

설치 후 K9s를 실행하려면 간단히 다음 명령어를 입력합니다:

k9s

 

k9s 첫 실행화면.

 

이 명령어를 입력하면 K9s 대시보드가 열리고, 여기서 Kubernetes 클러스터에 연결하여 리소스를 관리할 수 있습니다

K9s 사용법

리소스 보기

  • 기본 네비게이션: 화살표 키를 사용하여 리소스 유형 간 이동, Enter 키를 눌러 특정 리소스의 자세한 정보를 확인할 수 있습니다.
  • Pod 로그 보기: l 또는 :logs
  • Pod로 실행: e 또는 :exec
  • 포트 포워딩: f 또는 :port-forward

리소스 필터링 및 검색

  • 명령 바: :를 사용하여 명령 바에 접근
  • 필터 적용: :filter status=Running 등 필터를 적용하여 리소스 좁히기
  • 검색: :search my-pod 등으로 이름 또는 라벨로 리소스 검색

리소스 관리

  • 리소스 설명: d를 눌러 리소스 설명 확인
  • 리소스 편집: e를 눌러 YAML 파일 편집
  • 리소스 삭제: d를 눌러 리소스 삭제

고급 기능

뷰 커스터마이징

  • :view 명령어를 사용하여 레이아웃을 사용자 정의
  • :view save  :view load로 커스텀 뷰 저장 및 불러오기

단축키 및 명령어

  • ?를 눌러 사용 가능한 모든 단축키 목록 확인
  • : 명령 바를 사용하여 K9s 명령어 실행

참고

Kubectl

kubectl은 Kubernetes 클러스터를 cli(Command Line Interface) 방식으로 관리하는 데 쓰는 툴입니다. Kubernetes 클러스터에서 리소스 배포, 서비스 스케일링 조정, 로그 확인 등 다양한 작업을 수행할 수 있습니다.

Kubectl 설치

Windows에서 Kubectl 설치

- Chocolatey로 설치:

choco install kubernetes-cli

 

- Scoop으로 설치:

scoop install kubectl

 

- curl로 바이너리 다운로드:

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/windows/amd64/kubectl.exe" move kubectl.exe C:\Windows\System32\kubectl.exe

 

2.2. macOS에서 Kubectl 설치

macOS에서는 Homebrew를 이용해 손쉽게 kubectl을 설치할 수 있습니다.

 

- Homebrew로 설치:

brew install kubectl

 

- curl로 바이너리 다운로드:

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/darwin/amd64/kubectl"
chmod +x kubectl
sudo mv kubectl /usr/local/bin/kubectl

 

2.3. Linux에서 Kubectl 설치

Linux에서는 패키지 관리자를 사용하거나, 직접 바이너리를 다운로드하는 방법으로 kubectl을 설치할 수 있습니다.

 

- APT 패키지 관리자를 이용한 Ubuntu/Debian 설치:

sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg echo "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.list sudo apt-get update sudo apt-get install -y kubectl

 

- YUM 패키지 관리자를 이용한 CentOS/RHEL 설치:

cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-\$basearch enabled=1 gpgcheck=1 repo_gpgcheck=1 gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg exclude=kube* EOF sudo yum install -y kubectl --disableexcludes=kubernetes

 

- curl로 바이너리 다운로드:

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" chmod +x kubectl sudo mv kubectl /usr/local/bin/kubectl

 

 

설치가 완료된 후 kubectl version --client 명령어를 사용하여 설치가 성공적으로 이루어졌는지 확인합니다.

3. Kubectl 설정 및 기본 사용법

설치가 완료된 후, kubectl을 사용하여 Kubernetes 클러스터에 접근하려면 클러스터의 kubeconfig 파일을 설정해야 합니다. kubeconfig 파일은 kubectl이 어떤 클러스터에 연결할지, 어떤 자격 증명을 사용할지 등을 정의합니다.

Kubeconfig 파일 설정

보통 ~/.kube/config 경로에 위치하며, 다중 클러스터 환경에서는 여러 kubeconfig 파일을 사용할 수 있습니다. 환경 변수 KUBECONFIG를 사용하여 특정 설정 파일을 지정할 수 있습니다.

자세한 설정은 kubeconfig 파일을 사용하여 클러스터 접근 구성하기 문서를 참조해주세요!

$env:KUBECONFIG="C:\Users\your-user\.kube\config"

기본 명령어

클러스터 정보 확인: kubectl cluster-info

현재 컨텍스트 확인: kubectl config current-context

모든 네임스페이스의 리소스 목록 조회: kubectl get all --all-namespaces

리소스 배포: kubectl apply -f deployment.yaml

리소스 삭제: kubectl delete -f deployment.yaml

 

설치한 kubectl이 작동하지 않을 때 대처

설치 후 kubectl 명령어가 정상적으로 작동하지 않는 경우, PATH 환경 변수를 확인해보세요! 또한, Kubernetes 클러스터와 연결되지 않는다면 kubeconfig 파일이 올바르게 설정되었는지 확인해봐야 합니다.

버전 확인

현재 설치된 kubectl의 버전을 확인하여 Kubernetes 클러스터와 호환되는지 점검합니다.

kubectl version --client

 

참고 자료

일정 및 작업 관리

프로젝트 발표자료 팀원소개란

 이번프로젝트에서는 팀장으로서 팀이 프로젝트를 성공적으로 마무리 할 수 있도록 어떤 순서로 작업을 하고, 인원배분은 또 어떻게 해야할지에 대해서 많은 고민을 했다.

 


공동 작업

 여러명이서 함께하는 팀 프로젝트인 만큼 작업내용을 구체화해서 작업시간과 인원을 배분하면 좋겠다고 생각했고, 이렇게 구체화한 작업들을 관리하기 위해서 칸반보드를 사용해서 현재 프로젝트의 진행상황이 어떤지, 작업 중에 어떤 문제를 마주쳤고 어떻게 해결했는지 확실하게 남기고자 했다.

칸반보드
칸반보드를 통해 관리한 이슈들 목록

 팀 규칙, 주제 선정, 시스템 설계 작성 등 프로젝트 진행에 대해 전체적인 방향성 해결해야할 문제에는 팀원 모두가 함께 머리를 맞대 최대한 빠르게 합리적인 결론을 도출하고자했다. 그리고 아래처럼 토의의 내용과 결과를 issue 탭에 기록해 issue 탭에 기록한 내용을 바탕으로 프로젝트의 다음단계를 진행했다.

시스템 리소스 설계 중 토의내용 일부발췌
업무 분배 및 순서 토의내용 일부발췌

 

 실제 구현파트의 작업분배는 팀원들 각각의 강점을 고려해서 분배했고, 나는 POC로서 각 일정들이 제시간에 마무리 될 수 있도록 각 파트를 오가며 작업상황을 체크하고 작업을 도와서 최대한 빨리 작업을 마무리할 수 있게끔 하는 역할을 맡았다.

 

이땐 알지 못했다..ㅠ 작업파트를 정하지 않았다는 것이 얼마나 많은 노동을 의미하는지...


파트 분배 작업

 각자 파트를 나눠 작업해야하는 구간에서는 각 파트의 진행사항을 확인하고, 작업을 도왔다. 그래서 프론트 부분의 로직과 인프라, 백엔드 부분의 로직과 인프라 작업 모두에 참여했다.

 그리고 진행상황을 최대한 쉽게 파악하기 위해 각 파트에서 해야할 일의 프로세스를 정해서 체크리스트로 만들었다.

이슈 탭 진행상황 체크리스트

물론 작업 중에 이슈를 마주쳤을 때 아래처럼 해당 이슈에 대한 기록도 남겨 내가 어떤문제를 마주했고 어떻게 해결했는지 팀원들도 알 수 있도록 했다.

이슈 기록 중 일부 발췌

 

오류에 대한 해결방법을 보고싶다면 아래 게시글을 확인해주면 된다.

2023.06.20 - [DevOps] - [DevOps] 최종 프로젝트 회고 - 구현(클라우드 리소스)

 

[DevOps] 최종 프로젝트 회고 - 구현(클라우드 리소스)

아키텍처는 이미 앞서 설계 부분에서 소개를 했기 때문에 구현과정 중 트러블 슈팅에 대한 내용을 위주로 작성했다. 트러블 슈팅에 대한 내용을 말하기 전에, 우리 아키텍처가 더 나아지기 위해

dratini.tistory.com

 

Terraform을 사용해서 이번 프로젝트에서 구축해야하는 AWS 리소스들을 코드로 관리할 수 있게끔 했다. 

 

이번에는 프로젝트를 진행하면서 작성한 Terraform을 사용해서 어떻게 IaC 구현을 했는지 대한 기록을 남겨두려고 한다.


Terraform Backend 구성하기

 테라폼 백엔드를 사용해서 tfstate 파일을 깃허브 레포지토리로부터 분리해 외부로 현재 인프라가 어떻게 구성되었는지에 대한 정보가 노출되지 않도록 했다. s3만 사용할 수도 있지만, 다이나모 DB를 사용해서 locking까지 할 수 있도록 했다.

 

먼저 따로 만들어둔 테라폼 백엔드 작업용 디렉토리에서 tfstate파일을 저장할 저장소를 terraform을 사용해서 생성한다.

참고 : https://devops-james.tistory.com/123

 

저장소 생성 코드

더보기
#terraformbackendresource.tf

provider "aws" {
  region = "ap-northeast-2"
}

resource "aws_s3_bucket" "mys3bucket" {
  bucket = "Devops-Final-tfstate"
}

# Enable versioning so you can see the full revision history of your state files
resource "aws_s3_bucket_versioning" "mys3bucket_versioning" {
  bucket = aws_s3_bucket.mys3bucket.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_dynamodb_table" "mydynamodbtable" {
  name         = "terraform-locks"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}

output "s3_bucket_arn" {
  value       = aws_s3_bucket.mys3bucket.arn
  description = "[S3버킷의 ARN을 입력하세요]"
}

output "dynamodb_table_name" {
  value       = aws_dynamodb_table.mydynamodbtable.name
  description = "[다이나모 DB 테이블 이름 적어주세요]"

 

그리고 원격 백엔드를 둘 경우에는 먼저 둘 곳인 S3 저장소가 생성된 이후에 다시 terraform init을 해서 Terraform Backend를 적용시켜줘야한다.

 

Terraform Backend 적용 코드

더보기
terraform {
  backend "s3" {
    bucket = "Devops-Final-tfstate"
    key    = "TF-backend/terraform.tfstate"
    region = "ap-northeast-2"
    dynamodb_table = "terraform-locks"
  }
}

그리고 terraform init을 하면 아래처럼 테라폼 백엔드가 적용되는 것을 볼 수 있다.

terraform init
Initializing the backend...
Do you want to copy existing state to the new backend?
  Pre-existing state was found while migrating the previous "local" backend to the
  newly configured "s3" backend. No existing state was found in the newly
  configured "s3" backend. Do you want to copy this state to the new "s3"
  backend? Enter "yes" to copy and "no" to start with an empty state.

Terraform 코드를 사용한 리소스 생성.

VPC.tf

VPC.tf 에서 서브넷과 인터넷 게이트웨이, 보안 그룹, 라우팅 테이블 구성을 해준다. 그리고 VPC내부에서 보호되고있는 컴퓨팅 유닛이 VPC외부의 AWS리소스와 상호작용 할 수 있도록 VPC 엔드 포인트도 만들어줬다.

 

소스코드 더보기

더보기
resource "aws_vpc" "my-vpc" {
    cidr_block = "10.0.0.0/16"
    enable_dns_support   = true
    enable_dns_hostnames = true
    tags       = {
        Name = "Terraform VPC"
    }
}

# create subnet
resource "aws_subnet" "PublicSubnet01" {
  vpc_id = aws_vpc.my-vpc.id
  cidr_block = "10.0.1.0/24"
  availability_zone = "ap-northeast-2a"
  map_public_ip_on_launch = true

  tags = {
    Name = "my-public-subnet01"
  }
}

resource "aws_subnet" "PublicSubnet02" {
  vpc_id = aws_vpc.my-vpc.id
  cidr_block = "10.0.2.0/24"
  availability_zone = "ap-northeast-2c"
  map_public_ip_on_launch = true

  tags = {
    Name = "my-public-subnet02"
  }
}
resource "aws_subnet" "PrivateSubnet01" {
  vpc_id = aws_vpc.my-vpc.id
  cidr_block = "10.0.3.0/24"
  availability_zone = "ap-northeast-2a"
  
  tags = {
    Name = "my-private-subnet01"
  }
}
resource "aws_subnet" "PrivateSubnet02" {
  vpc_id = aws_vpc.my-vpc.id
  cidr_block = "10.0.4.0/24"
  availability_zone = "ap-northeast-2c"
  
  tags = {
    Name = "my-private-subnet02"
  }
}

# 인터넷 게이트웨이 ( 외부 인터넷에 연결하기 위함 )
resource "aws_internet_gateway" "my-IGW" {
  vpc_id = aws_vpc.my-vpc.id
}

# 라우팅 테이블
## 1. 퍼블릭 라우팅 테이블 정의
resource "aws_route_table" "my-public-route" {
  vpc_id = aws_vpc.my-vpc.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.my-IGW.id
  }
}

## 퍼블릭 라우팅 테이블 연결
resource "aws_route_table_association" "my-public-RT-Assoication01" {
  subnet_id = aws_subnet.PublicSubnet01.id
  route_table_id = aws_route_table.my-public-route.id
}
resource "aws_route_table_association" "my-public-RT-Assoication02" {
  subnet_id = aws_subnet.PublicSubnet02.id
  route_table_id = aws_route_table.my-public-route.id
}

## 보안 그룹
resource "aws_security_group" "my-SG" {
  vpc_id = aws_vpc.my-vpc.id
  name = "my SG"
  description = "my SG"
  tags = {
    Name = "my SG"
  }
}

## 보안 그룹 규칙
resource "aws_security_group_rule" "my-ingress-rule-22" {
  type              = "ingress"
  from_port         = 22
  to_port           = 22
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.my-SG.id

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_security_group_rule" "my-ingress-rule-80" {
  type              = "ingress"
  from_port         = 80
  to_port           = 80
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.my-SG.id

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_security_group_rule" "my-ingress-rule-443" {
  type              = "ingress"
  from_port         = 443
  to_port           = 443
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.my-SG.id

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_security_group_rule" "my-ingress-rule-3000" {
  type              = "ingress"
  from_port         = 3000
  to_port           = 3000
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.my-SG.id

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_security_group_rule" "my-ingress-rule-3306" {
  type              = "ingress"
  from_port         = 3306
  to_port           = 3306
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.my-SG.id

  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_security_group_rule" "my-egress-rule" {
  type = "egress"
  from_port = 0
  to_port = 0
  protocol = "-1"
  cidr_blocks = ["0.0.0.0/0"]
  security_group_id = aws_security_group.my-SG.id
  lifecycle {
    create_before_destroy = true
  }
}

# vpc내부의 컴퓨팅 유닛이 다이나모 DB에 연결할 수 있도록 하기위해 vpc 엔드포인트 생성.
resource "aws_vpc_endpoint" "my-vpc-endpoint" {
  vpc_id              = aws_vpc.my-vpc.id
  service_name        = "com.amazonaws.ap-northeast-2.dynamodb"
  vpc_endpoint_type   = "Gateway"
}

resource "aws_vpc_endpoint_route_table_association" "my_vpc_endpoint_rt_association" {
  vpc_endpoint_id = aws_vpc_endpoint.my-vpc-endpoint.id
  route_table_id  = aws_route_table.my-public-route.id
}

alb.tf

인터넷과 연결되지 않고 VPC 내부의 컴퓨팅 유닛으로 Load Balancing을 수행하는 ALB를 만들어줬다.

 

소스코드 더보기

더보기

 

resource "aws_alb" "my_alb" {
  name = "Taskmanagement-ALB2"
  internal = true
  load_balancer_type = "application"
  security_groups = [aws_security_group.my-SG.id]
  subnets = [aws_subnet.PrivateSubnet01.id, aws_subnet.PrivateSubnet02.id]
  enable_cross_zone_load_balancing = true
}

# 80포트를 리슨하고
resource "aws_lb_listener" "lb_listener" {
  load_balancer_arn = aws_alb.my_alb.arn
  port              = 80
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.my-target-group.arn
  }
}

# 3000포트를 타겟한다.
resource "aws_lb_target_group" "my-target-group" {
  name     = "Taskmanagement-TG2"
  port     = 3000
  protocol = "HTTP"
  vpc_id   = aws_vpc.my-vpc.id

  target_type = "ip"

  health_check {
  enabled             = true          # 헬스 체크 활성화
  interval            = 30            # 헬스 체크 간격(초)
  path                = "/"           # 헬스 체크에 사용할 경로
  protocol            = "HTTP"        # 사용할 프로토콜
  timeout             = 5             # 각 헬스 체크에 대한 타임아웃(초)
  healthy_threshold   = 3             # 건강한 상태로 판단하기 전에 연속적으로 통과해야 하는 헬스 체크 수
  unhealthy_threshold = 3             # 건강하지 않은 상태로 판단하기 전에 연속적으로 실패해야 하는 헬스 체크 수
  matcher             = "200-299"     # 헬스 체크 응답에 대한 HTTP 상태 코드
  }
}

apigateway.tf

APIGateway는 VPC 내부의 private ALB와 연결돼야하기 때문에 HTTP API로 생성했다.

api 설계에 맞춰서 구현된 alb와 lambda의 엔드포인트로 넘어가는 요청 메서드들을 설정해줬다. 

그리고 private ALB에 연결할 수 있도록 VPC Link를 만들어줬다.

 

코드보기

더보기
module "apigateway" {
  source  = "terraform-aws-modules/apigateway-v2/aws"
  version = "2.2.2"

  create_api_domain_name = false

  name          = "Task-management API-tf"
  description   = "Task-management API created by terraform"
  protocol_type = "HTTP"

#cors 설정. 테스트를 위해서 모든 헤더와 메서드, 송신자를 허용한다.
  cors_configuration = {
    allow_headers = ["*"]
    allow_methods = ["*"]
    allow_origins = ["*"]
  }

  default_route_settings = {
    data_trace_enabled       = true
    detailed_metrics_enabled = true
    logging_level            = "INFO"
    throttling_burst_limit   = 5000
    throttling_rate_limit    = 10000
  }

  integrations = {
    "POST /user" = {
      lambda_arn             = "[람다 ARN을 입력하세요]"
      payload_format_version = "1.0"
      timeout_milliseconds   = 12000
    }

    "GET /" = {
      connection_type    = "VPC_LINK"
      vpc_link           = "my-vpc-Link"
      integration_uri    = "[로드밸런서의 리스너 ARN을 입력하세요]"
      integration_type   = "HTTP_PROXY"
      integration_method = "GET"
    }

    "POST /" = {
      connection_type    = "VPC_LINK"
      vpc_link           = "my-vpc-Link"
      integration_uri    = "[로드밸런서의 리스너 ARN을 입력하세요]"
      integration_type   = "HTTP_PROXY"
      integration_method = "POST"
    }

    "PUT /{Task_id}" = {
      connection_type    = "VPC_LINK"
      vpc_link           = "my-vpc-Link"
      integration_uri    = "[로드밸런서의 리스너 ARN을 입력하세요]"
      integration_type   = "HTTP_PROXY"
      integration_method = "PUT"
    }

    "DELETE /{Task_id}" = {
      connection_type    = "VPC_LINK"
      vpc_link           = "my-vpc-Link"
      integration_uri    = "[로드밸런서의 리스너 ARN을 입력하세요]"
      integration_type   = "HTTP_PROXY"
      integration_method = "DELETE"
    }
  }

#VPC에 의해서 외부로부터 보호받고있는 ALB에 연결하기 위해서 
  vpc_links = {
    my-vpc-Link = {
      name               = "Task-VPC-Link-tf"
      security_group_ids = ["서브넷 아이디 입력하세요"] # 내가 연결하고싶은 vpc의 서브넷에 연결한다.
      subnet_ids         = ["서브넷 아이디 입력하세요", "서브넷 아이디 입력하세요"]
    }
  }

아키텍처는 이미 앞서 설계 부분에서 소개를 했기 때문에 구현과정 중 트러블 슈팅에 대한 내용을 위주로 작성했다.

 

트러블 슈팅에 대한 내용을 말하기 전에, 우리 아키텍처가 더 나아지기 위해서 어떤 작업을 하면 좋을지 생각해봤다.

아키텍처 발전 방안 1. Private Subnet과 Nat Gateway를 활용한 보안성 향상

 

 현재 구현한 아키텍처에는 ECS가 Public Subnet 에 위치하도록 구성이 되어 있는데, 이 부분이 보안 부분에 취약 할 수 있다는 우려가 있어 아래의 아마존 공식 문서를 참고해 지금의 이미지처럼 아키텍처를 보완했다.

 ECS 와 DB서버를 Private Subnet에 위치하게 두고 NAT Gateway를 Public Subnet으로 분리하여 ALB를 통한 ECS와의 통신은 유지하면서 클러스터 내부의 IP주소 노출위험을 제거했다. 

 

참고자료 : https://docs.aws.amazon.com/ko_kr/vpc/latest/userguide/vpc-nat-gateway.html

 

NAT 게이트웨이 - Amazon Virtual Private Cloud

보조 IPv4 주소는 선택 사항이며 NAT 게이트웨이를 사용하는 워크로드가 단일 대상(동일한 대상 IP, 대상 포트 및 프로토콜)에 대한 동시 연결 55,000개를 초과하는 경우 지정하거나 할당해야 합니다

docs.aws.amazon.com

 

 

 


Issue .1) ECR 레포지토리 이미지 Task 생성 실패

작성한 백엔드 코드를 테스트 해보기 위해 ECR에 이미지 푸쉬 후 콘솔에서 테스트를 해보려 했으나 클러스터에 Task가 생성되지 않는 문제가 발생 했다.

ecrtask-failtask-fail2

  • Fargate에서는 정상적으로 돌아가는 서비스가 EC2로 생성하면 지속된 에러가 괴롭혔다.
  • 확인 해본 결과 생성된 EC2가 서비스에 등록되지 않아 발생한 문제인 것을 확인했다.

svc1

  • 용량 공급자 전략으로 클러스터 기본 옵션을 사용 했고 이것이 문제인 것을 확인 했다.

Issue .2) 생성된 EC2 클러스터에 등록 불가.

시작 유형으로 직접 클러스터 생성 시 만들어지는 EC2를 등록하려 했으나 클러스터에 EC2가 잡히지 않았다.

cluster-ec2

  • 생성된 EC2에 퍼블릭 IP주소 할당이 안 되어 클러스터에 노출이 안 되는 것을 확인 했다.

ec2-ip

  • VPC 옵션 중 퍼블릭 IPv4 주소 자동 할당을 활성화 시켜주니 EC2에 퍼블릭 IPv4 주소가 할당 되고 클러스터에 EC2가 잡혔다.

vpc-ipEC2-ip2cluster-ec2-2

  • 정상적으로 EC2가 활성화 되었다.

infraTask-def2

  • 당시 생성 했던 EC2 유형.

image

  • 등록된 인프라 컨테이너

Issue. 3) Task 생성 실패

EC2 컨테이너는 등록을 했지만 이번엔 Task가 생성이 되지 않았다.

con-ec2

  • 에러 로그에서 생성된 EC2의 메모리의 크기가 컨테이너보다 작아서 발생 된 문제 라는 것을 확인했고 t3.nano 에서 m6i.large로 바꿨더니 정상적으로 Task가 생성 되었다.

imageimage

Issue .4) Service 배포 실패

정상적으로 Task 생성에는 성공 했지만 서비스 배포에는 실패 했다.

svc-failhealthcheckfail

  • health check 에 unhealthy가 떴고, 원인이 타겟 그룹과 ALB 의 포트 매핑이 잘못되어 원활하게 포트를 못 잡고 있던 것을 확인했다.

alb

  • ALB (80) - Listener(80) - Target Group (3000) - 컨테이너 (3000) 으로 매핑을 시켜 포트를 다시 잡아줬다.

sg-inbound

  • 보안그룹의 불필요한 포트들도 다시 정리 해주었다.

image

슈팅 성공

image

  • Healthy와 status code 200의 반환과 ALB 주소로 접속이 성공했다.

 


Issue .5) 배포된 이미지의 DynamoDB 엑세스 불가

 ECS 클러스터로 이미지 배포에는 성공했지만 배포된 컨테이너가 다이나모 DB에 제대로 아이템을 넣지 못하는 문제가 발생했다.

 

 ECR에 배포된 이미지가 정상임에도 실제 배포 환경에서 제대로 작동하지 않는 문제는 권한 문제가 원인일 수도 있다고 생각해 Task의 실행 역할을 확인해 DynamoDB에 엑세스권한이 없다는 것을 확인하고 다음 이미지와 같이 정책을 추가해줬다.

 하지만, 권한을 부여해줬음에도 제대로 작동하지 않는 것을 확인했고, 다른 원인이 있을 것이라고 생각해 알아봤다. 

 

https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/vpc-endpoints-dynamodb.html

 

 원인을 찾아보던 중 아마존 공식 문서에서 관련된 내용을 찾을 수 있었다.

 해당 문서는 vpc엔드포인트를 통해 VPC내부의 ec2가 외부의 Dynamodb 와 상호작용할 수 있도록 설정하는 법에 대해서 설명한 문서인데, 문서에서 안내해준대로 VPC 엔드 포인트를 통해 DynamoDB에 엑세스할 수 있도록 설정했더니 문제없이 아래처럼 EC2가 DynamoDB에 아이템을 집어넣는 것을 확인할 수 있었다.


Issue .6) 도메인 트래픽 API Gateway 접근 거부

 API Gateway의 CORS 설정에서 도메인 트래픽에 포함된 header를 거부하도록 설정해둬서 POSTMAN으로 접속시에는 원활하게 작동하는 API가 도메인을 웹브라우저로 접속하면 접속이 되지않는 현상을 발견했다.

 

 웹브라우저가 요청에서 어떤 header가 포함되어 요청 메시지를 보내는지 확인하고, CORS에서 해당 헤더에 대한 허가 정책을 설정해주니 API Gateway가 잘 작동하는 모습을 확인할 수 있었다.

 


리소스 구현을 진행하며 느낀 점

 아키텍처 컨셉을 증명하는 작업. 즉, 클라우드 리소스들을 만드는 작업을 진행하면서 이 클라우드 리소스에서 작업을 할때 어떤 점을 신경써줘야하는가에 대해서 감을 잡을 수 있었다.

 물론 AWS 클라우드 서비스의 종류는 아주아주 다양하고, 각 서비스들에 대해서 이해를 잘 하고있어야 제대로 사용할 수 있기 때문에 배우면 배워갈 수록 내가 점점 더 작아져보이는 듯한 기분이 든다..ㅎ

 그래도 하나하나씩 배우다보면 언젠가는 클라우드 서비스들을 자유롭게 엮어서 강력한 아키텍처를 구성할 수 있는 참된 엔지니어가 될 수 있을거라고 믿어 의심하지 않는다.

 그거 의심할 시간에 한번이라도 더 리소스들이 어떻게 작동하는지 파악해야하니까 말이다

CI/CD 파이프라인 구현 - Git Action workflow .yaml파일 작성

 

ECS 이미지 배포 yaml 코드

name: Deploy to Amazon ECS

on:
  push:
    branches: [ "dev" ]
    paths: 
      - 'Task/**' 

env:
  AWS_REGION: [region]
  ECR_REPOSITORY: [ECR Repository name]
  ECR_BACKUP_REPOSITORY: [ECR Repository name]
  ECS_SERVICE:[ECS Service name]
  ECS_CLUSTER: [ECS Cluster name]
  CONTAINER_NAME: [ECS Container name]

permissions:
  contents: read

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    env:
     working-directory: "./Task"  
  

    steps:
    - name: Checkout
      uses: actions/checkout@v3

# AWS 인증하기
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: ${{ env.AWS_REGION }}

# ECR에 로그인하기
    - name: Login to Amazon ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v1

#각 빌드된 이미지들에게 고유한 태그(github.sha값)를 달아 ECR에 푸시
    - name: Build, Unique tag, and push image to Amazon ECR
      id: build-unique-image
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        IMAGE_TAG: ${{ github.sha }}
      run: |
        # Build a docker container and
        # push it to ECR so that it can
        # be deployed to ECS.
        docker build -t $ECR_REGISTRY/$ECR_BACKUP_REPOSITORY:$IMAGE_TAG .
        docker push $ECR_REGISTRY/$ECR_BACKUP_REPOSITORY:$IMAGE_TAG
        echo "image=$ECR_REGISTRY/$ECR_BACKUP_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
      working-directory: ${{ env.working-directory }}
      
#latest 태그를 달아 ECR에 푸시한다. 푸시되는 레포지토리는 각각 다르다.
    - name: Build, latest tag, and push image to Amazon ECR
      id: build-image
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        IMAGE_TAG: latest
      run: |
        # Build a docker container and
        # push it to ECR so that it can
        # be deployed to ECS.
        docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
        echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
      working-directory: ${{ env.working-directory }}

#가장 최근에 사용한 task정의 파일을 사용할 수 있도록 가져온다.
    - name: Retrieve most recent ECS task definition JSON file
      id: retrieve-task-def
      run: |
        aws ecs describe-task-definition --task-definition [task-definition family name] --query taskDefinition > task-definition.json
        cat task-definition.json
        echo "::set-output name=task-def-file::task-definition.json"

# $GITHUB_OUTPUT
# ECS 태스크 정의에 푸시한 ECR이미지 id를 집어넣는다.
    - name: Fill in the new image ID in the Amazon ECS task definition
      id: task-def
      uses: aws-actions/amazon-ecs-render-task-definition@v1
      with:
        task-definition: ${{ steps.retrieve-task-def.outputs.task-def-file }}
        container-name: ${{ env.CONTAINER_NAME }}
        image: ${{ steps.build-image.outputs.image }}

# ECS 태스크 정의를 배포한다.
    - name: Deploy Amazon ECS task definition
      uses: aws-actions/amazon-ecs-deploy-task-definition@v1
      with:
        task-definition: ${{ steps.task-def.outputs.task-definition }}
        service: ${{ env.ECS_SERVICE }}
        cluster: ${{ env.ECS_CLUSTER }}
        wait-for-service-stability: true

이미지를 푸시할 ECR 이미지를 2개로 나눈 이유

 우린 ECR에 이미지를 푸시한 후 배포할 때, latest태그가 붙어있는 이미지를 배포하는 것으로 aws.json의 태스크 정의 파일을 작성했다.

 그런데 ECR 레포지토리에 푸시된 이미지를 보니 가장 최근에 배포된 latest태그가 붙어있는 이미지를 제외하고 나머지 이미지들은 태그가 붙어있지 않고있다는 것을 확인해 각 배포되는 이미지에 Unique한 값을 가진 태그를 붙여 서로 구별이 가능하고 만약의 경우에 태그를 지정해서 이미지 작업을 할 수 있도록 했다.

 

그리고 task.json파일에는 aws리소스의 주소같은 크리티컬한 정보들이 담겨있기 때문에 민감한 정보들의 노출을 최소화하기 위해서 가장 최근에 사용했던 task.json파일을 사용해서 인프라를 배포하도록 코드를 작성했다.

 

Lambda 이미지 배포 yaml 코드

name: Deploy to Auth Lambda

on:
  push:
    branches: [ "dev" ]
    paths: 
      - 'Auth/**'

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
     working-directory: "./Auth"  
    steps:
    - name: Checkout
      uses: actions/checkout@v3  # 최신 소스 코드를 체크아웃합니다.

    - name: Set up Node.js
      uses: actions/setup-node@v2
      with:
        node-version: 14  # 사용할 Node.js 버전을 지정합니다.

    - name: Install dependencies
      run: npm ci  # 필요한 의존성을 설치합니다.

    - name: Package Function
      run: zip -r function.zip *  # 필요한 파일들을 압축합니다.
      working-directory: ${{ env.working-directory }}
    
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}  # AWS_ACCESS_KEY_ID를 GitHub Secrets에서 가져옵니다.
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}  # AWS_SECRET_ACCESS_KEY를 GitHub Secrets에서 가져옵니다.
        aws-region: ap-northeast-2  # AWS region을 지정합니다.

    - name: Deploy to Lambda
      run: aws lambda update-function-code --function-name <람다 함수 이름> --zip-file fileb://function.zip  # Lambda 함수 이름
      working-directory: ${{ env.working-directory }}

 

S3 정적웹사이트 배포 yaml 코드

name: workflow for S3 Deploy

on:
  push:
    branches: [ "dev" ]
    paths: 
      - 'Front/**' 

env:
  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
  S3_BUCKET_NAME: ${{ secrets.S3_BUCKET_NAME }}
  S3_BUCKET_REGION: [region]
      
jobs:
  run:
    runs-on: ubuntu-latest
    env:
     working-directory: "./Front"  
    
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }}  # AWS_ACCESS_KEY_ID를 GitHub Secrets에서 가져옵니다.
          aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }}  # AWS_SECRET_ACCESS_KEY를 GitHub Secrets에서 가져옵니다.
          aws-region: ${{ env.S3_BUCKET_REGION }}  # AWS region을 지정합니다.

      - name: Deploy to S3
        run: |
          aws s3 sync ./ s3://${{ env.S3_BUCKET_NAME }}/
        working-directory: ${{ env.working-directory }}  # 정적 웹 페이지 파일이 위치한 디렉토리를 지정합니다.

 CI/CD 파이프라인을 만들면서 개발을 하면서 실제 리소스에서도 잘 돌아가는지 테스트하기 위한 dev브랜치와 진짜 서비스 제공을 위해서 배포하기 위한 버전 코드가 올라가는 main브랜치로 구분해서 CI/CD를 구현하려고 했습니다.
 dev브랜치 CI/CD가 배포하는 리소스와 main 브랜치 CI/CD가 배포하는 리소스를 구분해서 배포할 수 있다면 확실하게 개발과 프로덕션 파트를 분리할 수 있을 것이라고 생각한다.


CI/CD 파이프라인 구현을 진행하며

 CI/CD파이프라인 구현을 진행하면서 단순히 깃헙에 올라온 코드를 클라우드 리소스 상에 배포하는 것 뿐만 아니라, 개발 - 프로덕션의 단계를 구분해서 배포를 하기위해서는 어떻게 하는 것이 좋을까 제대로 고민해볼 수 있었던 시간이었다.

 그리고 한번은 이상한 코드가 dev브랜치의 Front디렉토리 안으로 push되어 배포까지 진행돼버려서 Route53도메인으로 접속했을 때 요상한 화면이 출력된 적이 있는데, 만일 production와 dev의 구분없이 단일 브랜치로 레포지토리 구성을 했다면 production단계에서 앞서 말한 것 같은 끔찍한 일이 생겼을 것이다.

 그래서 dev - production을 구분해서 작업하는 것이 굉장히 중요하다는 것을 느꼈고, 어떻게 하면 잘 구분해서 사용할 수 있을지 앞으로도 더 고민해봐야할 숙제다.

 

리소스 아키텍처


 

리소스 아키텍처 설명

1. 먼저 사용자는 Route53에서 배포된 도메인을 웹브라우저를 통해 접속한다. Route53이 사용자로부터 요청을 받으면, Route53은 CloudFront에서 캐싱한 S3의 정적웹페이지를 사용자에게 보여준다.

 

2. API GateWay는 정적 웹페이지로부터 오는 트래픽을 메서드와 도메인 엔드 포인트 별로 분류해 로그인 요청 데이터는 인증 계층 Lambda로 보내고, 작업관리 요청 데이터는 VPC 경계의 ALB로 보낸다.

   이로써 ALB는 외부에 노출되지 않습니다. 반응 트래픽은 API Gateway를 통해 원래의 클라이언트로

  반환된다. 

 

3. ALB(Application Load Balancer)의 주소는 외부에 노출되지 않고, API Gateway로부터 들어오는 요청을 현재 가동중인 ECS 서비스에 속한 EC2 컨테이너들로 보낸다. 응답 트래픽은 API Gateway를 통해 원래의 클라이언트로 반환된다. 

 

4. 작업 관리를 위한 CRUD 트래픽은 ECS에 배포된 컨테이너의 EC2와 주고 받는다. 사용자가 작업 관리 페이지에서 생성, 읽기, 업데이트, 삭제 (CRUD) 작업을 요청할 때, 이러한 요청은 ECS (Elastic Container Service)에서 실행되는 컨테이너로 전송된다.

 

5. Aurora와 통신하며, 저장된 작업 데이터를 사용해 작업 관리 페이지에서 들어오는 요청을 처리한다. ECS의 각 인스턴스는 Amazon Aurora 데이터베이스와 연결되어 있으며, 사용자의 요청에 따라 데이터베이스에서 정보를 추출하거나 업데이트한다.

 

6. ECS 인스턴스는 Amazon CloudWatch를 사용하여 ECS 인스턴스의 성능과 사용량을 실시간으로 모니터링 할 수 있다. 이를 통해 서비스의 건강 상태를 실시간으로 추적하고 필요한 경우 적절한 조치(ex. ASG 인스턴스 조절)를 취할 수 있도록 해준다.

 

7. Log DynamoDB는 사용자 요청에 따른 로그를 저장한다, Aurora에서 CRUD 작업이 처리될 때마다, 사용자의 모든 요청에 대한 작업관리 로그 데이터는 DynamoDB에 저장된다. 이를 통해 작업에 어떤 변동사항이 있는지 등에 대한 유용한 정보를 제공한다.

 

8. EventBridge에서 설정한 규칙에 따라 Log DynamoDB의 로그가 필터링된다. Amazon EventBridge는 Log DynamoDB저장된 로그 데이터를 필터링하고 분석하는 역할을 합니다. 설정된 규칙에 따라 특정 이벤트에 대한 알림을 Lambda로 보내다.

 

9. Lambda를 통해 SES 자격증명 생성으로 보안 인증된 이메일에 로그 기록을 전송한다. 필터링된 로그 데이터는 Lambda 함수를 사용하여 사용자의 이메일로 전송된다. 이를 통해 사용자는 중요한 이벤트에 대한 알림을 즉시 받을 수 있다.

 

그리고 Git Action은 GitHub 레포지토리에 push된 코드들을 자동으로 각 코드들이 배포되어야할 리소스로 배포한다.  CRUD이미지는 ECS 컨테이너로, 로그인요청처리와 로그이벤트코드는 각각의 람다 함수로, 프론트 웹페이지 코드는 s3 버킷으로 배포되도록 CI/CD 파이프라인을 구성할 계획이다.

 

 


 

리소스 아키텍처 구상 중 토의

Issue #1


API Gateway vs Load Balancer - 리소스 아키텍처 부분에서 최대한 고가용성을 확보하기 위해 다양한 기능을 제공하는데 특화된 API Gateway 보다는, 안정적인 트래픽 분산을 시킬 수 있어 서비스 제공을 안정적으로 유지하는데에 특화된 Load Balancer를 사용하는 것에 대한 논의

  • Load Balancer는 트래픽 분산에 특화되어 있고, 단순성을 생각했을 때 관리가 용이하며, 비용에서도 API Gateway와 거의 차이가 없기 때문에 Load Balancer를 사용하기로 결정하였다.

 

Issue #2


RDS 가용성 - Multi-AZ 기능을 사용해서 이중화 DB를 구성하여 만일 기존 DB 인스턴스에 중단이 발생했을 때, 자동으로 다른 가용 영역에 있는 복제본으로 스위칭시켜 서비스를 계속 제공할 수 있도록 하는 것에 대한 논의

  • RDS는 다중 AZ 배포와 자동 백업, 복구 등의 기능이 보장되고, 다중 AZ를 사용하면 트래픽을 오프로드하고 성능을 향상시키는 데에 도움이 될 것이라고 생각해 RDS 이중화를 사용하려했다
  • 물론, Aurora는 메인 DB를 Read, Write 할 수 있고 Sub DB들도 Read권한이 있어 트래픽이 분산될 뿐만 아니라 리소스의 낭비도 RDS에 비해 적기 때문에, RDS의 사용량에 따른 비용과 Aurora 비용을 비교하여 사용할 수 있다.  하지만, 프로젝트에서 사용할 수 있는 예산 문제로, Aurora 사용이 어려울 것 같아 RDS로 사용하게 되었다.

 

Issue #3


DynamoDB 활용 - 이벤트 로그 저장소 DB는 작업 변경 로그 메시지를 저장하는 것이 목적이기 때문에 매번 상황에 따라 형식과 내용이 바뀐다. 따라서 로그 메시지를 유동적으로 저장하기 위해 속성의 변경과 추가가 자유로운 DynamoDB를 사용하는 것이 어떨까? DynamoDB는 데이터가 key-value 형태로 저장되기 때문에 read 속도도 빨라 접속이 많이 발생해도 견딜 수 있다.

  • DynamoDB는 성능과 편의성에서도 용이하고, 구현할 아키텍처는 적은 양의 로그 데이터를 처리하기 때문에 처리 속도가 빠른 DynamoDB가 효과적이다. 또한 DynamoDB는 AWS Lambda와 같은 이벤트 기반 처리 시스템과 잘 통합되어 실시간 로그 분석과 같은 복잡한 로그 처리 작업 또한 쉽게 구현할 수 있게 해준다.

 

Issue #4


SNS + SQS vs Eventbridge - 기존에 사용해보아서 익숙한 SNS를 사용할지, 새로 접하지만 필터링 기능이 있어 이벤트 관리가 용이한 Eventbrige를 사용할지에 대한 논의

 

Choosing between messaging services for serverless applications | Amazon Web Services

Messaging is an important part of serverless applications and AWS services provide queues, publish/subscribe, and event routing capabilities. This post reviews the main features of SNS, SQS, and EventBridge and how they provide different capabilities for y

aws.amazon.com

 

Issue #5


ASG (EC2) vs Fargate - Fargate도 서버리스로 구동이 되고, 리소스 사용률이 높을수록 비용 방면에서 효율이 좋지만, ASG를 이용할 시, 인프라 관리가 어렵고 운영이 복잡할 수 있다.

  • ECS의 컨테이너를 구동할 때 사용하는 컴퓨팅 유닛으로 EC2를 사용하는 것이 리소스 사용률이 낮을 경우에는 Fargate가 EC2보다 평균적으로 비용이 13~18% 정도 비싸고, Cloudwatch를 통해서 리소스 예약률을 90% 이상 높게 유지하도록 auto scaling을 하도록 만들면 더 비용 절감을 할 수 있다. 따라서 EC2를 이용하기로 결정했다.
  • 참고자료 : https://youtu.be/-3YgdBpCN60
  • 리소스를 구현하고 아키텍처 컨셉이 끝난 뒤에 Fargate가 초소형 테스팅 환경에서는 EC2보다 더 우월하다는 내용을 담은 AWS공식 문서를 발견했다.... 이것을 보면 Fargate로 아키텍처를 정했어도 괜찮았을 것 같다....
  • https://containersonaws.com/introduction/ec2-or-aws-fargate/
 

EC2 or AWS Fargate?

AWS offers two ways to run containers: on EC2 and in AWS Fargate. Which one is right for your application?

containersonaws.com


리소스 아키텍처 설계를 진행하며..

 요구사항을 충족하기 위해서 요구사항의 기능을 만족시키기 위해 어떤 리소스를 사용하는 것이 좋을지 팀원들과 많은 토의를 했다.

 물론 기존에 알고있던 지식만으로 어떤 리소스를 사용할지 결정한다는 것은 너무나 오만한 발상이기 때문에, 한 리소스를 사용하기로 정하더라도 사용하는 확실한 근거를 만들기 위해서 자료를 조사하고 팀원들과 토의하는 과정을 거치려고 노력했다.

 그 결과 스스로도 리소스들이 그냥 해당기능을 구현할 수 있기 때문에 사용하는 것이 아니라, "이러이러한 특성이 있기 때문에 어떠한 장점이 있어서 해당 기능을 구현할 때 이 리소스를 사용했다" 라고 어느정도는 말할 수 있게 되었다고 생각해 뿌듯했다.

Final Project 시작

DevOps부트캠프의 파이널 프로젝트가 시작됐다.

프로젝트의 첫단계인 시나리오 선정과 요구사항 분석, 설계에 대해서 정리했다.

 

시나리오 선정 및 요구사항

팀원들과 의논하는 과정을 거쳐서 우리는 작업 관리 시스템 시나리오를 선택했다. 

시나리오 분석

DDD 설계 

 

 엔지니어님의 조언과 요구사항을 참고해 도메인 이벤트를 붙이고 이후에 액터, 커멘드 포스트잇을 붙여서 프로젝트를 구현할 때 어떤 부분에 대해서 미리 팀원들간의 토의를 해야하는지 파악하고 기록해뒀다. 
 외부시스템은 사용하지 않을 것이기에 제외했고, 에그리거트와 정책으로 도메인들을 분리하고 연결했다.

 

그렇게 사용자의 인증을 담당하는 User Management, 작업을 관리하는 Task Management Service, 그리고 작업과 시스템의 변동사항을 기록하고 변동사항에 알맞는 알림을 보내주는 Log Mornitoring System으로 분리했다.

 

ERD 설계

각 분리된 서비스 별로 사용할 데이터베이스의 기술 스택을 정했다.

 Log Mornitoring System와 User Management 파트에서는 차후 유연성 및 분산환경에서 확장성, 내결함성을 더 잘 지원할 수 있는 NoSQL을 사용하고,

 사용자로부터 가장 많은 요청을 처리해야하는 Task Management Service 파트에서는 MySQL을 사용하기로 결정했다.

 

 ERD를 작성하면서 Task에 반영되는 User 역할을 어떻게 구분할 것인가에 대한 논의를 한 결과 Task에서 Supervisor_email과 PIC_email 속성을 만들어 User table을 참조하도록 만들기로 했다.

 그리고 우린 백엔드 프로젝트를 진행하는 것이 아니기 때문에 Users 테이블에는 인증을 위한 최소한의 데이터만 사용도록 했다.

 

기술 스택

CRUD 문서 작성

 엔지니어께 CRUD 문서를 작성해 실제 구현단계에서의 실수를 방지하는 것이 좋다는 조언을 듣고, 구현할 api의 CRUD 문서를 작성했다. 문서를 팀원들과 정리하며 정말 필요한 기능이 무엇인지 생각해볼 수 있었고, 정말 필요한 기능들이 무엇인지 파악해 CRUD문서를 작성했다. 


요구사항 분석, 설계를 진행하며..

2023.04.05 - [DevOps] - [DevOps] 부트캠프 1차 팀프로젝트 회고

 이전 1차 팀프로젝트를 진행하면서 깨달은 초반 프로젝트 방향성을 확실하게 정리해야한다는 점을 마음에 세기고 도메인 주도 설계, 시나리오 분석을 하는 과정에서 팀원들과 최대한 많은 논의를 거쳐 프로젝트의 구조를 구체화 시키고 그 내용을 팀원들과 나누려고 노력했다.

 그 결과 GitHub에서는 최대한 코드 충돌을 줄이기 위해서 추후 CI/CD를 해야할 부분별로 브랜치를 나눠 Task, Auth, Front, Log 4개 Branch로 나눠 개발을 진행한 후 브랜치를 병합할 때 코드리뷰를 진행해 혹시모를 불상사를 예방하기로 했다.

 그 뒤에 1차적인 코드구현이 끝나고 클라우드 리소스에서 구상한 서비스가 작동하고있는 상태가 되면, 기존 브랜치들을 통합한 뒤에 실제 서비스 제공의 목적을 가진 production 브랜치와 개발되고있는 서비스의 코드가 올라가는 dev브랜치로 분리하기로 했다.

모니터링의 목표

CI/CD 파이프라인 마지막 Stage는 운영이다. 서비스에 생길 수 있는 현황을 파악하고 문제를 모니터링하는 과정으로 대표된다. 그렇다면 어떤 지표를 수집하고, 어떤 메트릭을 기준으로 삼아야 하지?

메트릭?

메트릭은 시간에 따라 측정한 결과값이다. 

예를 들어서 시간당 CPU 사용률, 연간 순매출처럼 시간이라는 차원이 함께 적용되어야 한다. 시간이 아닌 다른 차원(예를 들어, 서비스 별 매출)을 기준으로 삼을 수도 있다.

모니터링의 목표

모니터링을 통해 얻고자 하는 것은 다음과 같다.

  • 시간을 기준으로 측정되는 주요 메트릭을 최소화하여 고가용성 달성
  • 사용량을 추적하여, 배포에 앞서 세운 가설을 검증하고 개선

주요 벤더들이 이야기하는 모니터링의 목표와 메트릭

구글이 이야기하는 모니터링의 목표는 다음과 같다.

  • 장기적인 트렌드 분석
    • 데이터베이스가 얼마만큼의 용량을 차지하며, 얼마나 빨리 용량이 증가하는가?
    • DAU(일간 활성 사용자수)는 얼마나 빨리 증가하는가?
  • 시간의 경과 및 실험 그룹 간의 비교
    • 어떤 데이터베이스를 썼을 때 쿼리가 빠른가?
    • 캐시용 노드를 추가했을 때, 캐시 적중률(hit rate)이 얼마나 향상되는가?
    • 지난주보다 사이트가 얼마나 느려졌는가?
  • 경고
    • 인프라의 어떤 부분이 고장 났는가? 혹은 고장 날 수 있는가?

레퍼런스: http://docs.microsoft.com/ko-kr/azure/data-explorer/using-metrics

 

메트릭을 사용하여 Azure Data Explorer 성능, 상태 및 모니터링 - Azure Data Explorer

Azure Data Explorer 메트릭을 사용하여 클러스터의 성능, 상태 및 사용량을 모니터링하는 방법을 알아봅니다.

learn.microsoft.com

마이크로소프트에서는 어떤 메트릭을 볼까? 위 레퍼런스에서 알려주는 Azure 서비스에서 측정하는 메트릭의 주요 예는 다음과 같다.

  • 캐시 사용률
  • CPU, Memory
  • 인스턴스의 개수
  • 연결 유지

레퍼런스: docs.microsoft.com/ko-kr/azure/data-explorer/using-metrics

 

Google - Site Reliability Engineering

Monitoring Distributed Systems Written by Rob EwaschukEdited by Betsy Beyer Google’s SRE teams have some basic principles and best practices for building successful monitoring and alerting systems. This chapter offers guidelines for what issues should in

sre.google

앞서 살펴본 바와 같이 주요 메트릭은,

단일 노드일 경우 리눅스를 통해 측정할 수 있다. 클러스터 형태, 즉 여러 대의 노드로 구성되어 있는 경우, AWS 콘솔(CloudWatch 등)을 통해 이미 제공되고 있는 경우가 많다. 이번 유닛에서는 단일 노드에서의 측정과 함께, 쿠버네티스 클러스터 상에서의 모니터링 과정을 살펴본다.

모니터링 구분

어떠한 서비스가 제대로 작동되는지 확인하려면, 서비스 또는 시스템과 관련한 모든 변수들을 모니터링해야 한다.

하지만 우리가 서비스 모니터링을 위해 날씨나 데이터센터의 전력 공급에 신경 쓰지 않는다. 반대로, 발생하는 모든 메트릭을 모니터링하지 않는다. 모든 메트릭을 실시간으로 보는 것은 불가능하고, 너무 많은 메트릭을 모니터링하다 보면, 중요한 신호를 발견하기도 어렵다.

따라서 모니터링할 때에는 단계를 구분해서 계층적으로 해야 한다.

블랙박스 모니터링과 화이트박스 모니터링

블랙박스와 화이트박스의 구분은 박스를 기준으로 관찰자가 밖에서 바라보느냐, 안에서 바라보느냐의 차이다. 박스는 애플리케이션이 될 수도 있고, 쿠버네티스 시스템이 될 수도 있다.

  • 블랙박스 모니터링은 CPU/메모리/스토리지 등 인프라 수준의 모니터링에 유용하다. 쿠버네티스 시스템의 경우, 클러스터 정상 작동 여부 등 쿠버네티스 컴포넌트 그 자체를 모니터링하는 것도 블랙박스 모니터링에 해당한다.
  • 그러나, 애플리케이션 로직과 관련된 문제나 애플리케이션 내부 로직에 대한 세부적인 모니터링을 할 때는 화이트박스 모니터링이 더 효과적이다.

이렇게 서비스의 건강과 성능을 파악하고 개선하기 위해 모니터링은 필수적이다. 주요 메트릭을 잘 파악하고 적절한 도구와 전략을 사용하면 서비스의 품질을 획기적으로 향상시킬 수 있다.

[주제 1] 람다를 모니터링 하려는 경우, 메트릭을 활용해 어떤 질문이 나올 수 있을까요? 

AWS 람다를 모니터링하기 위해 주목해야 할 메트릭은 크게 5가지로 나눌 수 있다.

 

실행시간(duration): 함수가 작동하는데 어느정도의 시간이 걸리는지 알면 함수의 전체적인 성능이 어떤지 파악할 수 있다.


 호출(Invocations): 함수가 얼마나 자주 호출되는지를 알면 애플리케이션 활동과 함수의 전체적인 성능 추이가 어떻게 되는지 파악할 수 있다. 서비스 중단이나 AWS 서비스 문제 등이 있다면 호출 횟수가 갑자기 감소하는 현상을 볼 수도 있다.

 죽은 편지 에러(Dead-letter Errors): 비동기적으로 호출되거나 이벤트 소스 맵핑에서 호출되는 함수는 처리되지 않은 이벤트를 처리하기 위해 데드-레터 큐(DLQ)를 사용하는데, 여기서 데드-레터 에러 메트릭은 람다가 이벤트를 DLQ에 보낼 수 없는 횟수를 추적한다.

 동시성(Concurrency): 동시성 메트릭을 모니터링하면 과다 프로비저닝된 함수를 관리하고, 애플리케이션 트래픽의 흐름을 지원하기 위해 함수를 확장할지 안할지를 결정할 수 있다.


 프로비저닝된 동시성(Provisioned Concurrency): 람다는 필요할 때만 코드를 실행하기 때문에, 함수가 오랜 시간 동안 사용되지 않은 경우 추가적인 대기 시간(콜드 스타트)이 발생할 수 있다는 단점이 있다. 이 문제를 해결하기 위해 프로비저닝된 동시성을 사용하여 함수를 미리 초기화하면, 미리 요청 처리 준비를 할 수 있다.

 스로틀(Throttles): 요청이 들어오면, 함수는 미리 프로비저닝된 동시성 풀 또는 그 이상에서 스케일링하여 수요를 감당한다. 이때, 동시성 풀을 전부 사용해버리면 람다는 그 지역의 모든 함수를 스로틀링하고 들어오는 모든 요청을 거부한다.

 


나올 수 있는 질문들


람다를 효과적으로 모니터링하려면 이러한 메트릭을 이해하고 관찰해야 한다. 그래서 각 메트릭이 어떤 정보를 제공하고 어떻게 사용되는지에 대해 알면 알수록 문제가 발생했을 때 더 적절한 대처를 할 수 있다.

 

그래서 중요 메트릭에 대해서 간단하게 알아보았으니 어떤 질문들이 나올 수 있는지 한번 생각해봤다.

 


 

1. 특정 함수의 실행시간이 얼마나 되는가? : 당연하게도 함수의 성능을 파악하는데 매우 중요한 지표기 때문에, 실행 시간을 직접 관찰하면서 해당 람다에 추가적인 최적화 작업이 필요한지 알아볼 수 있다.

 

2. 함수가 얼마나 호출되고 있는가? : 함수가 얼마나 자주 호출되는지를 알면 애플리케이션 활동과 함수의 전체적인 성능 추이가 어떻게 되는지 파악할 수 있다. 서비스 중단이나 AWS 서비스 문제 등이 있다면 호출 횟수가 갑자기 감소하는 현상을 볼 수도 있다. 

 

3. 데드레터 큐로 이동하지 못한 이벤트의 수(dead-letter errors)는 얼마나 되는가? : 람다가 이벤트를 DLQ로 보내는데 실패한 갯수다. 이것을 파악하면 시스템에서 문제가 발생하는 부분을 찾는데 중요한 단서로 사용할 수 있다.

 


 

[주제 2] 쿠버네티스에 어떤 파드가 Pending 상태에 머물러있다면, 어떤 계층부터 살펴보아야 할까요? 이 경우는 파드가 Running 상태인데 잘 작동하지 않는 경우랑은 어떻게 다른가요?

파드 상태: 'Pending' vs 'Running'

먼저, 쿠버네티스 파드의 'Pending' 상태와 'Running' 상태에 대해 이해할 필요가 있다.

'Pending' 상태는 파드가 쿠버네티스 시스템에 의해 스케줄되었지만, 아직 하나 이상의 컨테이너가 노드에 배포되지 않은 상태를 말한다. 이는 일반적으로 리소스 부족, 볼륨 마운트 문제, 허용된 톨러레이션 미달 등과 같은 이슈 때문에 발생한다.

반면에, 'Running' 상태는 모든 파드의 컨테이너가 성공적으로 스케줄링되고 시작된 상태를 의미하는데, 'Running' 상태의 파드가 정상적으로 작동하지 않는다면, 일반적으로 애플리케이션 수준의 오류나 컨테이너 실행 환경 문제 등이 원인일 가능성이 높다.

그래서 파드가 'Pending' 상태에 머물러 있는 경우와 'Running' 상태인데 잘 작동하지 않는 경우는 문제를 해결하기 위해 검사해야 하는 부분이 다르다고 볼 수 있다.

 


 

파드 'Pending' 상태 문제 분석

'Pending' 상태의 파드를 해결하려면 아래 요소들을 확인해봐야한다.

1. 클러스터 및 노드 상태 확인:
쿠버네티스 클러스터와 노드가 정상적으로 작동하고 있는지 확인해야 한다. kubectl get nodes 명령어를 사용하여 노드 상태를 확인할 수 있고, 충분한 리소스(CPU, 메모리, 스토리지 등)가 있는지 확인해야 한다.

2. 파드 스케줄링 및 톨러레이션 확인:
파드의 스케줄링 정책과 톨러레이션을 확인해야 한다. 노드 셀렉터, 노드 어피니티, 테인트 및 톨러레이션 등이 올바르게 설정되어 있는지 확인해주면 된다.

3. 파드 볼륨 및 컨피그맵 확인:
파드가 필요로 하는 볼륨이나 컨피그맵이 존재하는지, 올바르게 마운트되어 있는지 확인해야 한다.

4. 파드 이벤트 로그 분석:
마지막으로, 파드의 이벤트 로그를 분석해줘야한다. kubectl describe pod <파드 이름> 명령어를 사용하여 파드의 상세 정보와 이벤트 로그를 확인할 수 있다.

'DevOps' 카테고리의 다른 글

[DevOps] 최종 프로젝트 회고 - 요구사항 분석  (0) 2023.06.12
[DevOps] 모니터링  (0) 2023.06.08
[DevOps] DOB - Project3  (0) 2023.05.30
[DevOps] 5/4 TIL : 마이크로서비스 Domain  (1) 2023.05.07
[DevOps] 프로젝트 2 진행  (0) 2023.04.30

+ Recent posts