cytopia.cloudformation
Ansible 角色:创建云形成堆栈
动机 | 安装 | 特点 | 变量 | 使用 | 模板 | 差异 | 依赖 | 要求 | 许可证
Ansible 角色用于将任意数量的 Jinja2 模板渲染成 CloudFormation 文件,并部署任意数量的堆栈。
动机
该角色克服了 CloudFormation 模板本身的局限性,并充分利用了 Ansible 的特性。
- CloudFormation 限制 - CloudFormation 的语法在条件、循环和复杂变量(如数组或字典)等编程逻辑方面非常有限。通过将 CloudFormation 模板包装到 Ansible 中,您将能够在模板中使用 Jinja2 指令,从而结合 Ansible 的优雅并通过 CloudFormation 堆栈进行部署。
- 环境无关 - 通过能够使用自定义循环变量渲染 CloudFormation 模板,您可以创建完全环境无关的模板,并将其重用于生产、测试、临时和其他环境。
- 干运行 - 使用 Ansible 部署 CloudFormation 模板的另一个优点是 Ansible 支持干运行模式(
--check
)以进行 CloudFormation 部署(自 Ansible 2.4 起)。在该模式下,它会创建变更集并告诉您如果您实际推出,会发生什么变化。这样,您可以安全地测试堆栈,然后再实际应用它们。
该角色可以仅通过 cloudformation_generate_only
生成模板,或者额外部署渲染的模板。因此,当您已经设置好部署基础设施时,可以通过仅渲染模板并将其交给现有基础设施来继续使用该角色。
当模板被渲染时,角色目录中会创建一个临时的 build/
目录。此目录可以每次运行该角色时保持不变或重新创建。用 cloudformation_clean_build_env
指定行为。
安装
您可以使用 Ansible Galaxy 安装该角色:
$ ansible-galaxy install cytopia.cloudformation
或者将其克隆到您的角色目录中:
$ git clone https://github.com/cytopia/ansible-role-cloudformation /path/to/ansible/roles
特点
- 部署任意数量的 CloudFormation 模板
- 使用 Jinja2 模板引擎创建 CloudFormation 模板
- 仅渲染模板,并使用当前基础设施进行部署
- 通过 Ansible
--check
模式进行干运行,这将创建临时变更集(例如:告知您某个资源是否需要重新创建) - 通过 cloudformation_diff 模块在本地和已部署的模板之间提供逐行差异
- 使用 Ansible vault 存储加密的敏感信息
变量
概述
以下变量可在 defaults/main.yml
中使用,以设置您的基础设施。
变量 | 类型 | 默认值 | 描述 |
---|---|---|---|
cloudformation_clean_build_env |
boolean | False |
每次运行时清理 Jinja2 渲染的 CloudFormation 模板的 build/ 目录。 |
cloudformation_generate_only |
boolean | False |
与部署 CloudFormation 模板相反,您也可以仅渲染它们并在 build/ 目录中使用,以便使用您当前的基础设施进行部署。提示:通过 ansible 命令行参数指定此变量 |
cloudformation_run_diff |
boolean | False |
此角色提供了一个自定义的 Ansible CloudFormation 模块 **cloudformation_diff**。此模块生成本地可以部署的 CloudFormation 模板与当前在 AWS CloudFormation 上部署的模板之间的文本差异输出。 我为什么要这个? 当前的 CloudFormation 模块在 --check 模式下只列出变更集,这将让您知道将会发生什么 类型 的变化(例如安全组),但不知道具体会发生什么变化(哪个安全组及其值)。为了能够查看将要发生的确切变化,请在此处启用 cloudformation_diff 模块。 |
cloudformation_diff_output |
字符串 | json |
当启用 cloudformation_run_diff 时,应该指定什么样的输出差异?如果您使用 JSON 编写 CloudFormation 模板,则在此处使用 json ,如果使用 YAML 编写,则在此处使用 yaml 。 |
cloudformation_required |
数组 | [] |
可用 CloudFormation 堆栈键的数组,您希望强制它们为必需,而不是可选。将检查每个 CloudFormation 堆栈项是否符合自定义设置的所需键。如果堆栈项不包含其中的任何键,则在实际部署之前将抛出错误。 |
cloudformation_defaults |
字典 | {} |
要应用于每个 CloudFormation 堆栈的默认值字典。请注意,这些值仍然可以在每个堆栈定义中覆盖。 |
cloudformation_stacks |
数组 | [] |
要部署的 CloudFormation 堆栈的数组。 |
详细信息
本节包含有关可用的字典或数组键的更详细描述。
cloudformation_defaults
键 | 类型 | 必需 | 描述 |
---|---|---|---|
aws_access_key |
字符串 | 可选 | 要使用的 AWS 访问密钥 |
aws_secret_key |
字符串 | 可选 | 要使用的 AWS 秘密密钥 |
security_token |
字符串 | 可选 | 要使用的 AWS 安全令牌 |
profile |
字符串 | 可选 | 要使用的 AWS boto 配置文件 |
notification_arns |
字符串 | 可选 | 将堆栈通知发布到这些 ARN |
termination_protection |
boolean | 可选 | 启用或禁用堆栈的终止保护。仅适用于 botocore >= 1.7.18 |
region |
字符串 | 可选 | 部署堆栈的 AWS 区域 |
cloudformation_stacks
键 | 类型 | 必需 | 描述 |
---|---|---|---|
stack_name |
字符串 | 必需 | CloudFormation 堆栈的名称 |
template |
字符串 | 必需 | 要渲染和部署的 CloudFormation 模板的路径(无需渲染) |
aws_access_key |
字符串 | 可选 | 要使用的 AWS 访问密钥(覆盖默认值) |
aws_secret_key |
字符串 | 可选 | 要使用的 AWS 秘密密钥(覆盖默认值) |
security_token |
字符串 | 可选 | 要使用的 AWS 安全令牌(覆盖默认值) |
profile |
字符串 | 可选 | 要使用的 AWS boto 配置文件(覆盖默认值) |
notification_arns |
字符串 | 可选 | 将堆栈通知发布到这些 ARN(覆盖默认值) |
termination_protection |
boolean | 可选 | 启用或禁用堆栈的终止保护。仅适用于 botocore >= 1.7.18 |
region |
字符串 | 可选 | 部署堆栈到的 AWS 区域(覆盖默认值) |
template_parameters |
字典 | 可选 | 必需的 CloudFormation 堆栈参数 |
tags |
字典 | 可选 | 与 CloudFormation 堆栈关联的标签 |
示例
定义要应用于所有堆栈的默认值(如果未在每个堆栈定义中覆盖)
# 强制每个 CloudFormation 堆栈项必须设置 'profile'
cloudformation_required:
- profile
cloudformation_defaults:
region: eu-central-1
定义要渲染和部署的 CloudFormation 堆栈
cloudformation_stacks:
- stack_name: stack-s3
template: files/cloudformation/s3.yml.j2
profile: production
template_parameters:
bucketName: my-bucket
tags:
env: production
- stack_name: stack-lambda
template: files/cloudformation/lambda.yml.j2
profile: production
termination_protection: True
template_parameters:
lambdaFunctionName: lambda
handler: lambda.run_handler
runtime: python2.7
s3Bucket: my-bucket
s3Key: lambda.py.zip
tags:
env: production
仅渲染您的 Jinja2 模板,但不将其部署到 AWS。渲染的 CloudFormation 文件将位于该角色的 build/
目录中。
$ ansible-playbook play.yml -e cloudformation_generate_only=True
使用
简单
基础用法示例:
playbook.yml
- hosts: localhost
connection: local
roles:
- cloudformation
group_vars/all.yml
# 定义 CloudFormation 堆栈
cloudformation_stacks:
# 第一个堆栈
- stack_name: stack-s3
profile: testing
region: eu-central-1
template: files/cloudformation/s3.yml.j2
template_parameters:
bucketName: my-bucket
tags:
env: testing
# 第二个堆栈
- stack_name: stack-lambda
profile: testing
termination_protection: True
region: eu-central-1
template: files/cloudformation/lambda.yml.j2
template_parameters:
lambdaFunctionName: lambda
handler: lambda.run_handler
runtime: python2.7
s3Bucket: my-bucket
s3Key: lambda.py.zip
tags:
env: testing
高级
高级用法示例,在不同的 虚拟 主机中独立调用角色。
inventory
[my-group]
infrastructure ansible_connection=local
application ansible_connection=local
playbook.yml
# 基础设施部分
- hosts: infrastructure
roles:
- cloudformation
tags:
- infrastructure
# 应用程序部分
- hosts: application
roles:
- some-role
tags:
- some-role
- application
- hosts: application
roles:
- cloudformation
tags:
- application
group_vars/my-group.yml
stack_prefix: testing
boto_profile: testing
s3_bucket: awesome-lambda
cloudformation_defaults:
profile: "{{ boto_profile }}"
region: eu-central-1
host_vars/infrastructure.yml
cloudformation_stacks:
- stack_name: "{{ stack_prefix }}-s3"
template: files/cloudformation/s3.yml.j2
template_parameters:
bucketName: "{{ s3_bucket }}"
tags:
env: "{{ stack_prefix }}"
host_vars/application.yml
cloudformation_stacks:
- stack_name: "{{ stack_prefix }}-lambda"
template: files/cloudformation/lambda.yml.j2
template_parameters:
lambdaFunctionName: lambda
handler: lambda.run_handler
runtime: python2.7
s3Bucket: "{{ s3_bucket }}"
s3Key: lambda.py.zip
tags:
env: "{{ stack_prefix }}"
模板
本节提供了使用 Jinja2 指令对 CloudFormation 模板可以做的事情的简要概述。
示例:子网定义
以下模板可以推广到不同的临时环境,并能够包含不同数量的子网。
Ansible 变量
---
# 文件:staging.yml
vpc_subnets:
- directive: subnetA
az: a
cidr: 10.0.10.0/24
tags:
- name: Name
value: staging-subnet-a
- name: env
value: staging
- directive: subnetB
az: b
cidr: 10.0.20.0/24
tags:
- name: Name
value: staging-subnet-b
- name: env
value: staging
---
# 文件:production.yml
vpc_subnets:
- directive: subnetA
az: a
cidr: 10.0.10.0/24
tags:
- name: Name
value: prod-subnet-a
- name: env
value: production
- directive: subnetB
az: b
cidr: 10.0.20.0/24
tags:
- name: Name
value: prod-subnet-b
- name: env
value: production
- directive: subnetC
az: b
cidr: 10.0.30.0/24
tags:
- name: Name
value: prod-subnet-c
- name: env
value: production
CloudFormation 模板
AWSTemplateFormatVersion: '2010-09-09'
Description: VPC 模板
Resources:
vpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: {{ vpc_cidr_block }}
EnableDnsSupport: true
EnableDnsHostnames: true
{% if vpc_tags %}
Tags:
{% for tag in vpc_tags %}
- Key: {{ tag.name }}
Value: {{ tag.value }}
{% endfor %}
{% endif %}
{% for subnet in vpc_subnets %}
{{ subnet.directive }}:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: {{ subnet.az }}
CidrBlock: {{ subnet.cidr }}
VpcId: !Ref vpc
{% if subnet.tags %}
Tags:
{% for tag in subnet.tags %}
- Key: {{ tag.name }}
Value: {{ tag.value }}
{% endfor %}
{% endif %}
示例:安全组
使用特定 IP 规则定义安全组时,如果想要保持环境无关并使用相同的 CloudFormation 模板来管理所有环境,会非常困难。然而,可以通过提供环境特定的数组定义通过 Jinja2 来轻松克服这一点。
Ansible 变量
---
# 文件:staging.yml
# 临时环境开放,以便开发人员能够
# 从连接的 VPN 中进行连接
security_groups:
- protocol: tcp
from_port: 3306
to_port: 3306
cidr_ip: 10.0.0.1/32
- protocol: tcp
from_port: 3306
to_port: 3306
cidr_ip: 192.168.0.15/32
- protocol: tcp
from_port: 3306
to_port: 3306
cidr_ip: 172.16.0.0/16
---
# 文件:production.yml
# 生产环境的规则远少于其他
# IP 范围。
security_groups:
- protocol: tcp
from_port: 3306
to_port: 3306
cidr_ip: 10.0.15.1/32
CloudFormation 模板
AWSTemplateFormatVersion: '2010-09-09'
Description: VPC 模板
Resources:
rdsSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: RDS 安全组
{% if security_groups %}
SecurityGroupIngress:
{% for rule in security_groups %}
- IpProtocol: "{{ rule.protocol }}"
FromPort: "{{ rule.from_port }}"
ToPort: "{{ rule.to_port }}"
CidrIp: "{{ rule.cidr_ip }}"
{% endfor %}
{% endif %}
差异
当启用 cloudformation_run_diff
时,您将能够查看本地(渲染的 jinja2)模板与当前在 AWS 上部署的模板之间的逐行差异输出。为了给您这个效果,您可以查看以下示例输出:
确保使用 --diff
运行 Ansible 以使其生效:
$ ansible-playbook play.yml --diff
JSON 差异
要以 JSON 差异模式输出,请将 cloudformation_diff_output
设置为 json
。
TASK [cloudformation : diff cloudformation template file] *********************************************
--- before
+++ after
@@ -38,7 +38,6 @@
"Type": "AWS::S3::BucketPolicy"
},
"s3Bucket": {
- "DeletionPolicy": "Retain",
"Properties": {
"BucketName": {
"Ref": "bucketName"
YAML 差异
要以 YAML 差异模式输出,请将 cloudformation_diff_output
设置为 yaml
。
TASK [cloudformation : diff cloudformation template file] *********************************************
--- before
+++ after
@@ -14,7 +14,6 @@
Service: !Sub 'logs.${AWS::Region}.amazonaws.com'
Bucket: !Ref 's3Bucket'
s3Bucket:
- DeletionPolicy: Retain
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref 'bucketName'
依赖
此角色不依赖于其他角色。
要求
使用至少 Ansible 2.5,以便也有适用于 CloudFormation 的 --check
模式。
当使用本地和远程 CloudFormation 模板的逐行差异时(cloudformation_run_diff=True
),需要 Python 模块 cfn_flip
。这可以轻松地在本地安装:
$ pip install cfn_flip
许可证
版权所有 (c) 2017 cytopia
Ansible role to render an arbitrary number of Jinja2 templates into cloudformation files and create any number of stacks.
ansible-galaxy install cytopia.cloudformation