We are Architect

4. AWS: 테라폼으로 구축하는 3티어 아키텍처 구현(3) 본문

Cloud/AWS

4. AWS: 테라폼으로 구축하는 3티어 아키텍처 구현(3)

the best infra 2024. 11. 28. 17:27

 

* 퍼블릭 서브넷 구간 구성: alb(외부)

  • 클라이언트의 진입 역할 및 프록시 역할을 한다. 그래서 트래픽 부하분산 및 ip노출을 방지할 수 있다.
  • 요청을 URL 경로 기반 라우팅 할수있다는 것이 ALB 사용의 큰 이유이다.
  • 다른 가용영역으로 자동분산하여 가용성이 좋다.

# Public-ALB 리스너
resource "aws_lb_listener" "Public-ALB-listner" {
  load_balancer_arn = aws_lb.Public-ALB.arn
  port = "80"
  protocol = "HTTP"

  default_action {
    type = "forward"
    target_group_arn = aws_lb_target_group.Web-Srv-tg.arn
  }
}

# Public-ALB
resource "aws_lb" "Public-ALB" {
  name = "Public-ALB"
  internal = false
  load_balancer_type = "application"
  security_groups = [aws_security_group.alb-sg.id]
  subnets = [ aws_subnet.Public-SN1.id, aws_subnet.Public-SN2.id ]

  tags = {
    Name = "Public-ALB"
  }
}

# Public-ALB 타겟그룹
resource "aws_lb_target_group" "Web-Srv-tg" {
  name = "Web-Srv-tg"
  port = 80
  protocol = "HTTP"
  target_type = "instance"
  vpc_id = aws_vpc.vpc.id

    health_check {
    path                = "/index.html"  # 해당 파일경로로 통신이 되는지 확인.
    interval            = 30 # 30초 마다 확인.
    timeout             = 5  # 5초이내 응답.
    healthy_threshold   = 2  # 2번 연속 응답시 건강 확인.
    unhealthy_threshold = 2  # 2번 연속 불응답시 불건강 확인.
  }

}

# Public-ALB + 타겟그룹1
resource "aws_lb_target_group_attachment" "web-server-1-attachment" {
  target_group_arn = aws_lb_target_group.Web-Srv-tg.arn
  target_id        = aws_instance.Web-Srv-1.id
  port             = 80
}

# Public-ALB + 타겟그룹2
resource "aws_lb_target_group_attachment" "web-server-2-attachment" {
  target_group_arn = aws_lb_target_group.Web-Srv-tg.arn
  target_id        = aws_instance.Web-Srv-2.id
  port             = 80
}

 

 

 

* 프레젠테이션 계층 구성: 웹 서버, NLB(내부)

  • 프레젠테이션 계층은 GUI, 인터페이스를 사용자에게  제공하는 계층으로 Web서버를 통해서 사용자와 교류를 한다.
  • 외부에서 ALB 도메인으로 요청을 보내면 ALB의 경로설정 값에 따라 Web서버로 보내게 된다.(현재는 경로설정 X)

# 웹 서버1 인스턴스 구성
resource "aws_instance" "Web-Srv-1" {
  ami = "ami-0de20b1c8590e09c5"
  instance_type = "t2.micro"
  key_name = "AWS 키값"
  subnet_id = aws_subnet.Private-SN1.id
  security_groups = [
    aws_security_group.Web-srv-sg.id
  ]

  user_data = <<-EOF
    #!/bin/bash
    (
      echo "qwe123"
      echo "qwe123"
    ) | passwd --stdin root
    sed -i "s/^PasswordAuthentication no/PasswordAuthentication yes/g" /etc/ssh/sshd_config
    sed -i "s/^#PermitRootLogin yes/PermitRootLogin yes/g" /etc/ssh/sshd_config
    yum update -y
    yum install -y httpd
    systemctl start httpd
    systemctl enable httpd
    echo "Web Server 1 is Ready" > /var/www/html/index.html
  EOF

  tags = {
    Name = "Web-Srv-1"
  }

}

