Terraform를 통한 불변 인프라(Immutable Infra) 구현

mDev_97·2023년 11월 6일

Terraform

목록 보기
4/4
post-thumbnail

안정성 확보를 위해 제일 먼저 확보되어야 하는 건 멱등성(idempotent)입니다.
즉, 같은 과정을 통해 같은 결과가 나와야 한다는 보장입니다.

이를 위해서 Infrastructure를 설계하는 Architect는 코드형 인프라(IaC), CI/CD 파이프라인으로
대표되는 다양한 장치를 마련합니다.

이를 위해서 이번 게시글에서는 IaC 중 하나인 Terraform과 CI/CD 파이프라인을 통해
직접 인프라를 구성하고, Tomcat 서버를 배포하는 프로젝트를 진행해보겠습니다.

IaC를 통해 Infra 구성

코드형 인프라(IaC) 중 하나인 Terraform을 사용하여 구축할 인프라는 다음과 같습니다.
아래의 인프라를 과정에 따라 차근차근 진행해보겠습니다.

Provider 지정

이번 프로젝트에서는 AWS 환경에 인프라를 구축할 것이기 때문에 AWS를 사용하기 위한
Provider를 설치할 수 있도록 합니다.

# main.tf

# Provider 작성
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-2" # Region 서울 
}

VPC 생성

다음으로는 논리적으로 격리된 가상 네트워크에서 AWS 리소스를 시작하기 위해 VPC를 구축하는 것입니다.

VPC는 자체 데이터 센터에서 운영하는 기존 네트워크와 아주 유사한 가상 네트워크
CIDR 블록 방식으로 IP 대역을 설정합니다.

# vpc.tf

# VPC 생성하기
resource "aws_vpc" "groomVPC" {
  cidr_block = "10.10.0.0/16"

  tags = {
    Name = "groomVPC"
  }
}

서브넷(Subnet) 구성

VPC를 생성한 후에는 서브넷을 추가할 수 있습니다.

서브넷은 VPC 대역 안의 IP 주소 범위를 CIDR 블록 방식으로 지정합니다.
그렇기 때문에 서브넷은 단일 가용 영역에 상주해야 하고, 서브넷을 추가한 후에는 VPC에 리소스를 배포할 수 있습니다.

보통은 하나의 Availability Zone을 외부에서 통신할 수 있는 Public Subnet
외부에서 통신할 수 없는 Private Subnet으로 나뉩니다.

우리 프로젝트에서는 Public Subnet이 2개, Private Subnet이 4개 필요하므로
아래의 코드와 같이 작성해줍니다.

# vpc.tf

...
...

# Web Subnet (Public) - 2개
resource "aws_subnet" "public_web_a" {
  vpc_id            = aws_vpc.groomVPC.id
  cidr_block        = "10.10.1.0/24"
  availability_zone = "ap-northeast-2a"

  tags = {
    Name = "public_web_a"
  }
}

resource "aws_subnet" "public_web_c" {
  vpc_id            = aws_vpc.groomVPC.id
  cidr_block        = "10.10.2.0/24"
  availability_zone = "ap-northeast-2c"

  tags = {
    Name = "public_web_c"
  }
}

# App Subnet (Private) - 2개
resource "aws_subnet" "private_app_a" {
  vpc_id            = aws_vpc.groomVPC.id
  cidr_block        = "10.10.101.0/24"
  availability_zone = "ap-northeast-2a"

  tags = {
    Name = "private_app_a"
  }
}

resource "aws_subnet" "private_app_c" {
  vpc_id            = aws_vpc.groomVPC.id
  cidr_block        = "10.10.102.0/24"
  availability_zone = "ap-northeast-2c"

  tags = {
    Name = "private_app_c"
  }
}

# DB Subnet (Private) - 2개
resource "aws_subnet" "private_db_a" {
  vpc_id            = aws_vpc.groomVPC.id
  cidr_block        = "10.10.201.0/24"
  availability_zone = "ap-northeast-2a"

  tags = {
    Name = "private_db_a"
  }
}

resource "aws_subnet" "private_db_c" {
  vpc_id            = aws_vpc.groomVPC.id
  cidr_block        = "10.10.202.0/24"
  availability_zone = "ap-northeast-2c"

  tags = {
    Name = "private_db_c"
  }
}

Internet Gateway(IGW) 구성

인터넷 게이트웨이는 수평 확장되고 가용성이 높은 중복 VPC 구성 요소로, VPC와 인터넷 간에 통신할 수 있게 해줍니다.

