ㅁ AWS Cloudformation 구성 요소
구분 | 내용 |
Template | - 템플릿(TemplatE): 스택 리소스 프로비저닝 및 구성을 위해 필요한 파일 - JSON 또는 YAML 형식 텍스트 파일로 작성 - 템플릿은 CloudFormation 스택에서 프로비정닝할 리소스를 설명함 - CloudFormation Designer 또는 테그슽 편집기를 사용하여 템플릿 생성 가능 |
Cloudformation | - Stack을 생성하고 Stack에 대한 변경 사항을 확인 및 업데이트 - Stack 생성 및 변경 중 에러 감지를 통한 롤백 지원 |
Stack | - Stack이란 하나의 단위로 관리할 수 있는 AWS 리소스 모음 - Stack의 생성, 수정, 삭제를 통해 리소스 모음의 생성, 수정, 삭제 가능 - Stack의 모든 리소스는 CloudFormation 템플릿을 통해 정의됨 - Stack을 삭제하면 관련 리소스가 모두 삭제됨 |
ㅁ AWS Cloudformation Template 구성 사항
AWS Cloudformation Template은 AWS 인프라를 설명하는 JSON 또는 YAML 형식의 텍스트 파일
템플릿은 'FormatVersion, Desscription, Metadata, Parameters, Mappings, Conditions, Transform, Resources, Outputs'과 같은 섹션으로 구분
이중 필요한 섹션은 Resource이며, 그 이외 나머지 세션은 옵션
{
"AWSTemplateFormatVersion" : "version data ",
"Description" : " JSON string ",
"Metadata" : {
template metadata : 템플릿에 대한 추가 정보
},
"Parameters" : {
set of. arameters : 템플릿 실행 시 전달할 파라미터 값
},
"Mappings" : {
set of mappings : 템플릿 실행 시 선택하게 되는 값(특정 리전, 인스턴스)
},
"Conditions" : {
set of conditions : 특정 자원에 대한 생성 여부를 판단하는 조건
},
"Transform" : {
set of transforms : Serverless 애플리케이션용
},
"Resources" : {
set of. resources : 생성될 AWS 자원 나열 (필수)
},
"Outputs" : {
set of outputs : 템플릿 실행 후 만들어진 자원 결과값(자원 ID, IP 등)
}
}
ㅁ Parameter 섹션
Stack 생성 시 리소스와 Outputs 세션에서 참조할 수 있는 값들을 지정하는 섹션으로 Parameter를 전달하기 위해 사용자가 직접 타이핑하고나, 선택 옵션을 통해 선택할 수 있도록 설정하는데 필요한 섹션
Parameters 섹션 예시
"Parameters" : {
"KeyPair" : {
"Description": "The EC2 Key Pair to allow SSH access to the instance",
"Type": "String"
}
},
ㅁ Resource 섹션
Amazon EC2 인스턴스 또는 Amazon S3 버킷 등 스택에 포함할 AWS. 리소스를 선언할 때 사용
Resource 섹션은 인스턴스나 버킷을 생성하기 위해 필요한 지정된 속성값(ImageID & Instance Type) 및 KeyPair 파라미터 값에 대한 참조를 통해 구성하게 됨
"Resource" : {
"Ec2Instance" : {
"Properties" : {
"ImageId" : "ami-9d23aeea",
"InstanceType" : "m3.medium",
"KeyName" : {
"Ref" : "KeyPair"
}
},
"Type" : "AWS::EC2::Instance"
}
},
ㅁ Output 섹션
Output 섹션은 스택 생성을 위해 탬플릿의 모든 수행을 완료한 후 작업의 결과로 전달 받을 수 있는 인스턴스의 아이디, 또는 Public IP 등 템플릿 수행 후 결과값에 대한 정보를 리턴받을 수 있는 섹션
"Outputs" : {
"InstanceId" : {
"Description" : "The InstanceId of the newly created EC2 instance",
"Value" : {
"Ref" : "Ec2Instance"
}
}
},
아래 예제는 아마존 웹 서비스(AWS)로 시작하는 DevOps 책의 내용으로 github.com/saga111/AWSDevOpsDiscoveryBook 에서 확인할 수 있다.
ㅁ VPC 예제 1 (CreateVPC_01.yaml)
AWSTemplateFormatVersion: 2010-09-09
Description: Make a VPC 1
Resources:
ToturialVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 172.0.0.0/16
EnableDnsHostnames: true
InternetGateway:
Type: AWS::EC2::InternetGateway
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref ToturialVPC
InternetGatewayId: !Ref InternetGateway
PublicSubnet01:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref ToturialVPC
CidrBlock: 172.0.0.0/24
AvailabilityZone: !Select
- '0'
- !GetAZs ''
PrivateSubnet01:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref ToturialVPC
CidrBlock: 172.0.1.0/24
AvailabilityZone: !Select
- '0'
- !GetAZs ''
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref ToturialVPC
PublicRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnetRouteTableAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet01
RouteTableId: !Ref PublicRouteTable
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref ToturialVPC
PrivateSubnetRouteTableAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet01
RouteTableId: !Ref PrivateRouteTable
Outputs:
VPC:
Description: Toturial VPC ID
Value: !Ref ToturialVPC
AZ1:
Description: Availability Zone 1
Value: !GetAtt
- PublicSubnet01
- AvailabilityZone
ㅁ VPC 예제 2 (CreateVPC_02.yaml)
AWSTemplateFormatVersion: 2010-09-09
Description: Make a VPC 2
Resources:
ToturialVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 172.0.0.0/16
EnableDnsHostnames: true
InternetGateway:
Type: AWS::EC2::InternetGateway
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref ToturialVPC
InternetGatewayId: !Ref InternetGateway
PublicSubnet01:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref ToturialVPC
CidrBlock: 172.0.0.0/24
AvailabilityZone: !Select
- '0'
- !GetAZs ''
PrivateSubnet01:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref ToturialVPC
CidrBlock: 172.0.1.0/24
AvailabilityZone: !Select
- '0'
- !GetAZs ''
PublicSubnet02:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref ToturialVPC
CidrBlock: 172.0.2.0/24
AvailabilityZone: !Select
- '1'
- !GetAZs ''
PrivateSubnet02:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref ToturialVPC
CidrBlock: 172.0.3.0/24
AvailabilityZone: !Select
- '1'
- !GetAZs ''
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref ToturialVPC
PublicRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnetRouteTableAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet01
RouteTableId: !Ref PublicRouteTable
PublicSubnetRouteTableAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet02
RouteTableId: !Ref PublicRouteTable
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref ToturialVPC
PrivateSubnetRouteTableAssociation1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet01
RouteTableId: !Ref PrivateRouteTable
PrivateSubnetRouteTableAssociation2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet02
RouteTableId: !Ref PrivateRouteTable
Outputs:
VPC:
Description: Toturial VPC ID
Value: !Ref ToturialVPC
AZ1:
Description: Availability Zone 1
Value: !GetAtt
- PublicSubnet01
- AvailabilityZone
AZ2:
Description: Availability Zone 2
Value: !GetAtt
- PublicSubnet02
- AvailabilityZone
ㅁ webApplication.JSON
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "AWS CloudFormation Sample Template: Sample template that can be used to test EC2 updates. **WARNING** This template creates an Amazon Ec2 Instance. You will be billed for the AWS resources used if you create a stack from this template.",
"Parameters" : {
"InstanceType" : {
"Description" : "WebServer EC2 instance type",
"Type" : "String",
"Default" : " t2.nano",
"AllowedValues" : [
"t1.micro",
"t2.nano",
"t2.micro",
"t2.small"
],
"ConstraintDescription" : "must be a valid EC2 instance type."
}
},
"Mappings" : {
"AWSInstanceType2Arch" : {
"t1.micro" : { "Arch" : "HVM64" },
"t2.nano" : { "Arch" : "HVM64" },
"t2.micro" : { "Arch" : "HVM64" },
"t2.small" : { "Arch" : "HVM64" }
},
"AWSRegionArch2AMI" : {
"us-east-1" : {"HVM64" : "ami-0ff8a91507f77f867", "HVMG2" : "ami-0a584ac55a7631c0c"},
"us-west-2" : {"HVM64" : "ami-a0cfeed8", "HVMG2" : "ami-0e09505bc235aa82d"},
"us-west-1" : {"HVM64" : "ami-0bdb828fd58c52235", "HVMG2" : "ami-066ee5fd4a9ef77f1"},
"eu-west-1" : {"HVM64" : "ami-047bb4163c506cd98", "HVMG2" : "ami-0a7c483d527806435"},
"eu-west-2" : {"HVM64" : "ami-f976839e", "HVMG2" : "NOT_SUPPORTED"},
"eu-west-3" : {"HVM64" : "ami-0ebc281c20e89ba4b", "HVMG2" : "NOT_SUPPORTED"},
"eu-central-1" : {"HVM64" : "ami-0233214e13e500f77", "HVMG2" : "ami-06223d46a6d0661c7"},
"ap-northeast-1" : {"HVM64" : "ami-06cd52961ce9f0d85", "HVMG2" : "ami-053cdd503598e4a9d"},
"ap-northeast-2" : {"HVM64" : "ami-0a10b2721688ce9d2", "HVMG2" : "NOT_SUPPORTED"},
"ap-northeast-3" : {"HVM64" : "ami-0d98120a9fb693f07", "HVMG2" : "NOT_SUPPORTED"},
"ap-southeast-1" : {"HVM64" : "ami-08569b978cc4dfa10", "HVMG2" : "ami-0be9df32ae9f92309"},
"ap-southeast-2" : {"HVM64" : "ami-09b42976632b27e9b", "HVMG2" : "ami-0a9ce9fecc3d1daf8"},
"ap-south-1" : {"HVM64" : "ami-0912f71e06545ad88", "HVMG2" : "ami-097b15e89dbdcfcf4"},
"us-east-2" : {"HVM64" : "ami-0b59bfac6be064b78", "HVMG2" : "NOT_SUPPORTED"},
"ca-central-1" : {"HVM64" : "ami-0b18956f", "HVMG2" : "NOT_SUPPORTED"},
"sa-east-1" : {"HVM64" : "ami-07b14488da8ea02a0", "HVMG2" : "NOT_SUPPORTED"},
"cn-north-1" : {"HVM64" : "ami-0a4eaf6c4454eda75", "HVMG2" : "NOT_SUPPORTED"},
"cn-northwest-1" : {"HVM64" : "ami-6b6a7d09", "HVMG2" : "NOT_SUPPORTED"}
}
},
"Resources" : {
"WebServerInstance": {
"Type" : "AWS::EC2::Instance",
"Metadata" : {
"Comment" : "Install a simple PHP application",
"AWS::CloudFormation::Init" : {
"config" : {
"packages" : {
"yum" : {
"httpd" : [],
"php" : []
}
},
"files" : {
"/var/www/html/index.php" : {
"content" : { "Fn::Join" : ["", [
"<?php\n",
"echo '<h1>AWS CloudFormation sample PHP application</h1>';\n",
"?>\n"
]]},
"mode" : "000644",
"owner" : "apache",
"group" : "apache"
},
"/etc/cfn/cfn-hup.conf" : {
"content" : { "Fn::Join" : ["", [
"[main]\n",
"stack=", { "Ref" : "AWS::StackId" }, "\n",
"region=", { "Ref" : "AWS::Region" }, "\n"
]]},
"mode" : "000400",
"owner" : "root",
"group" : "root"
},
"/etc/cfn/hooks.d/cfn-auto-reloader.conf" : {
"content": { "Fn::Join" : ["", [
"[cfn-auto-reloader-hook]\n","triggers=post.update\n","path=Resources.WebServerInstance.Metadata.AWS::CloudFormation::Init\n", "action=/opt/aws/bin/cfn-init -s ", { "Ref" : "AWS::StackId" }, " -r WebServerInstance "," --region ", { "Ref" : "AWS::Region" }, "\n",
"runas=root\n"
]]}
}
},
"services" : {
"sysvinit" : {
"httpd" : { "enabled" : "true", "ensureRunning" : "true" },
"cfn-hup" : { "enabled" : "true", "ensureRunning" : "true",
"files" : ["/etc/cfn/cfn-hup.conf", "/etc/cfn/hooks.d/cfn-auto-reloader.conf"]}
}
}
}
}
},
"Properties": {
"ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" },{ "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] },
"InstanceType" : { "Ref" : "InstanceType" },
"SecurityGroups" : [ {"Ref" : "WebServerSecurityGroup"} ],
"UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [
"#!/bin/bash -xe\n",
"yum install -y aws-cfn-bootstrap\n",
"# Install the files and packages from the metadata\n",
"/opt/aws/bin/cfn-init -v ",
" --stack ", { "Ref" : "AWS::StackName" },
" --resource WebServerInstance ",
" --region ", { "Ref" : "AWS::Region" }, "\n",
"# Start up the cfn-hup daemon to listen for changes to the Web Server metadata\n",
"/opt/aws/bin/cfn-hup || error_exit 'Failed to start cfn-hup'\n",
"# Signal the status from cfn-init\n",
"/opt/aws/bin/cfn-signal -e $? ",
" --stack ", { "Ref" : "AWS::StackName" },
" --resource WebServerInstance ",
" --region ", { "Ref" : "AWS::Region" }, "\n"
]]}}
},
"CreationPolicy" : {
"ResourceSignal" : {
"Timeout" : "PT5M"
}
}
},
"WebServerSecurityGroup" : {
"Type" : "AWS::EC2::SecurityGroup",
"Properties" : {
"GroupDescription" : "Enable HTTP access via port 80",
"SecurityGroupIngress" : [
{"IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0"}
]
}
}
},
"Outputs" : {
"WebsiteURL" : {
"Description" : "Application URL",
"Value" : { "Fn::Join" : ["", ["http://", { "Fn::GetAtt" : [ "WebServerInstance", "PublicDnsName" ]}]] }
}
}
}
ㅁ WebApplicatoin_update.JSON
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "AWS CloudFormation Sample Template: Sample template that can be used to test EC2 updates. **WARNING** This template creates an Amazon Ec2 Instance. You will be billed for the AWS resources used if you create a stack from this template.",
"Parameters" : {
"InstanceType" : {
"Description" : "WebServer EC2 instance type",
"Type" : "String",
"Default" : "t2.nano",
"AllowedValues" : [
"t1.micro",
"t2.nano",
"t2.micro",
"t2.small"
],
"ConstraintDescription" : "must be a valid EC2 instance type."
},
"KeyName" : {
"Description" : "Name of an existing EC2 key pair for SSH access",
"Type": "AWS::EC2::KeyPair::KeyName"
},
"SSHLocation" : {
"Description" : " The IP address range that can be used to SSH to the EC2 instances",
"Type": "String",
"MinLength": "9",
"MaxLength": "18",
"Default": "0.0.0.0/0",
"AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
"ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
}
},
"Mappings" : {
"AWSInstanceType2Arch" : {
"t1.micro" : { "Arch" : "HVM64" },
"t2.nano" : { "Arch" : "HVM64" },
"t2.micro" : { "Arch" : "HVM64" },
"t2.small" : { "Arch" : "HVM64" }
},
"AWSRegionArch2AMI" : {
"us-east-1" : {"HVM64" : "ami-0ff8a91507f77f867", "HVMG2" : "ami-0a584ac55a7631c0c"},
"us-west-2" : {"HVM64" : "ami-a0cfeed8", "HVMG2" : "ami-0e09505bc235aa82d"},
"us-west-1" : {"HVM64" : "ami-0bdb828fd58c52235", "HVMG2" : "ami-066ee5fd4a9ef77f1"},
"eu-west-1" : {"HVM64" : "ami-047bb4163c506cd98", "HVMG2" : "ami-0a7c483d527806435"},
"eu-west-2" : {"HVM64" : "ami-f976839e", "HVMG2" : "NOT_SUPPORTED"},
"eu-west-3" : {"HVM64" : "ami-0ebc281c20e89ba4b", "HVMG2" : "NOT_SUPPORTED"},
"eu-central-1" : {"HVM64" : "ami-0233214e13e500f77", "HVMG2" : "ami-06223d46a6d0661c7"},
"ap-northeast-1" : {"HVM64" : "ami-06cd52961ce9f0d85", "HVMG2" : "ami-053cdd503598e4a9d"},
"ap-northeast-2" : {"HVM64" : "ami-0a10b2721688ce9d2", "HVMG2" : "NOT_SUPPORTED"},
"ap-northeast-3" : {"HVM64" : "ami-0d98120a9fb693f07", "HVMG2" : "NOT_SUPPORTED"},
"ap-southeast-1" : {"HVM64" : "ami-08569b978cc4dfa10", "HVMG2" : "ami-0be9df32ae9f92309"},
"ap-southeast-2" : {"HVM64" : "ami-09b42976632b27e9b", "HVMG2" : "ami-0a9ce9fecc3d1daf8"},
"ap-south-1" : {"HVM64" : "ami-0912f71e06545ad88", "HVMG2" : "ami-097b15e89dbdcfcf4"},
"us-east-2" : {"HVM64" : "ami-0b59bfac6be064b78", "HVMG2" : "NOT_SUPPORTED"},
"ca-central-1" : {"HVM64" : "ami-0b18956f", "HVMG2" : "NOT_SUPPORTED"},
"sa-east-1" : {"HVM64" : "ami-07b14488da8ea02a0", "HVMG2" : "NOT_SUPPORTED"},
"cn-north-1" : {"HVM64" : "ami-0a4eaf6c4454eda75", "HVMG2" : "NOT_SUPPORTED"},
"cn-northwest-1" : {"HVM64" : "ami-6b6a7d09", "HVMG2" : "NOT_SUPPORTED"}
}
},
"Resources" : {
"WebServerInstance": {
"Type" : "AWS::EC2::Instance",
"Metadata" : {
"Comment" : "Install a simple PHP application",
"AWS::CloudFormation::Init" : {
"config" : {
"packages" : {
"yum" : {
"httpd" : [],
"php" : []
}
},
"files" : {
"/var/www/html/index.php" : {
"content" : { "Fn::Join" : ["", [
"<?php\n",
"echo '<h1>AWS CloudFormation sample PHP application</h1>';\n",
"echo 'Updated version via UpdateStack';\n ",
"?>\n"
]]},
"mode" : "000644",
"owner" : "apache",
"group" : "apache"
},
"/etc/cfn/cfn-hup.conf" : {
"content" : { "Fn::Join" : ["", [
"[main]\n",
"stack=", { "Ref" : "AWS::StackId" }, "\n",
"region=", { "Ref" : "AWS::Region" }, "\n"
]]},
"mode" : "000400",
"owner" : "root",
"group" : "root"
},
"/etc/cfn/hooks.d/cfn-auto-reloader.conf" : {
"content": { "Fn::Join" : ["", [
"[cfn-auto-reloader-hook]\n",
"triggers=post.update\n",
"path=Resources.WebServerInstance.Metadata.AWS::CloudFormation::Init\n",
"action=/opt/aws/bin/cfn-init -s ", { "Ref" : "AWS::StackId" }, " -r WebServerInstance ",
" --region ", { "Ref" : "AWS::Region" }, "\n",
"runas=root\n"
]]}
}
},
"services" : {
"sysvinit" : {
"httpd" : { "enabled" : "true", "ensureRunning" : "true" },
"cfn-hup" : { "enabled" : "true", "ensureRunning" : "true",
"files" : ["/etc/cfn/cfn-hup.conf", "/etc/cfn/hooks.d/cfn-auto-reloader.conf"]}
}
}
}
}
},
"Properties": {
"ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" },{ "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] },
"InstanceType" : { "Ref" : "InstanceType" },
"KeyName" : { "Ref" : "KeyName" },
"SecurityGroups" : [ {"Ref" : "WebServerSecurityGroup"} ],
"UserData" : { "Fn::Base64" : { "Fn::Join" : ["", [
"#!/bin/bash -xe\n",
"yum install -y aws-cfn-bootstrap\n",
"# Install the files and packages from the metadata\n",
"/opt/aws/bin/cfn-init -v ",
" --stack ", { "Ref" : "AWS::StackName" },
" --resource WebServerInstance ",
" --region ", { "Ref" : "AWS::Region" }, "\n",
"# Start up the cfn-hup daemon to listen for changes to the Web Server metadata\n",
"/opt/aws/bin/cfn-hup || error_exit 'Failed to start cfn-hup'\n",
"# Signal the status from cfn-init\n",
"/opt/aws/bin/cfn-signal -e $? ",
" --stack ", { "Ref" : "AWS::StackName" },
" --resource WebServerInstance ",
" --region ", { "Ref" : "AWS::Region" }, "\n"
]]}}
},
"CreationPolicy" : {
"ResourceSignal" : {
"Timeout" : "PT5M"
}
}
},
"WebServerSecurityGroup" : {
"Type" : "AWS::EC2::SecurityGroup",
"Properties" : {
"GroupDescription" : "Enable HTTP access via port 80",
"SecurityGroupIngress" : [
{"IpProtocol" : "tcp", "FromPort" : "22", "ToPort" : "22", "CidrIp" : { "Ref" : "SSHLocation"}},
{"IpProtocol" : "tcp", "FromPort" : "80", "ToPort" : "80", "CidrIp" : "0.0.0.0/0"}
]
}
}
},
"Outputs" : {
"WebsiteURL" : {
"Description" : "Application URL",
"Value" : { "Fn::Join" : ["", ["http://", { "Fn::GetAtt" : [ "WebServerInstance", "PublicDnsName" ]}]] }
}
}
}
'AWS DevOps > DevOps 개념' 카테고리의 다른 글
IaC(Infra as a Code)의 주요 도구 (0) | 2021.02.18 |
---|---|
DevOps를 위한 기술적 요소 (0) | 2021.02.18 |