1. 기본 환경 구성
이번 실습은 IAM 사용자 계정을 통해 관리 콘솔에 접근하고 액세스 키를 활용해 awscli 도구를 사용합니다.
해당 작업을 수행하지 않았다면 아래 토글을 확장해 작업을 선행하고 본격적인 실습에 들어갑니다.
IAM 사용자 생성 및 액세스 키 생성
1.1. Terraform을 통한 기본 인프라 배포
Terraform을 통한 기본 인프라 배포에 앞서 SSH 키 페어, IAM User Access Key ID, IAM User Secret Access Key를 미리 확인하고 메모해 둡니다.
Terraform으로 기본 인프라 배포
cd cnasg_class_tf/Section08
Bash
복사
# 실습 코드 경로 진입
export TF_VAR_KeyName=[각자 ssh keypair]
export TF_VAR_NickName=[각자 닉네임]
export TF_VAR_MyIamUserAccessKeyID=[각자 iam 사용자의 access key id]
export TF_VAR_MyIamUserSecretAccessKey=[각자 iam 사용자의 secret access key]
export TF_VAR_SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32
Bash
복사
# Terraform 환경 변수 저장
terraform init
terraform plan
Bash
복사
# Terraform 배포
nohup sh -c "terraform apply -auto-approve" > create.log 2>&1 &
Bash
복사
Note:
Terraform 배포가 완료되면(약 5분 정도 대기) 정상적으로 자원 생성이 되었는지 확인을 합니다.(cat create.log)
1.2. 기본 정보 확인 및 설정
Terraform 배포가 완료 후 출력되는 Outputs 정보에서 bastion_host_ip의 퍼블릭 IP를 확인합니다.
대상 IP로 인스턴스에 SSH로 접속하고 아래 명령어를 통해 정보를 확인합니다.
기본 정보 확인
aws sts get-caller-identity
Bash
복사
# caller id 확인
Note:
인스턴스가 생성되고 너무 빠르게 접속하면 aws 자격 증명이 완료되지 않을 수 있습니다.
그럴 경우에는 약간의 대기 후 다시 접속해 주세요.
기본 변수 선언
FE_VPC_ID=$(aws ec2 describe-vpcs \
--filters "Name=tag:Name,Values=FE-VPC" \
--query 'Vpcs[0].VpcId' --output text)
BE_VPC_ID=$(aws ec2 describe-vpcs \
--filters "Name=tag:Name,Values=BE-VPC" \
--query 'Vpcs[0].VpcId' \
--output text)
Bash
복사
# VPC ID 변수 선언
FE_PRIVATE_SUBNET_IDS=$(aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=${FE_VPC_ID}" \
"Name=tag:Name,Values=FE-VPC-private*" \
--query 'Subnets[*].SubnetId' \
--output text)
BE_PRIVATE_SUBNET_IDS=$(aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=${BE_VPC_ID}" \
"Name=tag:Name,Values=BE-VPC-private*" \
--query 'Subnets[*].SubnetId' \
--output text)
Bash
복사
# 프라이빗 서브넷 IDs 변수 선언 (ALL)
WEB1_ID=$(aws ec2 describe-instances \
--filters "Name=tag:Name,Values=WEB1" "Name=instance-state-name,Values=running" \
--query 'Reservations[0].Instances[0].InstanceId' --output text)
WEB2_ID=$(aws ec2 describe-instances \
--filters "Name=tag:Name,Values=WEB2" "Name=instance-state-name,Values=running" \
--query 'Reservations[0].Instances[0].InstanceId' --output text)
DB_API_ID=$(aws ec2 describe-instances \
--filters "Name=tag:Name,Values=DB-API" "Name=instance-state-name,Values=running" \
--query 'Reservations[0].Instances[0].InstanceId' --output text)
Bash
복사
# WEB1 / WEB2 / DB-API Instance ID
WEB1_ENI=$(aws ec2 describe-instances \
--instance-ids ${WEB1_ID} \
--query 'Reservations[0].Instances[0].NetworkInterfaces[0].NetworkInterfaceId' \
--output text)
WEB2_ENI=$(aws ec2 describe-instances \
--instance-ids ${WEB2_ID} \
--query 'Reservations[0].Instances[0].NetworkInterfaces[0].NetworkInterfaceId' \
--output text)
Bash
복사
# WEB1 / WEB2 ENI ID
WEB1_SG_ID=$(aws ec2 describe-security-groups \
--filters "Name=vpc-id,Values=${FE_VPC_ID}" "Name=group-name,Values=web1-sec-group" \
--query 'SecurityGroups[0].GroupId' --output text)
WEB2_SG_ID=$(aws ec2 describe-security-groups \
--filters "Name=vpc-id,Values=${FE_VPC_ID}" "Name=group-name,Values=web2-sec-group" \
--query 'SecurityGroups[0].GroupId' --output text)
Bash
복사
# WEB1 / WEB2 SG ID
WEB1_IP=192.168.11.100
WEB2_IP=192.168.11.200
Bash
복사
# WEB1 / WEB2 IP
echo "export FE_VPC_ID=${FE_VPC_ID}" >> /etc/profile
echo "export BE_VPC_ID=${BE_VPC_ID}" >> /etc/profile
echo "export WEB1_ID=${WEB1_ID}" >> /etc/profile
echo "export WEB2_ID=${WEB2_ID}" >> /etc/profile
echo "export DB_API_ID=${DB_API_ID}" >> /etc/profile
echo "export WEB1_ENI=${WEB1_ENI}" >> /etc/profile
echo "export WEB2_ENI=${WEB2_ENI}" >> /etc/profile
echo "export WEB1_SG_ID=${WEB1_SG_ID}" >> /etc/profile
echo "export WEB2_SG_ID=${WEB2_SG_ID}" >> /etc/profile
echo "export WEB1_IP=$WEB1_IP" >> /etc/profile
echo "export WEB2_IP=$WEB2_IP" >> /etc/profile
echo "FE_VPC_ID=${FE_VPC_ID}"
echo "BE_VPC_ID=${BE_VPC_ID}"
echo "FE_PRIVATE_SUBNET_IDS=${FE_PRIVATE_SUBNET_IDS}"
echo "BE_PRIVATE_SUBNET_IDS=${BE_PRIVATE_SUBNET_IDS}"
echo "WEB1_ID=${WEB1_ID}"
echo "WEB2_ID=${WEB2_ID}"
echo "DB_API_ID=${DB_API_ID}"
echo "WEB1_ENI=${WEB1_ENI}"
echo "WEB2_ENI=${WEB2_ENI}"
echo "WEB1_SG_ID=${WEB1_SG_ID}"
echo "WEB2_SG_ID=${WEB2_SG_ID}"
echo "WEB1_IP=$WEB1_IP"
echo "WEB2_IP=$WEB2_IP"
Bash
복사
# 전역 변수 선언 및 변수 값 출력
2. VPC 트래픽 통제 및 확인
2.1. VPC FlowLogs 구성
[VPC FlowLogs] IAM 구성
cat > vpc-flowlogs-trust.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "vpc-flow-logs.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
Bash
복사
# 신뢰 정책 파일 생성
aws iam create-role \
--role-name VPCFlowLogsRole \
--assume-role-policy-document file://vpc-flowlogs-trust.json
Bash
복사
# IAM 역할 생성
cat > vpc-flowlogs-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams"
],
"Resource": "*"
}
]
}
EOF
Bash
복사
# IAM 정책 파일 생성
aws iam create-policy \
--policy-name VPCFlowLogsPolicy \
--policy-document file://vpc-flowlogs-policy.json
Bash
복사
# IAM 정책 생성
aws iam attach-role-policy \
--role-name VPCFlowLogsRole \
--policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/VPCFlowLogsPolicy
Bash
복사
# IAM 역할에 IAM 정책 연결
FLOWLOG_ROLE_ARN=arn:aws:iam::${ACCOUNT_ID}:role/VPCFlowLogsRole
echo "export FLOWLOG_ROLE_ARN=${FLOWLOG_ROLE_ARN}" >> /etc/profile
echo "FLOWLOG_ROLE_ARN=${FLOWLOG_ROLE_ARN}"
Bash
복사
# IAM 역할의 ARN 정보 변수 선언
[VPC FlowLogs] CloudWatch Log Group 생성
LOG_GROUP_NAME=/cnasg/flowlogs/web-eni
echo "export LOG_GROUP_NAME=${LOG_GROUP_NAME}" >> /etc/profile
Bash
복사
# CloudWatch Log Group 이름 - 변수 선언
aws logs create-log-group \
--log-group-name ${LOG_GROUP_NAME}
Bash
복사
# CloudWatch Log Group 생성
[관리 콘솔 확인: CloudWatch → 로그 → Log Management]
[VPC FlowLogs] WEB1 / WEB2 ENI에 FlowLogs 생성
aws ec2 create-flow-logs \
--resource-type NetworkInterface \
--resource-ids ${WEB1_ENI} ${WEB2_ENI} \
--traffic-type REJECT \
--log-destination-type cloud-watch-logs \
--log-group-name ${LOG_GROUP_NAME} \
--deliver-logs-permission-arn ${FLOWLOG_ROLE_ARN}
Bash
복사
# FlowLogs 생성 (REJECT만 수집해서 CloudWatch Log Group에 게시)
aws ec2 describe-flow-logs \
--filter Name=resource-id,Values=${WEB1_ENI},${WEB2_ENI} \
--query 'FlowLogs[*].{FlowLogId:FlowLogId,ResourceId:ResourceId,Status:FlowLogStatus,Traffic:TrafficType,LogGroup:LogGroupName}' \
--output table
Bash
복사
# FlowLogs 상태 확인
2.2. 보안 그룹 동작 확인
WEB1 / WEB2 보안 그룹 확인
cat > /etc/profile.d/sgview.sh <<'EOF'
sgview () {
aws ec2 describe-security-groups --group-ids "$1" --output json | jq -r '
.SecurityGroups[0] as $sg |
"SG: \($sg.GroupName)",
"[Ingress]",
(
$sg.IpPermissions[]? |
"- \(.IpProtocol) \((.FromPort // "ALL"))-\((.ToPort // "ALL")) " +
(
( [.IpRanges[]?.CidrIp] + [.UserIdGroupPairs[]?.GroupId] )
| join(", ")
)
),
"[Egress]",
(
$sg.IpPermissionsEgress[]? |
"- \(.IpProtocol) \((.FromPort // "ALL"))-\((.ToPort // "ALL")) " +
(
( [.IpRanges[]?.CidrIp] + [.UserIdGroupPairs[]?.GroupId] )
| join(", ")
)
)
'
}
EOF
source /etc/profile.d/sgview.sh
Bash
복사
# 보안 그룹 확인 - 함수 선언 (sgview)
sgview "${WEB1_SG_ID}"
sgview "${WEB2_SG_ID}"
Bash
복사
# WEB1 / WEB2 보안 그룹 확인
WEB1 / WEB2 보안 그룹에 아웃바운드 규칙 삭제
aws ec2 revoke-security-group-egress \
--group-id ${WEB1_SG_ID} \
--ip-permissions '[{"IpProtocol":"-1","IpRanges":[{"CidrIp":"0.0.0.0/0"}]}]'
aws ec2 revoke-security-group-egress \
--group-id ${WEB2_SG_ID} \
--ip-permissions '[{"IpProtocol":"-1","IpRanges":[{"CidrIp":"0.0.0.0/0"}]}]'
Bash
복사
# 아웃바운드 규칙 삭제
sgview "${WEB1_SG_ID}"
sgview "${WEB2_SG_ID}"
Bash
복사
# WEB1 / WEB2 보안 그룹 확인
보안 그룹 동작의 이해를 돕기 위해 아웃바운드 규칙을 삭제한 것입니다.
[BASTION → WEB1 / WEB2] SSH 접근 확인
ssh ec2-user@${WEB1_IP}
Bash
복사
# WEB1으로 SSH 접근(password: cn@sg!@)
ssh ec2-user@${WEB2_IP}
Bash
복사
# WEB2로 SSH 접근(password: cn@sg!@)
sgview "${WEB1_SG_ID}"
sgview "${WEB2_SG_ID}"
Bash
복사
# WEB1 / WEB2 보안 그룹 확인
[사용자 PC → ALB → WEB1 / WEB2] HTTP 통신 확인
ALB_NAME="cnasg-fe-web-alb"
aws elbv2 describe-load-balancers \
--names $ALB_NAME \
--query 'LoadBalancers[0].DNSName' \
--output text
Bash
복사
# ALB 도메인 주소 확인
출력된 ALB 도메인 주소로 웹 접근을 수행합니다. (http://ALB 도메인 주소)
[사용자 PC → ALB → WEB1 / WEB2] CloudWatch 로그 스트림 확인
aws logs describe-log-streams \
--log-group-name "${LOG_GROUP_NAME}" \
--order-by LastEventTime \
--descending \
--max-items 10 \
--query 'logStreams[*].logStreamName' \
--output table
Bash
복사
# 생성된 로그 스트림 확인
STREAM1="${WEB1_ENI}-reject"
STREAM2="${WEB2_ENI}-reject"
echo "export STREAM1=$STREAM1" >> /etc/profile
echo "export STREAM2=$STREAM2" >> /etc/profile
Bash
복사
# 로그 스트림 이름 - 변수 선언
aws logs filter-log-events \
--log-group-name "${LOG_GROUP_NAME}" \
--log-stream-names "${STREAM1}" \
--filter-pattern "REJECT" \
--max-items 5 \
--query 'events[*].message' \
--output json | jq
Bash
복사
# WEB1 ENI의 로그 스트림 확인 (REJECT 패킷 5개만)
aws logs filter-log-events \
--log-group-name "${LOG_GROUP_NAME}" \
--log-stream-names "${STREAM2}" \
--filter-pattern "REJECT" \
--max-items 5 \
--query 'events[*].message' \
--output json | jq
Bash
복사
# WEB2 ENI의 로그 스트림 확인 (REJECT 패킷 5개만)
[사용자 PC → ALB → WEB1 / WEB2] ALB의 대상 그룹 확인
TG_ARN=$(aws elbv2 describe-target-groups \
--query 'TargetGroups[?contains(TargetGroupName, `cnasg`)].TargetGroupArn' \
--output text)
echo "export TG_ARN=$TG_ARN" >> /etc/profile
Bash
복사
# ALB 대상 그룹의 ARN 변수 선언
aws elbv2 describe-target-health \
--target-group-arn ${TG_ARN} \
--query 'TargetHealthDescriptions[*].{
InstanceId:Target.Id,
State:TargetHealth.State
}' \
--output table
Bash
복사
# 대상 그룹 상태 정보 확인
모든 대상이 Unhealthy인 이유는??
[사용자 PC → ALB → WEB1 / WEB2] 보안 그룹 Ingress 규칙 추가
watch -d "aws elbv2 describe-target-health \
--target-group-arn ${TG_ARN} \
--query 'TargetHealthDescriptions[*].{
InstanceId:Target.Id,
State:TargetHealth.State
}' \
--output table"
Bash
복사
# [신규 터미널] 대상 그룹 상태 정보 모니터링
aws ec2 authorize-security-group-ingress \
--group-id ${WEB1_SG_ID} \
--protocol tcp \
--port 80 \
--cidr 0.0.0.0/0
Bash
복사
# WEB1 보안 그룹의 Ingress에 80 포트 허용 (모든 IP 대상)
sgview "${WEB1_SG_ID}"
Bash
복사
# WEB1 보안 그룹 정보 확인
잠시 기다리면 WEB1 대상이 Healthy로 전환됩니다. (WEB2는 Unhealthy인 이유는??)
ALB 도메인 주소로 웹 접근을 수행하면 WEB1 페이지로만 접근됩니다.
aws ec2 authorize-security-group-ingress \
--group-id ${WEB2_SG_ID} \
--protocol tcp \
--port 80 \
--cidr 0.0.0.0/0
Bash
복사
# WEB2 보안 그룹도 Ingress에 80 포트 허용 (모든 IP 대상)
sgview "${WEB2_SG_ID}"
Bash
복사
# WEB2 보안 그룹 정보 확인
잠시 기다리면 모든 대상이 Healthy로 전환됩니다.
ALB 도메인 주소로 웹 접근을 수행하면 WEB1 / WEB2 페이지 모두에 접근됩니다.
[WEB1 → NAT → 인터넷 구간] HTTP 통신 확인
ssh ec2-user@${WEB1_IP}
Bash
복사
# WEB1으로 SSH 접근(password: cn@sg!@)
curl --connect-timeout 5 wttr.in
Bash
복사
# [WEB1] 외부 인터넷 구간으로 HTTP 통신
WEB1에서 외부 인터넷 구간으로 HTTP 통신이 불가한 이유는??
[WEB1 → NAT → 인터넷 구간] 보안 그룹 Egress 규칙 추가
aws ec2 authorize-security-group-egress \
--group-id ${WEB1_SG_ID} \
--protocol -1 \
--cidr 0.0.0.0/0
Bash
복사
# [BASTION] WEB1 보안 그룹의 Egress에 모든 통신 허용
sgview "${WEB1_SG_ID}"
Bash
복사
# [BASTION] WEB1 보안 그룹 정보 확인
curl --connect-timeout 5 wttr.in
Bash
복사
# [WEB1] 외부 인터넷 구간으로 HTTP 통신
aws ec2 authorize-security-group-egress \
--group-id ${WEB2_SG_ID} \
--protocol -1 \
--cidr 0.0.0.0/0
Bash
복사
# [BASTION] WEB2 보안 그룹의 Egress에 모든 통신 허용
WEB2도 마찬가지로 규칙을 추가합니다. (설정 전/후로 각자 통신 확인)
2.3. NACL 동작 확인
watch -d "aws elbv2 describe-target-health \
--target-group-arn ${TG_ARN} \
--query 'TargetHealthDescriptions[*].{
InstanceId:Target.Id,
State:TargetHealth.State
}' \
--output table"
Bash
복사
# [신규 터미널] 대상 그룹 상태 정보 모니터링
ALB 대상 그룹을 지속적으로 모니터링해서 NACL 동작을 이해합니다.
•
NACL(네트워크 ACL)에 대한 설정은 AWS CLI로 구성할 경우 복잡도가 높아 실습의 이해가 떨어질 수 있습니다.
그런 측면에서 이번 실습은 AWS 관리 콘솔에서 NACL를 관리하겠습니다.
기본 NACL 확인
VPC → 네트워크 ACL (진입)
•
FE-VPC-default 대상 선택
◦
인바운드 규칙 탭
◦
아웃바운드 규칙 탭
◦
서브넷 연결 탭
신규 NACL 생성
VPC → 네트워크 ACL (진입) ⇒ 네트워크 ACL 생성 (클릭)
•
이름: NEW-FE-PRIVATE-0-NACL
•
VPC: FE-VPC
•
네트워크 ACL 생성 (클릭)
신규 NACL 확인 및 설정
VPC → 네트워크 ACL (진입)
•
NEW-FE-PRIVATE-0-NACL 대상 선택
◦
인바운드 규칙 탭
◦
아웃바운드 규칙 탭
◦
서브넷 연결 탭 ⇒ 서브넷 연결 편집 (클릭)
▪
FE-VPC-private-ap-northeast-2a (선택)
▪
변경 사항 저장 (클릭)
잠시 기다리면 모든 대상이 Unhealthy로 전환됩니다.
신규 NACL - 인바운드 규칙 추가
VPC → 네트워크 ACL (진입)
•
NEW-FE-PRIVATE-0-NACL 대상 선택
◦
인바운드 규칙 탭 ⇒ 인바운드 규칙 편집 (클릭)
▪
새 규칙 추가 (클릭)
•
규칙 번호: 100
•
포트 범위: 80
•
소스: 0.0.0.0/0
•
허용/거부: 허용
▪
변경 사항 저장 (클릭)
여전히 모든 대상이 Unhealthy를 유지하는데 이유는??
신규 NACL - 아웃바운드 규칙 추가
VPC → 네트워크 ACL (진입)
•
NEW-FE-PRIVATE-0-NACL 대상 선택
◦
아웃바운드 규칙 탭 ⇒ 아웃바운드 규칙 편집 (클릭)
▪
새 규칙 추가 (클릭)
•
규칙 번호: 100
•
포트 범위: 1024-65535
•
소스: 0.0.0.0/0
•
허용/거부: 허용
▪
변경 사항 저장 (클릭)
잠시 기다리면 모든 대상이 Healthy로 전환됩니다.
WEB1에 SSH 접근하고, 외부 인터넷 구간이 통신하도록 NACL 규칙을 수정해 보세요.
기본 NACL로 다시 연결
VPC → 네트워크 ACL (진입)
•
FE-VPC-default 대상 선택
◦
서브넷 연결 탭 ⇒ 서브넷 연결 편집 (클릭)
▪
FE-VPC-private-ap-northeast-2a (선택)
▪
변경 사항 저장 (클릭)
신규 NACL 삭제
VPC → 네트워크 ACL (진입)
•
NEW-FE-PRIVATE-0-NACL 대상 선택
◦
작업 (선택) ⇒ 네트워크 ACL 삭제 (선택)
▪
삭제 (입력) → 삭제 (클릭)
3. PrivateLink 구성
ALB_NAME="cnasg-fe-web-alb"
aws elbv2 describe-load-balancers \
--names $ALB_NAME \
--query 'LoadBalancers[0].DNSName' \
--output text
Bash
복사
# ALB 도메인 주소 확인
출력된 ALB 도메인 주소로 웹 접근을 수행합니다. (http://ALB 도메인 주소)
ALB를 통해 WEB1/2로 부하 분산되서 통신되지만, 아직 DB와 연결되지 못했습니다.
3.1. DB 생성 및 DB-API 연결 (퍼블릭)
AWS RDS 생성 및 확인
DB_ID=cnasg-db
DB_NAME=fruitdb
DB_USER=cnasg
DB_PASS='Cnasg1234!'
DB_CLASS=db.t3.micro
DB_PORT=3306
echo "export DB_ID=${DB_ID}" >> /etc/profile
echo "export DB_NAME=${DB_NAME}" >> /etc/profile
echo "export DB_USER=${DB_USER}" >> /etc/profile
echo "export DB_PASS=${DB_PASS}" >> /etc/profile
echo "export DB_CLASS=${DB_CLASS}" >> /etc/profile
echo "export DB_PORT=${DB_PORT}" >> /etc/profile
Bash
복사
# 기본 변수 선언
RDS_SN_GROUP="cnasg-db-subnet-group"
echo "export RDS_SN_GROUP=$RDS_SN_GROUP" >> /etc/profile
aws rds create-db-subnet-group \
--db-subnet-group-name ${RDS_SN_GROUP} \
--db-subnet-group-description "CNASG BE VPC Private Subnets" \
--subnet-ids ${BE_PRIVATE_SUBNET_IDS}
Bash
복사
# RDS DB 서브넷 그룹 생성
aws rds describe-db-subnet-groups \
--db-subnet-group-name ${RDS_SN_GROUP} \
--query 'DBSubnetGroups[0].Subnets[*].SubnetIdentifier' \
--output table
Bash
복사
# RDS DB 서브넷 그룹 확인
[관리 콘솔 확인: Aurora and RDS → 서브넷 그룹]
RDS_SG_ID=$(aws ec2 describe-security-groups \
--filters "Name=vpc-id,Values=${BE_VPC_ID}" \
"Name=group-name,Values=rds-sec-group" \
--query 'SecurityGroups[0].GroupId' \
--output text)
echo "export RDS_SG_ID=${RDS_SG_ID}" >> /etc/profile
echo "RDS_SG_ID=${RDS_SG_ID}"
Bash
복사
# RDS 보안 그룹 ID - 변수 선언
sgview "${RDS_SG_ID}"
Bash
복사
# RDS 보안 그룹 정보 확인
aws rds create-db-instance \
--db-instance-identifier ${DB_ID} \
--engine mysql \
--engine-version 8.0 \
--db-instance-class ${DB_CLASS} \
--allocated-storage 20 \
--storage-type gp3 \
--master-username ${DB_USER} \
--master-user-password ${DB_PASS} \
--db-name ${DB_NAME} \
--port ${DB_PORT} \
--vpc-security-group-ids ${RDS_SG_ID} \
--db-subnet-group-name ${RDS_SN_GROUP} \
--no-publicly-accessible \
--backup-retention-period 0 \
--no-multi-az \
--deletion-protection \
--tags Key=Name,Value=CNASG-RDS
Bash
복사
# RDS - MySQL DB 인스턴스 생성
watch -d "aws rds describe-db-instances \
--db-instance-identifier ${DB_ID} \
--query 'DBInstances[0].DBInstanceStatus' \
--output text"
Bash
복사
# RDS - MySQL DB 인스턴스 상태 확인 (모니터링)
RDS DB 인스턴스 생성까지 약 5분 정도의 대기 시간이 필요합니다.
[관리 콘솔 확인: Aurora and RDS → 데이터베이스]
aws rds describe-db-instances \
--db-instance-identifier ${DB_ID} \
--query 'DBInstances[0].Endpoint.{Address:Address,Port:Port}' \
--output table
Bash
복사
# DB 인스턴스의 RDS 엔드포인트 확인
DB_ENDPOINT=$(aws rds describe-db-instances \
--db-instance-identifier ${DB_ID} \
--query 'DBInstances[0].Endpoint.Address' \
--output text)
echo "export echo DB_ENDPOINT=${DB_ENDPOINT}" >> /etc/profile
echo $DB_ENDPOINT
Bash
복사
# RDS 엔드포인트 변수 선언
[DB-API 접속] AWS RDS 엔드포인트 접속 및 DB 생성
Terraform 배포가 완료 후 출력되는 Outputs 정보에서 db_api_host_ip의 퍼블릭 IP를 확인하고 SSH로 접속합니다.
DB_EP_ADDR=[DB_ENDPOINT_ADDRESS 입력]
Bash
복사
# 기본 변수 선언
DB_NAME=fruitdb
DB_USER=cnasg
DB_PASS='Cnasg1234!'
Bash
복사
mysql -h ${DB_EP_ADDR} \
-P 3306 \
-u ${DB_USER} \
--password=${DB_PASS}
Bash
복사
# MySQL 접속
USE fruitdb;
Bash
복사
# DB 선택 (fruitdb)
CREATE TABLE IF NOT EXISTS products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
price INT NOT NULL
);
Bash
복사
# 테이블 생성 (products)
INSERT INTO products (name, price) VALUES
('사과', 500),
('바나나', 1000),
('수박', 2000);
Bash
복사
# 데이터 입력
SELECT * FROM products ORDER BY price ASC, name ASC;
Bash
복사
# 테이블 확인 (products)
exit
Bash
복사
# MySQL에서 빠져 나오기
[DB-API 접속] DB-API 설정
curl http://127.0.0.1/products.php
Bash
복사
# DB-API 동작 수행
cat > /etc/httpd/conf.d/db-api-env.conf <<EOF
SetEnv DB_HOST ${DB_EP_ADDR}
SetEnv DB_NAME ${DB_NAME}
SetEnv DB_USER ${DB_USER}
SetEnv DB_PASS ${DB_PASS}
SetEnv DB_PORT 3306
EOF
Bash
복사
# db-api-env.conf에 환경 변수 선언
systemctl restart httpd
Bash
복사
# httpd 재시작
curl http://127.0.0.1/products.php
Bash
복사
# DB-API 동작 수행
[BASTION 접속] WEB1 / WEB2에 DB-API 설정
DB_API_PUBLIC_IP=$(aws ec2 describe-instances \
--filters "Name=tag:Name,Values=DB-API" \
"Name=instance-state-name,Values=running" \
--query 'Reservations[0].Instances[0].PublicIpAddress' \
--output text)
echo "DB_API_PUBLIC_IP=${DB_API_PUBLIC_IP}"
Bash
복사
# DB-API 퍼블릭 IP를 변수 선언
WEB_SSH_PASS='cn@sg!@'
Bash
복사
# WEB1 / WEB2 SSH 암호를 변수 선언
sshpass -p "$WEB_SSH_PASS" ssh -T -o StrictHostKeyChecking=no ec2-user@$WEB1_IP <<EOF
sudo tee /etc/httpd/conf.d/api-env.conf >/dev/null <<CONF
SetEnv API_URL http://${DB_API_PUBLIC_IP}/products.php
CONF
sudo systemctl restart httpd
EOF
Bash
복사
# WEB1에 API_URL 환경 변수를 선언하고 httpd 재시작
# WEB2 설정
sshpass -p "$WEB_SSH_PASS" ssh -T -o StrictHostKeyChecking=no ec2-user@$WEB2_IP <<EOF
sudo tee /etc/httpd/conf.d/api-env.conf >/dev/null <<CONF
SetEnv API_URL http://${DB_API_PUBLIC_IP}/products.php
CONF
sudo systemctl restart httpd
EOF
Bash
복사
# WEB2에 API_URL 환경 변수를 선언하고 httpd 재시작
출력된 ALB 도메인 주소로 웹 접근을 수행합니다. (HTTP)
과일 품목을 추가해 보기
3.2. DB-API 연결 (PrivateLink)
Internal NLB와 대상 그룹 생성
NLB_TG_ARN=$(aws elbv2 create-target-group \
--name cnasg-dbapi-tg \
--protocol TCP \
--port 80 \
--vpc-id ${BE_VPC_ID} \
--target-type instance \
--query 'TargetGroups[0].TargetGroupArn' \
--output text)
echo "export NLB_TG_ARN=${NLB_TG_ARN}" >> /etc/profile
echo "NLB_TG_ARN=${NLB_TG_ARN}"
Bash
복사
# NLB 대상 그룹 생성 및 변수 선언
aws elbv2 register-targets \
--target-group-arn ${NLB_TG_ARN} \
--targets Id=${DB_API_ID},Port=80
Bash
복사
# NLB 대상 그룹에 DB-API 인스턴스 등록
NLB_ARN=$(aws elbv2 create-load-balancer \
--name cnasg-dbapi-nlb \
--type network \
--scheme internal \
--subnets ${BE_PRIVATE_SUBNET_IDS} \
--query 'LoadBalancers[0].LoadBalancerArn' \
--output text)
echo "export NLB_ARN=${NLB_ARN}" >> /etc/profile
echo "NLB_ARN=${NLB_ARN}"
Bash
복사
# NLB 생성(BE-VPC 프라이빗 서브넷) 및 ARN 변수 선언
aws elbv2 create-listener \
--load-balancer-arn ${NLB_ARN} \
--protocol TCP \
--port 80 \
--default-actions Type=forward,TargetGroupArn=${NLB_TG_ARN}
Bash
복사
# NLB에 리스너 생성(TCP 80 → 대상 그룹)
watch -d "aws elbv2 describe-target-health \
--target-group-arn ${NLB_TG_ARN} \
--query 'TargetHealthDescriptions[*].{
InstanceId:Target.Id,
State:TargetHealth.State
}' \
--output table"
Bash
복사
# NLB 대상 그룹에 속한 대상 확인
NLB 생성 시점으로 약 3분 정도의 대기가 필요합니다.
[PrivateLink 구성] BE-VPC에 엔드포인트 서비스 생성
EP_SVC_NAME=$(aws ec2 create-vpc-endpoint-service-configuration \
--network-load-balancer-arns ${NLB_ARN} \
--no-acceptance-required \
--query 'ServiceConfiguration.ServiceName' \
--output text)
echo "export EP_SVC_NAME=${EP_SVC_NAME}" >> /etc/profile
echo "EP_SVC_NAME=${EP_SVC_NAME}"
Bash
복사
# NLB에 엔드포인트 서비스 생성
[관리 콘솔 확인: VPC → PrivateLink 및 Lattice → 엔드포인트 서비스]
[PrivateLink 구성] FE-VPC에 VPC 인터페이스 엔드포인트 생성
VPCE_SG_ID=$(aws ec2 create-security-group \
--group-name cnasg-vpce-sg \
--description "CNASG Interface Endpoint SG" \
--vpc-id ${FE_VPC_ID} \
--query 'GroupId' --output text)
echo "export VPCE_SG_ID=${VPCE_SG_ID}" >> /etc/profile
echo "VPCE_SG_ID=${VPCE_SG_ID}"
Bash
복사
# VPC 엔드포인트 전용 보안 그룹 생성 및 ID 변수 선언
aws ec2 authorize-security-group-ingress \
--group-id ${VPCE_SG_ID} \
--protocol tcp \
--port 80 \
--source-group ${WEB1_SG_ID}
aws ec2 authorize-security-group-ingress \
--group-id ${VPCE_SG_ID} \
--protocol tcp \
--port 80 \
--source-group ${WEB2_SG_ID}
Bash
복사
# 위 보안 그룹의 인바운드 규칙 추가 (tcp 80 트래픽에 대해 WEB1과 WEB2만 허용)
sgview "${VPCE_SG_ID}"
Bash
복사
# VPCE 보안 그룹 정보 확인
VPCE_ID=$(aws ec2 create-vpc-endpoint \
--vpc-id ${FE_VPC_ID} \
--vpc-endpoint-type Interface \
--service-name ${EP_SVC_NAME} \
--subnet-ids ${FE_PRIVATE_SUBNET_IDS} \
--security-group-ids ${VPCE_SG_ID} \
--query 'VpcEndpoint.VpcEndpointId' \
--output text)
echo "export VPCE_ID=${VPCE_ID}" >> /etc/profile
echo "VPCE_ID=${VPCE_ID}"
Bash
복사
# VPC 인터페이스 엔드포인트 생성 (FE-VPC의 프라이빗 서브넷)
VPCE_DNS=$(aws ec2 describe-vpc-endpoints \
--vpc-endpoint-ids ${VPCE_ID} \
--query 'VpcEndpoints[0].DnsEntries[0].DnsName' \
--output text)
echo "export VPCE_DNS=${VPCE_DNS}" >> /etc/profile
echo "VPCE_DNS=${VPCE_DNS}"
Bash
복사
# VPC 인터페이스 엔드포인트의 DNS 주소 변수 선언
[관리 콘솔 확인: VPC → PrivateLink 및 Lattice → 엔드포인트]
WEB1 / WEB2에 DB-API 수정 (PrivateLink)
sshpass -p "$WEB_SSH_PASS" ssh -T -o StrictHostKeyChecking=no ec2-user@$WEB1_IP <<EOF
sudo tee /etc/httpd/conf.d/api-env.conf >/dev/null <<CONF
SetEnv API_URL http://${VPCE_DNS}/products.php
CONF
sudo systemctl restart httpd
EOF
Bash
복사
# WEB1에 API_URL 환경 변수를 선언하고 httpd 재시작
sshpass -p "$WEB_SSH_PASS" ssh -T -o StrictHostKeyChecking=no ec2-user@$WEB2_IP <<EOF
sudo tee /etc/httpd/conf.d/api-env.conf >/dev/null <<CONF
SetEnv API_URL http://${VPCE_DNS}/products.php
CONF
sudo systemctl restart httpd
EOF
Bash
복사
# WEB2에 API_URL 환경 변수를 선언하고 httpd 재시작
출력된 ALB 도메인 주소로 웹 접근을 수행합니다. (HTTP)
4. 실습 환경 삭제
VPC FLowLogs 삭제
FLOWLOG_IDS=$(aws ec2 describe-flow-logs \
--filter Name=resource-id,Values=${WEB1_ENI},${WEB2_ENI} \
--query 'FlowLogs[*].FlowLogId' \
--output text)
aws ec2 delete-flow-logs \
--flow-log-ids ${FLOWLOG_IDS}
Bash
복사
# VPC FlowLogs 삭제
aws logs delete-log-group \
--log-group-name ${LOG_GROUP_NAME}
Bash
복사
# CloudWatch Log Group 삭제
aws iam detach-role-policy \
--role-name VPCFlowLogsRole \
--policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/VPCFlowLogsPolicy
Bash
복사
# VPC FlowLogs IAM 역할에서 정책 분리
aws iam delete-policy \
--policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/VPCFlowLogsPolicy
Bash
복사
# VPC FlowLogs IAM 정책 삭제
aws iam delete-role \
--role-name VPCFlowLogsRole
Bash
복사
# VPC FlowLogs IAM 역할 삭제
PrivateLink를 위한 엔드포인트 삭제
aws ec2 delete-vpc-endpoints \
--vpc-endpoint-ids ${VPCE_ID}
Bash
복사
# VPC 엔드포인트 삭제 (FE-VPC)
watch -d "aws ec2 describe-vpc-endpoints \
--query 'VpcEndpoints[*].{ID:VpcEndpointId,State:State}' \
--output table"
Bash
복사
# VPC 엔드포인트 확인 (FE-VPC)
aws ec2 delete-security-group \
--group-id ${VPCE_SG_ID}
Bash
복사
# VPC 엔드포인트 전용 보안 그룹 삭제 (FE-VPC)
VPC 엔드포인트 삭제를 확인한 후에 보안 그룹을 삭제할 수 있습니다.
aws ec2 describe-vpc-endpoint-service-configurations \
--query 'ServiceConfigurations[*].{ServiceId:ServiceId,ServiceName:ServiceName}' \
--output table
Bash
복사
# 엔드포인트 서비스 확인 (BE-VPC)
EP_SVC_ID=$(aws ec2 describe-vpc-endpoint-service-configurations \
--query "ServiceConfigurations[?ServiceName=='${EP_SVC_NAME}'].ServiceId | [0]" \
--output text)
aws ec2 delete-vpc-endpoint-service-configurations \
--service-ids ${EP_SVC_ID}
Bash
복사
# 엔드포인트 서비스 삭제 (BE-VPC)
Internal NLB 삭제 (BE-VPC)
NLB_LISTENER_ARN=$(aws elbv2 describe-listeners \
--load-balancer-arn ${NLB_ARN} \
--query 'Listeners[*].ListenerArn' \
--output text)
echo ${NLB_LISTENER_ARN}
aws elbv2 delete-listener \
--listener-arn ${NLB_LISTENER_ARN}
Bash
복사
# NLB 리스너 확인 및 삭제
aws elbv2 delete-load-balancer \
--load-balancer-arn ${NLB_ARN}
Bash
복사
# NLB 삭제
aws elbv2 delete-target-group \
--target-group-arn ${NLB_TG_ARN}
Bash
복사
# NLB 대상 그룹 삭제
AWS RDS 삭제
aws rds modify-db-instance \
--db-instance-identifier "${DB_ID}" \
--no-deletion-protection \
--apply-immediately
Bash
복사
# DeletionProtection 해제
aws rds describe-db-instances \
--db-instance-identifier "${DB_ID}" \
--query 'DBInstances[0].{Status:DBInstanceStatus,DeletionProtection:DeletionProtection}' \
--output table
Bash
복사
# DeletionProtection 확인
aws rds delete-db-instance \
--db-instance-identifier "${DB_ID}" \
--skip-final-snapshot \
--delete-automated-backups
Bash
복사
# DB 인스턴스 삭제
watch -d "aws rds describe-db-instances \
--db-instance-identifier ${DB_ID} \
--query 'DBInstances[0].DBInstanceStatus' \
--output text"
Bash
복사
# DB 인스턴스 확인
DB 인스턴스 삭제는 약 2분 정도 대기가 필요합니다.
aws rds delete-db-subnet-group \
--db-subnet-group-name "${RDS_SN_GROUP}"
Bash
복사
# RDS DB 서브넷 그룹 삭제
Terraform 자원 삭제
nohup sh -c "terraform destroy -auto-approve" > delete.log 2>&1 &
Bash
복사
# terraform 자원 삭제
Note:
Terraform 자원 삭제가 완료되면(약 3분 정도 대기) 정상적으로 자원 삭제가 되었는지 확인을 합니다.(cat delete.log)
여기까지 섹션 08 실습 - Amazon VPC 보안 구성을 마칩니다.
수고하셨습니다 :)