인터넷 게이트웨이를 사용하면 퍼블릭 서브넷의 리소스가 인터넷에 연결할 수 있고,
IP 주소를 사용하여 서브넷의 리소스에 대한 연결을 시작할 수 있습니다

인터넷 게이트웨이는 VPC 라우팅 테이블에서 인터넷 라우팅 가능 트래픽에 대한 대상을 제공합니다.

# vpc.tf

...
...

# Internet Gateway
resource "aws_internet_gateway" "internet_gw" {
  vpc_id = aws_vpc.groomVPC.id

  tags = {
    Name = "internet_gw"
  }
}

탄력적 IP 및 NAT Gateway 생성

NAT 게이트웨이는 NAT(네트워크 주소 변환) 서비스입니다.
프라이빗 서브넷의 인스턴스가 VPC 외부의 서비스에 연결할 수 있지만,
외부 서비스에서 이러한 인스턴스와의 연결을 시작할 수 없도록 NAT 게이트웨이를 사용합니다.

퍼블릭 NAT 게이트웨이의 경우 인터넷 게이트웨이는 퍼블릭 NAT 게이트웨이의 프라이빗 IPv4 주소를 NAT 게이트웨이와 연결된 탄력적 IP 주소에 매핑합니다.

# vpc.tf

...
...

# Elastic IP for NAT Gateway
resource "aws_eip" "ngw_ip" {
  domain = "vpc"

  lifecycle {
    create_before_destroy = true
  }
}

# NAT Gateway
resource "aws_nat_gateway" "nat_gateway" {
  allocation_id = aws_eip.ngw_ip.id
  subnet_id     = aws_subnet.public_web_a.id

  tags = {
    Name = "NAT Gateway"
  }

  depends_on = [aws_internet_gateway.internet_gw]
}

이렇게 VPC와 VPC의 구성요소들을 선언해주어 네트워크 구성을 하였습니다.

Security Group 생성

Security Group은 VPC의 리소스와 주고받을 수 있는 트래픽을 제어하는 방화벽 역할을 합니다.
인바운드 트랙픽 및 아웃바운드 트래픽을 허용하는 포트 및 프로토콜을 선택할 수 있습니다.

# sg.tf

# Security Group
resource "aws_security_group" "groomVPC-sg" {
  name   = "groomVPC-sg"
  vpc_id = aws_vpc.groomVPC.id

  ingress {
    from_port        = 22
    to_port          = 22
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }

  ingress {
    from_port        = 80
    to_port          = 80
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }

  ingress {
    from_port        = 8080
    to_port          = 8080
    protocol         = "tcp"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }

  egress {
    from_port        = 0
    to_port          = 0
    protocol         = "-1"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }
}

Load Balancer 생성

Applicaion Load Balancing(ALB)은 수신 애플리케이션 트래픽을 여러 가용 영역에 있는
EC2 인스턴스와 같은 여러 대상에 분산합니다.

Listener는 사용자가 구성한 프로토콜과 포트를 사용하여 클라이언트의 연결 요청을 확인합니다.
로드 밸런서에 하나 이상의 리스너를 추가하고, 리스너에 대해 정의하는 규칙에 따라 로드 밸런서가
요청을 등록된 대상으로 라우팅하는 방법이 결정됩니다.

Target Group은 사용자가 지정한 프로토콜과 포트 번호를 사용하여 EC2 인스턴스와 같은
하나 이상의 등록된 대상으로 요청을 라우팅합니다.

그럼 VPC를 통해 네트워크 구성과 시큐리티 그룹을 생성했으니 이제는 AWS 리소스들을 생성해주어야 합니다.
이를 위해서 아키텍처에 web lbapp lb라고 나와있는 로드밸런서를 생성해보겠습니다.

# lb.tf

# Web Load Balancer (Application)
resource "aws_lb" "web_lb" {
  name               = "web-lb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.groomVPC-sg.id]
  subnets            = [aws_subnet.public_web_a.id, aws_subnet.public_web_c.id]
}

# Web Load Balancer Listener
resource "aws_lb_listener" "web_lb_listener" {
  load_balancer_arn = aws_lb.web_lb.arn
  port              = "80"
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.web_lb_tg.arn
  }
}

# Web Load Balancer Target Group
resource "aws_lb_target_group" "web_lb_tg" {
  name        = "web-lb-tg"
  target_type = "instance"
  port        = 80
  protocol    = "HTTP"
  vpc_id      = aws_vpc.groomVPC.id
}

