개요
진행하던 사이드프로젝트의 인프라 구성을 AWS 콘솔로 구성하던 중 AI를 활용해보면 어떨까 하는 생각이 들었습니다. Terraform이 이 분야에서 가장 유명하고 사용해본 적도 있지만 HCL이 익숙하지 않은 문제가 있어 Pulumi라는 도구를 사용해보기로 했습니다. Pulumi + AI로 간단한 인프라 구성을 진행해본 후기를 공유합니다.
Pulumi
Pulumi 소개
Pulumi는 Infrastructure as Code(IaC) 도구로, 인프라를 코드로 관리할 수 있게 해줍니다. 이 분야에서 가장 유명한 도구는 Terraform(OpenTofu)이지만 개요에서 잠깐 언급했듯이 HCL이라는 별도의 언어를 학습해야 하는 문제가 있습니다. 반면 Pulumi는 Python, Node.js, YAML 등 개발자들에게 친숙한 다양한 언어의 IaC를 지원합니다.
Pulumi 셋업
Terraform(OpenTofu)처럼 Pulumi도 State라는 인프라 상태를 나타내는 개념이 있습니다. Pulumi이 실제 인프라를 수정하면서 State를 수정하고 State 변화를 통해 인프라 변경을 추적합니다. 다양한 저장소를 지원하지만 저는 File 저장소를 사용했습니다. file을 저장소로 사용하려면 아래 명령으로 로그인하면 됩니다.
pulumi login file:///path/to/your/pulumi.state
LLM 활용하기
시작 프롬프트는 이렇게 작성했고 첫 결과물은 아래와 같습니다.
“AWS에 인프라를 구성하려고 해. 구성할 서비스는 단일 EC2 인스턴스에 docker-compose로 웹 서버를 서빙하는 구조야. VPC, EC2를 구성하고 이 인스턴스에 셸 접근이 가능한 AMI를 하나 만드는 구성을 pulumi-python으로 작성해줘”
import pulumi
import pulumi_aws as aws
# VPC 생성
vpc = aws.ec2.Vpc("my-vpc",
cidr_block="10.0.0.0/16",
enable_dns_hostnames=True,
enable_dns_support=True,
tags={"Name": "my-vpc"})
# 퍼블릭 서브넷 생성
public_subnet = aws.ec2.Subnet("public-subnet",
vpc_id=vpc.id,
cidr_block="10.0.1.0/24",
map_public_ip_on_launch=True,
availability_zone="us-west-2a",
tags={"Name": "public-subnet"})
# 인터넷 게이트웨이 생성 및 VPC에 연결
igw = aws.ec2.InternetGateway("my-igw",
vpc_id=vpc.id,
tags={"Name": "my-igw"})
# 라우트 테이블 생성 및 인터넷 게이트웨이로 라우팅
route_table = aws.ec2.RouteTable("public-rt",
vpc_id=vpc.id,
routes=[aws.ec2.RouteTableRouteArgs(
cidr_block="0.0.0.0/0",
gateway_id=igw.id
)],
tags={"Name": "public-rt"})
# 라우트 테이블을 퍼블릭 서브넷에 연결
route_table_association = aws.ec2.RouteTableAssociation("public-rta",
subnet_id=public_subnet.id,
route_table_id=route_table.id)
security_group = aws.ec2.SecurityGroup("web-sg",
description="Allow HTTP and SSH access",
vpc_id=vpc.id,
ingress=[
aws.ec2.SecurityGroupIngressArgs(
protocol="tcp",
from_port=80,
to_port=80,
cidr_blocks=["0.0.0.0/0"],
),
aws.ec2.SecurityGroupIngressArgs(
protocol="tcp",
from_port=22,
to_port=22,
cidr_blocks=["0.0.0.0/0"],
)
],
egress=[aws.ec2.SecurityGroupEgressArgs(
protocol="-1",
from_port=0,
to_port=0,
cidr_blocks=["0.0.0.0/0"],
)],
tags={"Name": "web-sg"})
ami = aws.ec2.get_ami(most_recent=True,
owners=["099720109477"],
filters=[aws.GetAmiFilterArgs(
name="name",
values=["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"],
)])
user_data = """#!/bin/bash
apt-get update
apt-get install -y docker.io docker-compose
systemctl start docker
systemctl enable docker
"""
instance = aws.ec2.Instance("web-server",
instance_type="t2.micro",
vpc_security_group_ids=[security_group.id],
ami=ami.id,
subnet_id=public_subnet.id,
user_data=user_data,
tags={"Name": "web-server"},
key_name="your-key-pair-name") # 기존 키 페어 이름을 지정하세요
pulumi.export("instance_public_ip", instance.public_ip)
첫 구성 자체도 IAM을 AMI로 오타내서 빠진 것을 제외하면 원하는 부분은 다 포함되어 있습니다. 아래 프롬프트로 IAM 계정을 추가하고 권한 부여하는 부분을 추가했습니다. HTTPS 접근을 위한 보안 그룹 수정은 제가 직접 수정했습니다.
“이 파일은 pulumi로 AWS에 인프라를 구성하고 있어. 구성할 서비스는 단일 EC2 인스턴스에 docker-compose로 웹 서버를 서빙하는 구조야. VPC, AMI를 사용한 EC2를 구성하고 있어. 이 EC2 인스턴스에 쉘 접근이 가능한 IAM 계정을 만드는 코드를 추가해줘”
“resumait-shell-user IAM 계정에 자체적으로 비밀번호를 바꿀 수 있는 권한, 이메일을 연결할 수 있는 권한을 추가해줘”
import pulumi
import pulumi_aws as aws
# 공통 태그 정의
common_tags = {
"service-name": "resumait"
}
# VPC 생성
vpc = aws.ec2.Vpc("resumait-vpc",
cidr_block="10.0.0.0/16",
enable_dns_hostnames=True,
enable_dns_support=True,
tags={**common_tags, "Name": "resumait-vpc"})
# 퍼블릭 서브넷 생성
public_subnet = aws.ec2.Subnet("resumait-public-subnet",
vpc_id=vpc.id,
cidr_block="10.0.1.0/24",
map_public_ip_on_launch=True,
availability_zone="ap-northeast-2a",
tags={**common_tags, "Name": "resumait-public-subnet"})
# 인터넷 게이트웨이 생성 및 VPC에 연결
igw = aws.ec2.InternetGateway("resumait-igw",
vpc_id=vpc.id,
tags={**common_tags, "Name": "resumait-igw"})
# 라우트 테이블 생성 및 인터넷 게이트웨이로 라우팅
route_table = aws.ec2.RouteTable("resumait-public-rt",
vpc_id=vpc.id,
routes=[aws.ec2.RouteTableRouteArgs(
cidr_block="0.0.0.0/0",
gateway_id=igw.id
)],
tags={**common_tags, "Name": "resumait-public-rt"})
# 라우트 테이블을 퍼블릭 서브넷에 연결
route_table_association = aws.ec2.RouteTableAssociation("resumait-public-rta",
subnet_id=public_subnet.id,
route_table_id=route_table.id)
# 보안 그룹 생성
security_group = aws.ec2.SecurityGroup("resumait-web-sg",
description="Allow HTTP and SSH access",
vpc_id=vpc.id,
ingress=[
aws.ec2.SecurityGroupIngressArgs(
protocol="tcp",
from_port=80,
to_port=80,
cidr_blocks=["0.0.0.0/0"],
),
aws.ec2.SecurityGroupIngressArgs(
protocol="tcp",
from_port=443,
to_port=443,
cidr_blocks=["0.0.0.0/0"],
),
aws.ec2.SecurityGroupIngressArgs(
protocol="tcp",
from_port=22,
to_port=22,
cidr_blocks=["0.0.0.0/0"],
)
],
egress=[aws.ec2.SecurityGroupEgressArgs(
protocol="-1",
from_port=0,
to_port=0,
cidr_blocks=["0.0.0.0/0"],
)],
tags={**common_tags, "Name": "resumait-web-sg"})
# AMI 선택
ami = aws.ec2.get_ami(most_recent=True,
owners=["099720109477"],
filters=[aws.ec2.GetAmiIdsFilterArgs(
name="name",
values=["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"],
)])
# 사용자 데이터 스크립트
user_data = """#!/bin/bash
apt-get update
apt-get install -y docker.io docker-compose
systemctl start docker
systemctl enable docker
"""
# EC2 인스턴스 생성
instance = aws.ec2.Instance("resumait-web-server",
instance_type="t2.micro",
vpc_security_group_ids=[security_group.id],
ami=ami.id,
subnet_id=public_subnet.id,
user_data=user_data,
tags={**common_tags, "Name": "resumait-web-server"},
key_name="resumait-dev") # 기존 키 페어 이름을 지정하세요
# 출력
pulumi.export("instance_public_ip", instance.public_ip)
# IAM 사용자 생성 (콘솔 액세스 활성화)
shell_user = aws.iam.User("resumait-shell-user",
name="resumait-shell-user",
tags={**common_tags, "Name": "resumait-shell-user"})
# 콘솔 로그인 비밀번호 생성
login_profile = aws.iam.UserLoginProfile("resumait-shell-user-login",
user=shell_user.name,
password_reset_required=True)
# IAM 액세스 키 생성
access_key = aws.iam.AccessKey("resumait-shell-user-key",
user=shell_user.name)
# EC2 접근을 위한 IAM 정책 생성
shell_policy = aws.iam.Policy("resumait-shell-policy",
description="Policy for EC2 shell access",
policy="""{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2-instance-connect:SendSSHPublicKey"
],
"Resource": "*"
}
]
}""",
tags={**common_tags, "Name": "resumait-shell-policy"})
# 사용자에게 정책 연결
policy_attachment = aws.iam.UserPolicyAttachment("resumait-shell-policy-attachment",
user=shell_user.name,
policy_arn=shell_policy.arn)
# IAM 사용자 셀프 관리 정책 생성
self_manage_policy = aws.iam.Policy("resumait-shell-self-manage-policy",
description="Policy for self-managing credentials and profile",
policy="""{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iam:ChangePassword",
"iam:GetAccountPasswordPolicy",
"iam:UpdateLoginProfile",
"iam:GetLoginProfile",
"iam:UpdateUser",
"iam:GetUser"
],
"Resource": ["arn:aws:iam::*:user/${aws:username}"]
}
]
}""",
tags={**common_tags, "Name": "resumait-shell-self-manage-policy"})
# 사용자에게 셀프 관리 정책 연결
self_manage_policy_attachment = aws.iam.UserPolicyAttachment("resumait-shell-self-manage-attachment",
user=shell_user.name,
policy_arn=self_manage_policy.arn)
# 유저 정보 출력
pulumi.export('shell_user', shell_user.id)
# 액세스 키와 콘솔 비밀번호 정보 출력
pulumi.export("shell_user_access_key_id", access_key.id)
pulumi.export("shell_user_secret_access_key", access_key.secret)
pulumi.export("shell_user_console_password", login_profile.password)
후기
- 콘솔로 인프라 구성하는 것보다 훨씬 빠르고 쉽게 구성할 수 있었습니다.
- 비슷한 인프라를 콘솔로 구성해봤기 봤고, 코드가 의도한대로인지 대충은 알 수 있었기 때문에 더 좋았다고 생각합니다.
- 한눈에 보인다는 점에서 더더욱 좋았습니다
- 앞으로 UI는 AI 중심으로 재편될 것
- 내 기억을 떠올려보면 콘솔로 이 인프라를 처음 구성할 때 며칠은 걸렸음
- 공부해야 할 개념이 너무 많고 뭐가 있는지 찾기 힘들고 불편했었음
- UI는 필요한 부분만 보여줘야 하는데 AWS 콘솔은 보여주는 게 너무 많음
- AI가 이런 부분을 잘 해결해 줄 수 있을 것 같음
콘솔로 인프라를 구성할 때보다 훨씬 빠르고 쉽게 구성할 수 있었습니다. 정확히 재보진 않았지만 1시간을 넘진 않았습니다. 비슷한 인프라를 콘솔로 구성해보아서 지식/경험이 있었고 코드가 의도한대로 작성했는지 알 수 있었기 때문에 더 효율적이라고 생각합니다. AWS의 끔찍한(개인적인 생각입니다) UI와 다르게 필요한 부분만 한 눈에 보여 좋았습니다. 앞으로 UI도 AI 중심으로 재편될 것이 분명합니다. 제가 처음으로 AWS 인프라를 구성하던 때를 떠올려보면 EC2 인스턴스 목록을 보는 데만 몇 시간이 걸렸습니다. 보여주는 정보량이 너무 많아서 찾기 힘들고 불편했었습니다. AI를 사용하면 불필요한 EC2 > 인스턴스 위계 계층 부분을 UI에서 전부 없애고 검색창 하나면 됩니다. 사용자 수준에 따라 보여주는 정보 수준도 다르게할 수 있습니다. 수준이 높다면 설정 하나하나 보여주지만 수준이 낮다면 어떤 운영체제 이미지를 썼는지 보여주지 않고 인스턴스 상태만 보여주는 식으로요. AI가 가져올 미래가 새삼 두려워지는 하루였습니다.