# 웹 서버2 인스턴스 구성
resource "aws_instance" "Web-Srv-2" {
  ami = "ami-0de20b1c8590e09c5"
  instance_type = "t2.micro"
  key_name = "AWS 키값"
  subnet_id = aws_subnet.Private-SN2.id
  security_groups = [
    aws_security_group.Web-srv-sg.id
  ]

  user_data = <<-EOF
    #!/bin/bash
    (
      echo "qwe123"
      echo "qwe123"
    ) | passwd --stdin root
    sed -i "s/^PasswordAuthentication no/PasswordAuthentication yes/g" /etc/ssh/sshd_config
    sed -i "s/^#PermitRootLogin yes/PermitRootLogin yes/g" /etc/ssh/sshd_config
    yum update -y
    yum install -y httpd
    systemctl start httpd
    systemctl enable httpd
    echo "Web Server 2 is Ready" > /var/www/html/index.html
  EOF

  tags = {
    Name = "Web-Srv-2"
  }

}

 

  • NLB를 통해서 Web서버에서 WAS서버로 요청

# Private-NLB 리스너
resource "aws_lb_listener" "Private-NLB-listner" {
  load_balancer_arn = aws_lb.Private-NLB.arn
  port = "8080"
  protocol = "TCP"

  default_action {
    type = "forward"
    target_group_arn = aws_lb_target_group.WAS-Srv-tg.arn
  }
}

# Private-NLB
resource "aws_lb" "Private-NLB" {
  name = "Private-NLB"
  internal = true
  load_balancer_type = "network"
  security_groups = [aws_security_group.Private-nlb-sg.id]
  subnets = [ aws_subnet.Private-SN3.id, aws_subnet.Private-SN4.id ]

  tags = {
    Name = "Private-NLB"
  }
}

# Private-NLB 타겟그룹
resource "aws_lb_target_group" "WAS-Srv-tg" {
  name = "WAS-Srv-tg"
  port = 8080
  protocol = "TCP"
  target_type = "instance"
  vpc_id = aws_vpc.vpc.id

  health_check {
    protocol            = "HTTP"
    healthy_threshold   = 3
    unhealthy_threshold = 3
    timeout             = 5
    interval            = 10
  }

  tags = {
    Name = "WAS-Srv-tg"
  }

}

# Private-NLB + 타겟그룹1
resource "aws_lb_target_group_attachment" "WAS-server-1-attachment" {
  target_group_arn = aws_lb_target_group.WAS-Srv-tg.arn
  target_id        = aws_instance.WAS-Srv-1.id
  port             = 8080
}

# Private-NLB + 타겟그룹2
resource "aws_lb_target_group_attachment" "WAS-server-2-attachment" {
  target_group_arn = aws_lb_target_group.WAS-Srv-tg.arn
  target_id        = aws_instance.WAS-Srv-2.id
  port             = 8080
}

 

 

 

 

* 애플리케이션 계층 구성: WAS 서버

  • 프레젠테이션 계층과 데이터 계층으로부터 요청을 주고받는 계층.
  • NLB를 통해서 온 요청이 WAS서버로 전달 되게 되고 해당 WAS서버에서는 원래는 받은 요청에 따른 데이터 값을 DB 서버에게 넘겨주고 받는 역할을 해야 한다. 
  • NLB의 도메인 및 포트로 요청 보내서 WAS서버는 요청을 받는다. 

Web서버에서 WAS서버로 직접 통신을 요청한 결과

 