# App Load Balancer (Application)
resource "aws_lb" "app_lb" {
  name               = "app-lb"
  internal           = true
  load_balancer_type = "application"
  security_groups    = [aws_security_group.groomVPC-sg.id]
  subnets            = [aws_subnet.private_app_a.id, aws_subnet.private_app_c.id]
}

# App Load Balancer Listener
resource "aws_lb_listener" "app_lb_listener" {
  load_balancer_arn = aws_lb.app_lb.arn
  port              = "80"
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.app_lb_tg.arn
  }
}

# App Load Balancer Target Group
resource "aws_lb_target_group" "app_lb_tg" {
  name        = "app-lb-tg"
  target_type = "instance"
  port        = 80
  protocol    = "HTTP"
  vpc_id      = aws_vpc.groomVPC.id
}

Auto Scaling 생성

Auto Scaling은 애플리케이션 로드를 처리하는 데 사용할 수 있는 EC2 인스턴스의 정확한 수를 확보하는 데 도움이 됩니다.

Auto Scaling 그룹의 최소, 최대 인스턴스 수를 지정할 수 있으며, Auto Scaling은 그룹이 이 크기보다 작아지거나 초과하지 않도록 보장합니다.

Auto Scaling Group을 생성할 때는 EC2 인스턴스의 최소, 최대 갯수와 원하는 인스턴스의 수를 지정할 수 있습니다.

그룹에서는 Launch Template 또는 Launch Configuration을 EC2 인스턴스에 대한 구성 템플릿으로 사용합니다.
이 때, 구성 템플릿에는 인스턴스의 AMI ID, 인스턴스의 유형, 키 페어, 보안 그룹 등을 지정할 수 있습니다.

# asg.tf

# EC2 Key Pair
resource "tls_private_key" "rsa_4096" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

variable "key_name" {
  type        = string
  default     = "terraform_devops"
  description = "pem file"
}

resource "aws_key_pair" "key_pair" {
  key_name   = var.key_name
  public_key = tls_private_key.rsa_4096.public_key_openssh
}

resource "local_file" "private_key" {
  content  = tls_private_key.rsa_4096.private_key_pem
  filename = var.key_name
}

# Web EC2 Instance Launch Template
resource "aws_launch_template" "web_launch_template" {
  name          = "web_launch_template"
  image_id      = "ami-086cae3329a3f7d75" # 인스턴스 이미지
  instance_type = "t2.micro"              # 인스턴스 타입
  
  key_name = aws_key_pair.key_pair.key_name

  user_data = filebase64("${path.module}/server.sh")

  network_interfaces {
    associate_public_ip_address = true
    security_groups             = [aws_security_group.groomVPC-sg.id]
  }
}

# Web Auto Scaling Group
resource "aws_autoscaling_group" "web_asg" {
  name                = "web-asg"
  min_size            = 2
  max_size            = 2
  desired_capacity    = 2
  target_group_arns   = [aws_lb_target_group.web_lb_tg.arn]
  vpc_zone_identifier = [aws_subnet.public_web_a.id, aws_subnet.public_web_c.id]

  launch_template {
    id      = aws_launch_template.web_launch_template.id
    version = "$Latest"
  }
}

# App EC2 Instance Launch Template
resource "aws_launch_template" "app_launch_template" {
  name          = "app_launch_template"
  image_id      = "ami-086cae3329a3f7d75" # 인스턴스 이미지
  instance_type = "t2.micro"              # 인스턴스 타입
  key_name      = aws_key_pair.key_pair.key_name

  user_data = filebase64("${path.module}/server.sh")

  network_interfaces {
    associate_public_ip_address = false
    security_groups             = [aws_security_group.groomVPC-sg.id]
  }
}

# App Auto Scaling Group
resource "aws_autoscaling_group" "app_asg" {
  name                = "app-asg"
  min_size            = 2
  max_size            = 2
  desired_capacity    = 2
  target_group_arns   = [aws_lb_target_group.app_lb_tg.arn]
  vpc_zone_identifier = [aws_subnet.private_app_a.id, aws_subnet.private_app_c.id]

  launch_template {
    id      = aws_launch_template.app_launch_template.id
    version = "$Latest"
  }
}

Route Table 생성

로드 밸런서와 오토 스케일링 그룹을 생성하였다면 서브넷 또는 게이트웨이의 네트워크 트래픽이
전송되는 위치를 결정
하기 위해서 각 서브넷을 라우팅 테이블에 연결해야 합니다.
그렇지 않으면 서브넷이 기본 라우팅 테이블과 암시적으로 연결됩니다.

