1
테라폼으로 정의되지 않은 외부 리소스 또는 저장된 정보를 테라폼 내에서 참조할 때 사용
mkdir 35
cd 35
2
data 소스 = data로 시작함.
vi main.tf
data "local_file" "abc" {
filename = "${path.module}/abc.txt"
}
data 정보를 참조.
local 프로바이이더를 씀.
file을 소스로 사용함.
이름 abc
3
# 실습 확인을 위해서 abc.txt 파일 생성
echo "t101 study - 2 week" > abc.txt
4
terraform init && terraform plan && terraform apply -auto-approve
5
terraform state list
data.local_file.abc
// 데이터 블록, 로컬 프로바이더, 파일, abc라는 리소스 유형
6
// 테라폼 콘솔로 변수 값 확인 가능함
// 대화형으로 확인 가능. 전체 상태 정보를 확인가능하다.
terraform console
> data.local_file.abc
{
"content" = <<-EOT
t101 study - 2 week
EOT
"content_base64" = "dDEwMSBzdHVkeSAtIDJ3ZWVrCg=="
"content_base64sha256" = "GTJyISrUCoLsNeCTz8="
"content_base64sha512" = "tp0cRC/xBlJH4txY6Skb/FmexTrlNXgAeSYPtmPhvYhgwsXTWJ2jpTJkkiqclybnVu8MA=="
"content_md5" = "983a604da71f3b8291"
"content_sha1" = "75a47c30031f170e9539324f95c87"
"content_sha256" = "193272212ad40a8542a782893b0564bb0d7824f3f"
"content_sha512" = "b69d1c442ff106520994c99248aa725c9b9d5bbc30"
"filename" = "./abc.txt"
"id" = "75a47c30031f5f39324f95c87"
}
// 특정 내용만 보고 싶을때는 ?
data.local_file.abc.filename
data.local_file.abc.content
data.local_file.abc.id
exit
> data.local_file.abc.filename
"./abc.txt"
> data.local_file.abc.content
<<EOT
t101 study - 2 week
EOT
> data.local_file.abc.id
"75a47c30031f5fb5170e9539324f95c87"
1
# Terraform Code
data "<리소스 유형>" "<이름>" {
<인수> = <값>
}
# 데이터 소스 참조
data.<리소스 유형>.<이름>.<속성>
2
예시)
AWS 가용 영역 정보를 가져오고 싶을때 ?
ap-northeast-2a 정보를 가져와 보자~
# Declare the data source
data "aws_availability_zones" "available" {
state = "available"
}
resource "aws_subnet" "primary" {
availability_zone = data.aws_availability_zones.available.names[0]
# e.g. ap-northeast-2a
}
resource "aws_subnet" "secondary" {
availability_zone = data.aws_availability_zones.available.names[1]
# e.g. ap-northeast-2b
}
// 가용 영역 정보를 가져오고 싶다.
// data소스를 활용한다.
// data.aws_availability_zones.available.names[0]
// data.프로바이더 aws . 가용영역 . 이름 . names는 속성이다.
3
속성 참고
Attribute Reference
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones
1
vi main.tf
resource "local_file" "abc" {
content = "123!"
filename = "${path.module}/abc.txt"
}
data "local_file" "abc" {
filename = local_file.abc.filename
}
resource "local_file" "def" {
content = data.local_file.abc.content
filename = "${path.module}/def.txt"
}
// abc라는 파일을 만듬
// def 파일의 컨텐츠에 data사용. 123! 들어감.
2
// data는 1개
// resource는 2개
# terraform state list
data.local_file.abc
local_file.abc
local_file.def
// content = data.local_file.abc.content
// def의 콘텐츠 내용이 def.txt 파일에 들어간다.
// 123! 이 들어간다. 동일하게 콘텐츠가 123!로 들어간다.
// more abc.txt
// more def.txt
1
vi az.tf
data "aws_availability_zones" "available" {
state = "available"
}
// aws를 만들거니 init 한번 해줘야 함. aws 프로바이더 관련 파일을 다운로드된다.
2
#
terraform init -upgrade && terraform apply -auto-approve
terraform state list
# terraform state list
data.aws_availability_zones.available
data.local_file.abc
local_file.abc
local_file.def
// 데이터 소스 관련해 1개가 추가 되었다. data.aws_availability_zones.available
3
#
// 명령어로 보기
terraform state show data.aws_availability_zones.available
# data.aws_availability_zones.available:
data "aws_availability_zones" "available" {
group_names = [
"ap-northeast-2",
]
id = "ap-northeast-2"
names = [
"ap-northeast-2a",
"ap-northeast-2b",
"ap-northeast-2c",
"ap-northeast-2d",
]
state = "available"
zone_ids = [
"apne2-az1",
"apne2-az2",
"apne2-az3",
"apne2-az4",
]
}
4
// 테라폼 콘솔로도 필터링해서 볼 수 있다.
terraform console
>
data.aws_availability_zones.available
data.aws_availability_zones.available.names
data.aws_availability_zones.available.names[0]
data.aws_availability_zones.available.names[1]
data.aws_availability_zones.available.zone_ids[0]
data.aws_availability_zones.available.zone_ids[1]
exit
1
입력 변수는 인프라를 구성하는 데 필요한 속성 값을 정의해 코드의 변경 없이 여러 인프라를 생성하는 데 목적이 있다.
2
cd
mkdir 36
cd 36
3
변수는 variable로 시작되는 블록으로 구성된다.
변수 블록 뒤의 이름값은 동일 모듈 내 모든 변수 선언에서 고유해야 하며, 이 이름으로 다른 코드 내에서 참조된다
variable 블록으로 선언한다.
4
예)
# variable 블록 선언의 예
variable "<이름>" {
<인수> = <값>
}
variable "image_id" {
type = string
}
5
예제)
vi main.tf
variable "string" {
type = string
description = "var String"
default = "myString"
}
variable "number" {
type = number
default = 123
}
variable "boolean" {
default = true
}
variable "list" {
default = [
"google",
"vmware",
"amazon",
"microsoft"
]
}
output "list_index_0" {
value = var.list.0
}
output "list_all" {
value = [
for name in var.list : upper(name)
]
}
variable "map" { # Sorting
default = {
aws = "amazon",
azure = "microsoft",
gcp = "google"
}
}
variable "set" { # Sorting
type = set(string)
default = [
"google",
"vmware",
"amazon",
"microsoft"
]
}
variable "object" {
type = object({ name = string, age = number })
default = {
name = "abc"
age = 12
}
}
variable "tuple" {
type = tuple([string, number, bool])
default = ["abc", 123, true]
}
variable "ingress_rules" { # optional ( >= terraform 1.3.0)
type = list(object({
port = number,
description = optional(string),
protocol = optional(string, "tcp"),
}))
default = [
{ port = 80, description = "web" },
{ port = 53, protocol = "udp" }]
}
// 확인
terraform init && terraform plan && terraform apply -auto-approve
terraform state list
#
terraform output
list_all = [
"GOOGLE",
"VMWARE",
"AMAZON",
"MICROSOFT",
]
list_index_0 = "google"
설명
// output "list_all" {
value = [
for name in var.list : upper(name)
// output "list_index_0" { 은 리스트의 0을 가져온다. google을 가져온다.
1
입력되는 변수 타입 지정 외에, 사용자 지정한 값에 대한 유효성 검사가 가능하다.
변수 브럭 내에 validation 블럭에서 조건인 condition내에 규칙이 true 또는 false를 반환해야 하면 false인 경우 출력 되는 메시지를 정의한다.
예제)
vi main.tf
variable "image_id" {
type = string
description = "The id of the machine image (AMI) to use for the server."
validation {
condition = length(var.image_id) > 4
error_message = "The image_id value must exceed 4."
}
validation {
# regex(...) fails if it cannot find a match
condition = can(regex("^ami-", var.image_id))
error_message = "The image_id value must starting with \"ami-\"."
}
}
// id 가 최소 4글자 이상이여야 한다. ami 이미지를 잘못 넣은것을 검사할때 사용한다.
// ami 이미지 이름이 ami- 로 시작해야 한다.고 컨티션을 정의한다.
#
terraform apply -auto-approve
var.image_id
The id of the machine image (AMI) to use for the server.
Enter a value: ami (3글자만 입력했다)
...
에러가 나온다!!!
#
terraform apply -auto-approve
var.image_id
The id of the machine image (AMI) to use for the server.
Enter a value: ami- (ami- 이나 4글자이상이 아니다.)
...
│ The image_id value must exceed 4.
#
terraform apply -auto-approve
var.image_id
The id of the machine image (AMI) to use for the server.
Enter a value: ami-12345678
// 4자 이상이 되어야 한다.
// ami- 로 시작되어야 한다.
// 실행시켜봄 , 입력 변수를 지정하지 않았으니 대화형으로 물어봄.
// ami-11111111 넣어봄 = 성공
1
입력을 변수로 받아보자.
8080 포트 사용하다 9090으로 변경할 경우 사용가능하다.
vi main.tf
variable "my_password" {}
resource "local_file" "abc" {
content = var.my_password
filename = "${path.module}/abc.txt"
}
#
terraform init -upgrade
terraform apply -auto-approve
var.my_password
Enter a value: qwe123
...
// 변수를 입력 안했으니 입력 값을 받는다. qwe123 입력.
# 확인
terraform state list
local_file.abc
# 상세 보기
terraform state show local_file.abc
cat abc.txt ; echo
qwe123
2
# 해당 파일에 다른 내용으로 변경해 보기
terraform apply -auto-approve
var.my_password
Enter a value: t101mypss
...
# 확인
cat abc.txt ; echo
t101mypss
3
민감한 변수 취급?
passwd 노출하면 안된다.
출력할 때 암호화 되어 보이도록 한다.
variable "my_password" {
default = "password"
sensitive = true
}
resource "local_file" "abc" {
content = var.my_password
filename = "${path.module}/abc.txt"
}
4
확인?
# 출력 부분에 내용 안보임!
terraform apply -auto-approve
5
terraform state show local_file.abc
# 결과물 파일 확인
cat abc.txt ; echo
6
# terraform.tfstate 파일 확인
상태 저장 파일에는 저장이 되어 있다.
상태정보 파일은 보안에 유의해야 한다.
cat terraform.tfstate | grep '"content":'
"content": "password",
https://spacelift.io/blog/terraform-tfvars
7이 가장 우선순위가 높다.
1
우선순위 ?
가장 낮은 우선 순위? 대화형으로 입력하는것
main.tf
variable "my_var" {}
resource "local_file" "abc" {
content = var.my_var
filename = "${path.module}/abc.txt"
}
실행 후 입력
# 실행
terraform apply -auto-approve
var.my_var
Enter a value: var1
...
# 확인
terraform state show local_file.abc
cat abc.txt ; echo
2 번째 ?
variable 블록의 default 값
variable "my_var" {
default = "var2"
}
resource "local_file" "abc" {
content = var.my_var
filename = "${path.module}/abc.txt"
}
# 실행
terraform apply -auto-approve
# 확인
terraform state show local_file.abc
cat abc.txt ; echo
var2
3 번째 ?
환경 변수 (TF_VAR 변수 이름)
환경 변수의 접두사에 TF_VAR_ 가 포함되면 그 뒤의 문자열을 변수 이름으로 인식
환경 변수가 디폴트 값보다 우선된다!!!
# Linux/macOS
export TF_VAR_my_var=var3
terraform apply -auto-approve
# 확인
cat abc.txt ; echo
var3
4 우선 순위 ?
terraform.tfvars에 정의된 변수 선언
#
echo 'my_var="var4"' > terraform.tfvars
cat terraform.tfvars
var4
#
terraform apply -auto-approve
# 확인
cat abc.txt ; echo
var4
5 우선 순위?
*.auto.tfvars에 정의된 변수 선언
a. 보다 b.이 우선순위가 높다.
# a.auto.tfvars 파일 생성
echo 'my_var="var5_a"' > a.auto.tfvars
ls *.tfvars
#
terraform apply -auto-approve
# 확인
cat abc.txt ; echo
# b.auto.tfvars 파일 생성
echo 'my_var="var5_b"' > b.auto.tfvars
ls *.tfvars
a.auto.tfvars b.auto.tfvars terraform.tfvars
#
terraform apply -auto-approve
# 확인
cat abc.txt ; echo
var5_b
6. 우선 순위 ?
*.auto.tfvars.json에 정의된 변수 선언
# a.auto.tfvars.json 파일 생성
cat <<EOF > a.auto.tfvars.json
{
"my_var" : "var6_a"
}
EOF
ls *.tfvars ; ls *.json
#
terraform apply -auto-approve
# 확인
cat abc.txt ; echo
var5_b
# c.auto.tfvars.json 파일 생성
cat <<EOF > c.auto.tfvars.json
{
"my_var" : "var6_c"
}
EOF
ls *.tfvars ; ls *.json
#
terraform apply -auto-approve
# 확인
cat abc.txt ; echo
var6_c
// a보다 c가 우선 순위가 높다!!
7 최우선 순위 ?
CLI 실행 시 -var 인수에 지정 또는 -var-file로 파일 지정
최우선 순위로 반영 된다!!!
#
terraform apply -auto-approve -var=my_var=var7
cat abc.txt ; echo
var7
#
terraform apply -auto-approve -var=my_var=var7 -var=my_var=var8
cat abc.txt ; echo
var8
# var9.txt 파일 생성
echo 'my_var="var9"' > var9.txt
#
terraform apply -auto-approve -var=my_var=var7 -var-file="var9.txt"
cat abc.txt ; echo
var9
직접 VPC를 만들어보자!!!
1
cd
mkdir vpc
cd vpc
vi vpc.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_vpc" "myvpc" {
cidr_block = "10.10.0.0/16"
tags = {
Name = "t101-study"
}
}
2
# 배포, 확인
terraform init && terraform plan && terraform apply -auto-approve
terraform state list
terraform state show aws_vpc.myvpc
3
terraform console
aws_vpc.myvpc.id 로 정보를 볼수 있다.
4
# VPC 확인
export AWS_PAGER=""
aws ec2 describe-vpcs | jq
aws ec2 describe-vpcs --filter 'Name=isDefault,Values=false' | jq
aws ec2 describe-vpcs --filter 'Name=isDefault,Values=false' --output yaml
// default vpc 는 조회 안하도록 필터링해서 조회하자.
5
vpc dns 옵션 수정
vi main.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_vpc" "myvpc" {
cidr_block = "10.10.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "t101-study"
}
}
// 콘솔 vpc 에서 확인하자.
// 콘솔 vpc > Resource map에서 확인하자. 서브넷이 없다.
6
// subnet 만들기
provider "aws" {
region = "ap-northeast-2"oo
}
resource "aws_vpc" "myvpc" {
cidr_block = "10.10.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "t101-study"
}
}
resource "aws_subnet" "mysubnet1" {
vpc_id = aws_vpc.myvpc.id
cidr_block = "10.10.1.0/24"
availability_zone = "ap-northeast-2a"
tags = {
Name = "t101-subnet1"
}
}
resource "aws_subnet" "mysubnet2" {
vpc_id = aws_vpc.myvpc.id
cidr_block = "10.10.2.0/24"
availability_zone = "ap-northeast-2c"
tags = {
Name = "t101-subnet2"
}
}
output "aws_vpc_id" {
value = aws_vpc.myvpc.id
}
// vpc_id 는 aws_vpc.myvpc,id 를 가져와서 사용한다.
// 콘솔 > vpc Resource map에서 확인하자.
7
# 배포
terraform plan && terraform apply -auto-approve
terraform state list
aws_subnet.mysubnet1
aws_subnet.mysubnet2
aws_vpc.myvpc
// vpc 1개, 서브넷 1,2가 있다.
terraform state show aws_subnet.mysubnet1
terraform output
terraform output aws_vpc_id
terraform output -raw aws_vpc_id
# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot
# 서브넷 확인
aws ec2 describe-subnets --output text
# 참고 :
aws ec2 describe-subnets --filters "Name=vpc-id,Values=vpc-<자신의 VPC ID>"
VPCID=$(terraform output -raw aws_vpc_id)
aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPCID" | jq
aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPCID" --output table
8
igw 추가 ?
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_vpc" "myvpc" {
cidr_block = "10.10.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "t101-study"
}
}
resource "aws_subnet" "mysubnet1" {
vpc_id = aws_vpc.myvpc.id
cidr_block = "10.10.1.0/24"
availability_zone = "ap-northeast-2a"
tags = {
Name = "t101-subnet1"
}
}
resource "aws_subnet" "mysubnet2" {
vpc_id = aws_vpc.myvpc.id
cidr_block = "10.10.2.0/24"
availability_zone = "ap-northeast-2c"
tags = {
Name = "t101-subnet2"
}
}
resource "aws_internet_gateway" "myigw" {
vpc_id = aws_vpc.myvpc.id
tags = {
Name = "t101-igw"
}
}
output "aws_vpc_id" {
value = aws_vpc.myvpc.id
}
9
# 배포
terraform plan && terraform apply -auto-approve
terraform state list
aws_internet_gateway.myigw
aws_subnet.mysubnet1
aws_subnet.mysubnet2
aws_vpc.myvpc
10
디폴트 라우팅 추가 ?
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_vpc" "myvpc" {
cidr_block = "10.10.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "t101-study"
}
}
resource "aws_subnet" "mysubnet1" {
vpc_id = aws_vpc.myvpc.id
cidr_block = "10.10.1.0/24"
availability_zone = "ap-northeast-2a"
tags = {
Name = "t101-subnet1"
}
}
resource "aws_subnet" "mysubnet2" {
vpc_id = aws_vpc.myvpc.id
cidr_block = "10.10.2.0/24"
availability_zone = "ap-northeast-2c"
tags = {
Name = "t101-subnet2"
}
}
resource "aws_internet_gateway" "myigw" {
vpc_id = aws_vpc.myvpc.id
tags = {
Name = "t101-igw"
}
}
resource "aws_route_table" "myrt" {
vpc_id = aws_vpc.myvpc.id
tags = {
Name = "t101-rt"
}
}
resource "aws_route_table_association" "myrtassociation1" {
subnet_id = aws_subnet.mysubnet1.id
route_table_id = aws_route_table.myrt.id
}
resource "aws_route_table_association" "myrtassociation2" {
subnet_id = aws_subnet.mysubnet2.id
route_table_id = aws_route_table.myrt.id
}
resource "aws_route" "mydefaultroute" {
route_table_id = aws_route_table.myrt.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.myigw.id
}
output "aws_vpc_id" {
value = aws_vpc.myvpc.id
}
// 라우팅 테이블을 만들고, 연결 한다.
11
# 배포
terraform plan && terraform apply -auto-approve
terraform state list
aws_internet_gateway.myigw
aws_route.mydefaultroute
aws_route_table.myrt
aws_route_table_association.myrtassociation1
aws_route_table_association.myrtassociation2
aws_subnet.mysubnet1
aws_subnet.mysubnet2
aws_vpc.myvpc
terraform state show aws_route.mydefaultroute
# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot
# 라우팅 테이블 확인
#aws ec2 describe-route-tables --filters 'Name=tag:Name,Values=t101-rt' --query 'RouteTables[].Associations[].SubnetId'
aws ec2 describe-route-tables --filters 'Name=tag:Name,Values=t101-rt' --output table
12
보안그룹 = sg.tf
ec2 = ec2.tf
vi sg.tf
resource "aws_security_group" "mysg" {
vpc_id = aws_vpc.myvpc.id
name = "T101 SG"
description = "T101 Study SG"
}
resource "aws_security_group_rule" "mysginbound" {
type = "ingress"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.mysg.id
}
resource "aws_security_group_rule" "mysgoutbound" {
type = "egress"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.mysg.id
}
# 배포
ls *.tf
terraform plan && terraform apply -auto-approve
terraform state list
aws_security_group.mysg
aws_security_group_rule.mysginbound
aws_security_group_rule.mysgoutbound
...
terraform state show aws_security_group.mysg
terraform state show aws_security_group_rule.mysginbound
# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot
13
ec2 배포
vi ec2.tf
data "aws_ami" "my_amazonlinux2" {
most_recent = true
filter {
name = "owner-alias"
values = ["amazon"]
}
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-ebs"]
}
owners = ["amazon"]
}
resource "aws_instance" "myec2" {
depends_on = [
aws_internet_gateway.myigw
]
ami = data.aws_ami.my_amazonlinux2.id
associate_public_ip_address = true
instance_type = "t2.micro"
vpc_security_group_ids = ["${aws_security_group.mysg.id}"]
subnet_id = aws_subnet.mysubnet1.id
user_data = <<-EOF
#!/bin/bash
wget https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-x86_64
mv busybox-x86_64 busybox
chmod +x busybox
RZAZ=$(curl http://169.254.169.254/latest/meta-data/placement/availability-zone-id)
IID=$(curl 169.254.169.254/latest/meta-data/instance-id)
LIP=$(curl 169.254.169.254/latest/meta-data/local-ipv4)
echo "<h1>RegionAz($RZAZ) : Instance ID($IID) : Private IP($LIP) : Web Server</h1>" > index.html
nohup ./busybox httpd -f -p 80 &
EOF
user_data_replace_on_change = true
tags = {
Name = "t101-myec2"
}
}
output "myec2_public_ip" {
value = aws_instance.myec2.public_ip
description = "The public IP of the Instance"
}
// ami = data.aws_ami.my_amazonlinux2.id
ami를 가져 온다.
// subnet_id = aws_subnet.mysubnet1.id
서브넷 id를 확인한다.
#
ls *.tf
terraform plan && terraform apply -auto-approve
terraform state list
data.aws_ami.my_amazonlinux2
aws_instance.myec2
...
terraform state show data.aws_ami.my_amazonlinux2
terraform state show aws_instance.myec2
# 데이터소스 값 확인
terraform console
>
data.aws_ami.my_amazonlinux2.id
"ami-01c81850a6167bb81"
// ami 정보가 나온다.
data.aws_ami.my_amazonlinux2.image_id
data.aws_ami.my_amazonlinux2.name
data.aws_ami.my_amazonlinux2.owners
data.aws_ami.my_amazonlinux2.platform_details
data.aws_ami.my_amazonlinux2.hypervisor
data.aws_ami.my_amazonlinux2.architecture
exit
# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot
# 출력된 EC2 퍼블릭IP로 cul 접속 확인
terraform output -raw myec2_public_ip
52.79.154.3
MYIP=$(terraform output -raw myec2_public_ip)
while true; do curl --connect-timeout 1 http://$MYIP/ ; echo "------------------------------"; date; sleep 1; done
Mon Jul 10 17:03:00 KST 2023
<h1>RegionAz(apne2-az1) : Instance ID(i-064948a0afaa3a969) : Private IP(10.10.1.109) : Web Server</h1>
------------------------------
Mon Jul 10 17:03:01 KST 2023
<h1>RegionAz(apne2-az1) : Instance ID(i-064948a0afaa3a969) : Private IP(10.10.1.109) : Web Server</h1>
------------------------------
Mon Jul 10 17:03:02 KST 2023
14
삭제
terraform destroy -auto-approve
https://brunch.co.kr/@topasvga/2797
https://brunch.co.kr/@topasvga/3361
몰아보기
https://brunch.co.kr/@topasvga/3347
https://gasidaseo.notion.site/gasidaseo/CloudNet-Blog-c9dfa44a27ff431dafdd2edacc8a1863
감사합니다.