# WAS 서버1 인스턴스 구성
resource "aws_instance" "WAS-Srv-1" {
  ami = "ami-0de20b1c8590e09c5"
  instance_type = "t2.micro"
  key_name = "AWS 키값"
  subnet_id = aws_subnet.Private-SN3.id
  security_groups = [
    aws_security_group.WAS-srv-sg.id
  ]  

  user_data = <<-EOF
    #!/bin/bash
    (
      echo "qwe123"
      echo "qwe123"
    ) | passwd --stdin root
    sed -i "s/^PasswordAuthentication no/PasswordAuthentication yes/g" /etc/ssh/sshd_config
    sed -i "s/^#PermitRootLogin yes/PermitRootLogin yes/g" /etc/ssh/sshd_config
    yum update -y
    yum install -y httpd
    systemctl start httpd
    systemctl enable httpd
    echo "This is the WAS server" > /var/www/html/index.html
    # 80번리스너 줄 위에 8080번리스너 추가.
    sed -i '/^Listen 80$/i Listen 8080' /etc/httpd/conf/httpd.conf
    systemctl restart httpd
  EOF

    tags = {
    Name = "WAS-Srv-1"
  }

}

# WAS 서버2 인스턴스 구성
resource "aws_instance" "WAS-Srv-2" {
  ami = "ami-0de20b1c8590e09c5"
  instance_type = "t2.micro"
  key_name = "AWS 키값"
  subnet_id = aws_subnet.Private-SN4.id
  security_groups = [
    aws_security_group.WAS-srv-sg.id
  ]  

  user_data = <<-EOF
    #!/bin/bash
    (
      echo "qwe123"
      echo "qwe123"
    ) | passwd --stdin root
    sed -i "s/^PasswordAuthentication no/PasswordAuthentication yes/g" /etc/ssh/sshd_config
    sed -i "s/^#PermitRootLogin yes/PermitRootLogin yes/g" /etc/ssh/sshd_config
    yum update -y
    yum install -y httpd
    systemctl start httpd
    systemctl enable httpd
    echo "This is the WAS server" > /var/www/html/index.html
    # 80번리스너 줄 위에 8080번리스너 추가.
    sed -i '/^Listen 80$/i Listen 8080' /etc/httpd/conf/httpd.conf
    systemctl restart httpd
  EOF

    tags = {
    Name = "WAS-Srv-2"
  }

}

 

 

 

 

* 데이터 계층 구성: db서버

  • 데이터베이스를 관리하는 계층으로 실제 데이터를 저장하고 검색하는 역할로써 DB정보를 제공해 준다. 
  • 현재 DB서버에는 MYSQL 8.0 버전이 설치되어 있고 WAS서버에서 DB서버로 통신 요청이 가능한지 테스트하였다.
  • 특히 WAS서버에서 DB서버끼리 통신 시에는 데이터베이스용 포트만 보안그룹에서 허용해서 통신하는 것이 좋다.

WAS서버에서 mysql을 설치하고 DB서버로 접속 시도해서 통신 확인

# DB 서버1 인스턴스 구축
resource "aws_instance" "DB-Srv-1" {
  ami = "ami-0de20b1c8590e09c5"
  instance_type = "t2.micro"
  key_name = "AWS 키값"
  subnet_id = aws_subnet.Private-SN5.id
  security_groups = [
    aws_security_group.DB-srv-sg.id
  ]  

user_data = <<-EOF
#!/bin/bash
# Update and install MySQL 8.0
yum update -y
  yum install -y https://dev.mysql.com/get/mysql80-community-release-el9-1.noarch.rpm
# (!)인증키없이 설치 - 보안상 매우 위험!!!
yum install -y mysql-server --nogpgcheck

# Start MySQL service
systemctl start mysqld
systemctl enable mysqld

# Retrieve temporary MySQL password
TEMP_PASSWORD=$(grep 'temporary password' /var/log/mysqld.log | awk '{print $NF}')
echo "Temporary MySQL password: $TEMP_PASSWORD" >> /var/log/mysql_setup.log

# MySQL initial setup
mysql --connect-expired-password -uroot -p"$TEMP_PASSWORD" <<MYSQL_SCRIPT
  ALTER USER 'root'@'localhost' IDENTIFIED BY 'Qwer123!';
  CREATE USER 'admin'@'%' IDENTIFIED BY 'Qwer123!';
  GRANT ALL PRIVILEGES ON *.* TO 'admin'@'%';
  FLUSH PRIVILEGES;
MYSQL_SCRIPT

echo "MySQL installation and configuration complete" >> /var/log/mysql_setup.log
EOF



  tags = {
    Name = "DB-Srv-1"
  }

}