# rt.tf

# Web Route Table
resource "aws_route_table" "web_route_table" {
  vpc_id = aws_vpc.groomVPC.id

  route {
    cidr_block = "0.0.0.0/0" #인터넷 게이트웨이 
    gateway_id = aws_internet_gateway.internet_gw.id
  }

  tags = {
    Name = "Public Web Route Table"
  }
}

# Web Route Table Association
resource "aws_route_table_association" "web_rta_a" {
  subnet_id      = aws_subnet.public_web_a.id
  route_table_id = aws_route_table.web_route_table.id
}

resource "aws_route_table_association" "web_rta_c" {
  subnet_id      = aws_subnet.public_web_c.id
  route_table_id = aws_route_table.web_route_table.id
}

# App Route Table
resource "aws_route_table" "app_route_table" {
  vpc_id = aws_vpc.groomVPC.id

  tags = {
    Name = "Private App Route Table"
  }
}

# App Route Table Association
resource "aws_route_table_association" "app_rta_a" {
  subnet_id      = aws_subnet.private_app_a.id
  route_table_id = aws_route_table.app_route_table.id
}

resource "aws_route_table_association" "app_rta_c" {
  subnet_id      = aws_subnet.private_app_c.id
  route_table_id = aws_route_table.app_route_table.id
}

resource "aws_route" "private_rt_route" {
  route_table_id         = aws_route_table.app_route_table.id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id         = aws_nat_gateway.nat_gateway.id
}

resource "aws_route_table_association" "app_db_rta" {
  subnet_id      = aws_subnet.private_db_a.id
  route_table_id = aws_route_table.app_route_table.id
}

RDS 생성

이제 상단의 인프라 아키텍처 중에서 마지막인 RDS 인스턴스를 생성해보겠습니다.

RDS는 관계형 데이터베이스를 더 쉽게 설치하고 운영 및 확장할 수 있는 웹 서비스입니다.
RDS는 DB 인스턴스와 함께 다중 AZ 배포를 사용하여 DB 인스턴스에 대한 고가용성 및 장애 조치 지원을 제공합니다.

이번 프로젝트에서는 PostgreSQL을 사용하여 DB 인스턴스를 구성하고 읽기 전용 복제본(Read Replica)를 구성합니다.
PostgreSQL DB 인스턴스를 복제 원본으로 사용하기 위해서는 먼저 원본 DB 인스턴스에서 자동 백업을 활성화하고,
백업 보존 기간을 0 이상의 값으로 지정해주어야 합니다.

📌 RDS for PostgreSQL 14.1 이전 버전의 PostgreSQL 읽기 전용 복제본에 대해서는 자동 백업을 설정할 수 없습니다. 읽기 전용 복제본에 대한 자동 백업은 RDS for PostgreSQL 14.1 이상 버전에서만 지원됩니다.

우선은 RDS를 위한 Security 그룹을 추가 해주겠습니다.

# sg.tf

...
...

# RDS Security Group
resource "aws_security_group" "rds-sg" {
  name   = "rds-security-group"
  vpc_id = aws_vpc.groomVPC.id

  ingress {
    from_port       = 3306
    to_port         = 3306
    protocol        = "tcp"
    security_groups = [aws_security_group.groomVPC-sg.id]
  }

  tags = {
    Name = "rds-sg"
  }
}

RDS를 위한 시큐리티 그룹을 생성해주었으면 이를 사용하여 RDS DB 인스턴스
읽기 전용 복제본을 함께 생성해줍니다.

# rds.tf

# DB Subnet Groups
resource "aws_db_subnet_group" "db_subnet_group" {
  name       = "db-subnet-group"
  subnet_ids = [aws_subnet.private_db_a.id, aws_subnet.private_db_c.id]
}

# RDS DB Parameter Group
resource "aws_db_parameter_group" "db_pg" {
  name   = "db-pg"
  family = "postgres14"

  parameter {
    name  = "log_connections"
    value = "1"
  }
}

# RDS Instance
resource "aws_db_instance" "primary-rds-instance" {
  identifier             = "primary-rds-instance"
  instance_class         = "db.t3.micro"
  allocated_storage      = 5
  engine                 = "postgres"
  engine_version         = "14.3"
  username               = "moon"
  password               = "moonstar"
  db_subnet_group_name   = aws_db_subnet_group.db_subnet_group.name
  vpc_security_group_ids = [aws_security_group.rds-sg.id]
  parameter_group_name   = aws_db_parameter_group.db_pg.name
  #   publicly_accessible     = true
  skip_final_snapshot     = true
  multi_az                = true
  backup_retention_period = 1
}

# RDS Instance Read Replica
resource "aws_db_instance" "replica-rds-instance" {
  nchar_character_set_name = "replica-rds-instance"
  identifier               = "replica-rds-instance"
  replicate_source_db      = aws_db_instance.primary-rds-instance.identifier
  instance_class           = "db.t3.micro"
  apply_immediately        = true
  #   publicly_accessible      = true
  skip_final_snapshot    = true
  vpc_security_group_ids = [aws_security_group.rds-sg.id]
  parameter_group_name   = aws_db_parameter_group.db_pg.name
  multi_az               = true
}

결과 확인

지금까지 Terraform을 통해 AWS 리소스를 사용해 인프라를 구축할 수 있도록 하였습니다.
그럼 지금까지 작성한 내용이 정상적으로 인프라를 구축하는지 확인해보는 과정을 진행해보도록 하겠습니다.

우선 아래의 명령어를 통해서 Terraform 구성 파일이 포함된 작업 디렉터리를 초기화 해줍니다.

terraform init

이는 새 Terraform 구성을 작성하거나 버전 제어에서 기존 구성을 복제한 후에 실행해야 하는 첫 번째 명령입니다.

작업 디렉터리가 초기화 되었다면 아래의 명령을 사용하여 Terraform이 인프라에 적용하려는
변경 사항을 미리 볼 수 있는 실행 계획을 생성해보겠습니다.

terraform plan

plan 명령만으로는 작성된 변경 사항이 실제로는 수행되지 않습니다.

plan으로 실행 계획이 문제가 없다면
이제 아래의 명령어를 통해서 Terraform에 계획된 작업을 실행하도록 해줍니다.

terraform apply

RDS 인스턴스와 복제본 생성의 경우 많은 시간이 소요될 수 있습니다.

CI/CD 파이프라인 구축

지금까지는 Terraform을 통해 AWS 리소스를 사용해 인프라를 구축하였습니다.
그럼 이제 구축한 인프라에 Tomcat 서버를 배포하도록 하겠습니다.
CI/CD 파이프라인 툴로는 Jenkins를 사용하기 기존에 많은 분들이 사용하는 방식과는 다르게
EC2 인스턴스에 Docker를 설치하고, Docker Container로 Tomcat을 올리고
Tomcat Container에 Tomcat에서 제공하는 샘플 파일을 동작시키는 방식으로 진행하도록 하겠습니다.

EC2에 Docker 설치

우선 EC2 인스턴스에서 도커를 사용하기 위해서는 Docker를 설치해주어야 합니다.
물론 Jenkins에서 사용하는 Docker Pipeline 플러그인 등 다양한 방법이 있지만
이번 프로젝트에서는 CI/CD 파이프라인은 간단하게 진행하도록 하겠습니다.

그럼 EC2 인스턴스에 도커를 설치하기 위해서 구성한 EC2 인스턴스에 접속하여서
아래의 명령어를 통해서 간단하게 도커를 설치해주도록 하겠습니다.

# 패키지 업데이트
sudo apt update

# https 관련 패키지 설치
sudo apt install apt-transport-https ca-certificates curl software-properties-common

# Docker 리포지토리에 접근을 위한 gpg 키 설정
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

# Docker 리포지토리 등록
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"

# 패키지 다시 업데이트
sudo apt update

# Docker 설치
sudo apt install docker-ce

# Docker 버전 확인
sudo docker --version

위의 과정을 Terraform을 통해서 구축한 2개의 EC2 인스턴스에서 모두 실행해주시면 됩니다.

Jenkins 구성

이번 프로젝트에서도 마찬가지로 Jenkins는 GCP에서 VM 인스턴스로 Jenkins 서버를 별도로 구축하였습니다.

📌 Jenkins 서버 구성 방법은 [K8S] CI/CD Pipeline, 모니터링, 로깅 시스템 구축 프로젝트 - CI/CD 파이프라인 구축:1 게시글에서 확인하실 수 있습니다.

Jenkins 서버를 구성하셨다면 Jenkins에서 EC2 인스턴스에 배포하기 위한 몇 가지 작업들이 있습니다.

SSH Agent Plugin

EC2 인스턴스에는 SSH를 통해 접근해야 하기 때문에 Jenkins에서 제공하는 플러그인 중 하나인
SSH Agent 플러그인을 설치해주도록 하겠습니다.