# DB 서버2 인스턴스 구축
resource "aws_instance" "DB-Srv-2" {
  ami = "ami-0de20b1c8590e09c5"
  instance_type = "t2.micro"
  key_name = "AWS 키값"
  subnet_id = aws_subnet.Private-SN6.id
  security_groups = [
    aws_security_group.DB-srv-sg.id
  ]  

  user_data = <<-EOF
    #!/bin/bash
    (
      echo "qwe123"
      echo "qwe123"
    ) | passwd --stdin root
    sed -i "s/^PasswordAuthentication no/PasswordAuthentication yes/g" /etc/ssh/sshd_config
    sed -i "s/^#PermitRootLogin yes/PermitRootLogin yes/g" /etc/ssh/sshd_config
    yum update -y

    # MySQL 8.0 설치
    amazon-linux-extras enable mysql8.0
    yum install -y mysql-server
    systemctl start mysqld
    systemctl enable mysqld

    # MySQL 초기 설정
    TEMP_PASSWORD=$(grep 'temporary password' /var/log/mysqld.log | awk '{print $NF}')
    echo "Temporary MySQL password: $TEMP_PASSWORD" >> /var/log/mysql_setup.log

    # 보안 설정: 비밀번호 설정 및 사용자 추가
    mysql --connect-expired-password -uroot -p"$TEMP_PASSWORD" <<MYSQL_SCRIPT
      ALTER USER 'root'@'localhost' IDENTIFIED BY 'password123!';
      CREATE USER 'admin'@'%' IDENTIFIED BY 'password123!';
      GRANT ALL PRIVILEGES ON *.* TO 'admin'@'%';
      FLUSH PRIVILEGES;
    MYSQL_SCRIPT
  EOF


  tags = {
    Name = "DB-Srv-2"
  }

}

 

 

 

 

* bastion 호스트 서버 구성(의존성 및 보안 때문에 마지막에 구성)

  • 유일하게 외부에서 내부 서버들로 접속하여서 서버를 관리하기 위한 서버.
  • SSH접속만 허가하였으며 AWS의 키페어 없이는 절대 다른 서버들과 통신을 할 수가 없다.
  • 되도록이면 보안그룹의 고정 IP를 정하여 보안을 강화해야 한다. 
# Bastion Host EC2 인스턴스
resource "aws_instance" "bastion" {
  ami = "ami-0de20b1c8590e09c5"
  instance_type = "t2.micro"
  subnet_id     = aws_subnet.Public-SN1.id
  key_name      = "AWS 키값"
  associate_public_ip_address = true # 퍼블릭 IP 할당
  security_groups = [aws_security_group.bastion-srv-sg.id]

  user_data = <<-EOF
    #!/bin/bash
    yum update -y
    echo "Bastion Host is ready." > /var/www/html/index.html
  EOF

  tags = {
    Name = "Bastion Host"
  }
}

 

 

* 유의 해야할 사항

  • 사실 각 서버의 들어가서 실적인 기능 구현을 해야 하며 더욱 보완해야 할 사항 등이 매우 많다. 
  • 뿐만 아니라 테라폼 main.tf 파일에서 모든 작업이 이루어져서 모듈화까지 진행해야한다.
  • 그러나 이번 글에서는 아주 큰 틀만 살펴보기 위한 간단한 실습이었으며 차후에는 보완 및 수정할 것이다.