[Dashboard] -> [Jenkins 관리] -> [Plugins] 이동

[Available plugins]에서 SSH Agent plugins를 설치해줍니다.

Crendentials 등록

SSH 접속을 위한 플러그인을 설치하였다면, 이제 SSH 접근을 위한 EC2 인스턴스의 키를 등록해주어야 합니다.

[Dashboard] -> [Jenkins 관리] -> [Credentials] 이동

(global) 클릭 -> [Add Credentials] 클릭

Credentials의 종류는 SSH Username with private key를 선택하고
ID는 EC2_SSH, Username은 ubuntu로 입력해줍니다.

그 다음으로는 Private Key에 Enter directly를 선택하여 Terraform을 통해서 생성된
terraform_devops라는 키를 복사하여 직접 입력해주도록 합니다.

Jenkinsfile 스크립트 작성

Jenkins의 구성을 완료하였으니 이제 CI/CD 파이프라인을 만들어 보겠습니다.

[Dashboard] -> [새로운 Item]으로 들어가서 Item의 이름을 지정하고 Pipeline으로 선택해줍니다.
Item의 이름은 terraform_ec2입니다.

이번 프로젝트에서는 자동 CI/CD를 구축하는 것이 목적이 아닌
단순하게 Jenkins를 사용해서 EC2 인스턴스에 Tomcat을 배포하는 것이 목적이기 때문에
트리거와 같은 것은 별도로 선택하지 않겠습니다.

그리고 Pipeline 부분에 직접 스크립트를 작성해주도록 하겠습니다.
Pipeline 스크립트는 다음과 같습니다.

pipeline {
    agent any
    
    environment {
        AWS_PUBLIC_IP_1 = '첫 번째 EC2 인스턴스의 퍼블릭 IP를 입력'
        AWS_PUBLIC_IP_2 = '두 번째 EC2 인스턴스의 퍼블릭 IP를 입력'
    }
    
    stages {
        stage('deploy 1') {
            steps {
                sshagent(['EC2_SSH']) {
                    sh"""
                    ssh -o StrictHostKeyChecking=no ubuntu@${AWS_PUBLIC_IP_1} '
                    sudo wget https://tomcat.apache.org/tomcat-7.0-doc/appdev/sample/sample.war
                    sudo docker run -d --name tomcat -p 80:8080 tomcat:9.0
                    sudo docker cp sample.war tomcat:/usr/local/tomcat/webapps
                    sudo rm -rf sample.*
                    '
                    """
                }
            }
        }
        
        stage('deploy 2') {
            steps {
                sshagent(['EC2_SSH']) {
                    sh"""
                    ssh -o StrictHostKeyChecking=no ubuntu@${AWS_PUBLIC_IP_2} '
                    sudo wget https://tomcat.apache.org/tomcat-7.0-doc/appdev/sample/sample.war
                    sudo docker run -d --name tomcat -p 80:8080 tomcat:9.0
                    sudo docker cp sample.war tomcat:/usr/local/tomcat/webapps
                    sudo rm -rf sample.*
                    '
                    """
                }
            }
        }
    }
}

파이프라인 스크립트에서 간단하게 설명드리자면 다음과 같습니다.

  1. EC2 인스턴스에 SSH 접속
  2. Tomcat에서 제공하는 Sample War 파일 설치
  3. Tomcat Docker Container 실행
  4. Sample War 파일을 컨테이너로 이동
  5. Sample 파일 삭제

Pipeline 실행

파이프라인 생성이 끝났다면 우측의 실행 버튼을 통해서 파이프라인을 실행해줍니다.

그럼 다음과 같이 파이프라인이 정상적으로 실행된 것을 확인할 수 있습니다.

결과 확인

지금까지 Jenkins에 구축한 CI/CD 파이프라인으로 우리가 Terraform을 활용해서 구축한
인프라에 Tomcat 서버를 구축하였는데요.

이제 마지막으로 AWS 로드밸런서의 DNS로 접근하여 정상적으로 웹 서버가 배포되었는지 확인하고
이번 프로젝트를 끝마치도록 하겠습니다.

📌 샘플로 추가한 War 파일은 /sample로 호스팅되고 있기 때문에
[로드밸런스 DNS]/sample로 접속해주시면 됩니다.

접속 시 정상적으로 배포되는 것을 확인

profile
안녕하세요. 백엔드, 클라우드, 인프라에 관심과 열정이 있는 김문성입니다. 😊

0개의 댓글