Skip to content

Files

Latest commit

f87fb9e · Dec 29, 2021

History

History
1122 lines (848 loc) · 45.1 KB

File metadata and controls

1122 lines (848 loc) · 45.1 KB

九、构建云网络

现在我们已经在数字海洋推出了服务器,我们将开始考虑在亚马逊网络服务 ( AWS 内推出服务。

在我们启动实例之前,我们需要为它们创建一个托管网络。这被称为 VPC,我们需要将一些不同的元素整合到一个剧本中来创建一个,然后我们就可以在我们的实例中使用它。

在本章中,我们将:

  • 获得对 AWS 的介绍
  • 介绍我们正在努力实现的目标以及原因
  • 创建 VPC、子网和路由—网络和路由
  • 创建安全组—防火墙
  • 创建一个弹性负载平衡(ELB)—负载平衡器

技术要求

在本章中,我们将使用 AWS 您需要管理员权限才能创建允许 Ansible 与您的帐户交互所需的角色。与其他章节一样,您可以在随附的 GitHub 存储库中的Chapter09文件夹中找到完整的行动手册,网址为

AWS 简介

AWS 从 2002 年就有了;它从提供一些不以任何方式连接的服务开始——它以这种形式发展,直到 2006 年初重新启动。重新启动的 AWS 汇集了三项服务:

  • 亚马逊弹性计算云(Amazon EC2) :这是 AWS 计算服务
  • 亚马逊简单存储服务(亚马逊 S3) :亚马逊可扩展的对象存储可访问服务
  • 亚马逊简单队列服务(亚马逊 SQS) :该服务主要为 web 应用提供消息队列

自 2006 年以来,它已经从三个独特的服务发展到超过 160 个,覆盖超过 15 个主要领域,例如:

  • 计算
  • 储存;储备
  • 数据库ˌ资料库
  • 网络和内容交付
  • 机器学习
  • 分析学
  • 安全性、身份和合规性
  • 物联网

在 2018 年 2 月的财报电话会议上透露,AWS 2017 年营收为 174.6 亿美元,占亚马逊总营收的 10%;对于最初提供共享空闲计算时间的服务来说,这还不错。

在撰写本报告时,AWS 横跨 18 个地理区域,总共拥有 54 个可用性区域:https://aws.amazon.com/about-aws/global-infrastructure/

那么是什么让 AWS 如此成功呢?不仅是它的覆盖面,还有它提供服务的方式。报道援引 AWS 首席执行官安迪·贾西的话说:

"Our mission is to enable any developer or any company to be able to build all their technology applications on top of our infrastructure technology platform."

作为个人,您可以访问与大型跨国公司相同的应用编程接口、服务、区域、工具以及定价模型,也可以访问消费服务的亚马逊本身。这确实给了你从小规模和大规模开始的自由。例如,亚马逊 EC2 实例从 t2.nano (1 个 vCPU,0.5G)的每月 4.50 美元左右开始,一直到 x1e.32xlarge (128 个 vCPU,3,904 GB RAM,两个 1920 GB SSD 存储)的每月 19,000 美元以上,正如您所看到的,可以想象到的每种工作负载都有实例类型。

这两个实例和大多数服务都按现收现付模块计费,从 EC2 实例的每秒计费或您使用的存储的每月每 GB 计费。

亚马逊虚拟私有云概述

在这一章,我们将集中推出亚马逊虚拟私有云 ( 亚马逊 VPC);这是网络层,它将容纳我们将在下一章推出的计算和其他亚马逊服务。

我们将推出的 VPC 行动手册概述如下:

正如你所看到的,我们将把我们的 VPC 投放到 EU-西部#1 (爱尔兰)地区;我们将为我们的 EC2 实例以及应用弹性负载平衡器跨越所有三个可用性区域。我们将只对我们的亚马逊关系数据库服务 ( RDS )实例使用两个可用性区域,并对亚马逊弹性文件系统 ( 亚马逊 EFS )卷使用两个区域。

这意味着我们的 Ansible 行动手册需要创建/配置以下内容:

  • 一个亚马逊 VPC
  • EC2 实例的三个子网
  • 亚马逊无线电数据系统实例的两个子网
  • 亚马逊 EFS 卷的两个子网
  • 应用负载平衡器的三个子网
  • 一个互联网网关

我们还需要配置以下内容:

  • 允许通过互联网网关访问的一种路由
  • 一个安全组,允许每个人访问应用负载平衡器上的端口80 (HTTP)和443 (HTTPS)
  • 一个安全组,允许可信源访问 EC2 实例上的端口22 (SSH)
  • 一个安全组,允许从应用负载平衡器访问端口80 (HTTP)到 EC2 实例
  • 一个安全组,允许从 EC2 实例访问亚马逊 RDS 实例上的端口3306 (MySQL)
  • 一个安全组允许从 EC2 实例访问亚马逊 EFS 卷上的端口2049 (NGF)

这将为我们提供基本网络,允许对除应用负载平衡器之外的所有内容进行限制性访问,我们希望应用负载平衡器公开可用。在我们开始创建部署网络的 Ansible 行动手册之前,我们需要获得一个 AWS API 访问密钥和机密。

创建访问密钥和机密

为您自己的 AWS 用户创建一个访问密钥和机密密钥是非常可能的,这将使 Ansible 能够完全访问您的 AWS 帐户。

正因为如此,我们将考虑为 Ansible 创建一个用户,该用户只拥有访问 AWS 部分的权限,我们知道 Ansible 将需要与这些部分进行交互,以完成我们在本章中介绍的任务。我们将给予 Ansible 对以下服务的完全访问权限:

  • 亚马逊 VPC
  • 亚马逊 EC2
  • 亚马逊无线电数据系统
  • 亚马逊 EFS

为此,请登录 AWS 控制台,可在https://console.aws.amazon.com/找到该控制台。登录后,单击服务,可以在屏幕顶部的菜单中找到该服务。在打开的菜单中,在搜索框中输入IAM,然后点击应该是唯一的结果,IAM 管理用户访问和加密密钥。这将带您进入类似以下内容的页面:

在 IAM 页面,点击左侧菜单中的组;我们将创建一个具有分配给它的权限的组,然后我们将创建一个用户并将其分配给我们的组。

进入“组”页面后,单击“创建新组”按钮。这个过程有三个主要步骤,第一步是设置组的名称。在提供的空白处,输入组名Ansible,然后点击下一步按钮。

下一步是我们附加策略的地方;我们将使用亚马逊提供的。选择 AmazonEC2FullAccess、AmazonVPCFullAccess、AmazonRDSFullAccess 和 Amazon elastifilesystemfull access;一旦四个都被选中,点击下一步按钮。

你现在应该在一个页面上,给你一个你已经选择的选项的概述;它应该如下所示:

当您对您的选择满意时,单击创建组按钮,然后单击左侧菜单中的用户。

在“用户”页面上,单击“添加用户”,这将带您进入一个页面,您可以在其中配置所需的用户名以及您想要的用户类型。输入以下信息:

  • 用户名:在这里输入Ansible
  • AWS 访问类型:选中它表示编程访问的地方旁边的框;我们的Ansible用户不需要 AWS 管理控制台访问,所以不要选中该选项

您现在应该能够单击“下一步:权限”按钮;这将带您进入为用户设置权限的页面。由于我们已经创建了组,请从列表中选择Ansible组,然后单击下一步:查看,这将带您浏览您输入的选项。如果你对它们满意,那么点击创建用户按钮。

这将带您进入如下页面(我故意模糊了访问密钥标识):

如您所见,成功消息告诉您,这是您最后一次能够下载凭据,这意味着您将无法再次看到机密访问密钥。单击“显示”按钮并记下密钥,或者单击“下载”。csv 按钮;您将没有机会恢复机密访问密钥,只能让它过期并生成新密钥。

现在,我们有了一个用户的访问密钥 ID 和机密访问密钥,该用户拥有使用 Ansible 启动我们的 VPC 所需的权限,我们可以开始编写剧本了。

VPC 剧本

我们需要讨论的第一件事是如何以安全可靠的方式将我们的访问密钥 ID 以及机密访问密钥传递给 Ansible。由于我将在 GitHub 上的公共存储库中共享最终剧本,我不想与世界共享我的 AWS 密钥,因为这可能会变得昂贵!通常,如果它是一个私有存储库,我会使用 Ansible Vault 来加密密钥,并将它们与其他潜在的敏感数据(如部署密钥等)一起包含在其中。

在这种情况下,我不想在存储库中包含任何加密的信息,因为这意味着人们需要解密它,编辑值,然后重新加密它。幸运的是,Ansible 提供的 AWS 模块允许您在 Ansible 控制器上设置两个环境变量;这些变量将作为行动手册执行的一部分来阅读。

要设置变量,请运行以下命令,确保用您自己的访问密钥和密码替换内容(下面列出的信息只是占位符值):

$ export AWS_ACCESS_KEY=AKIAI5KECPOTNTTVM3EDA
$ export AWS_SECRET_KEY=Y4B7FFiSWl0Am3VIFc07lgnc/TAtK5+RpxzIGTr

设置后,您可以通过运行以下命令查看内容:

$ echo $AWS_ACCESS_KEY

从输出中可以看到,这将显示AWS_ACCESS_KEY变量的内容:

现在,我们有了一种将凭据传递给 Ansible 的方法,我们可以通过运行以下命令来创建行动手册结构:

$ mkdir vpc vpc/group_vars vpc/roles
$ touch vpc/production vpc/site.yml vpc/group_vars/common.yml
$ cd vpc

现在我们已经有了基础,我们可以开始创建角色;与前几章不同,我们将在添加每个角色后运行剧本,以便更详细地讨论发生了什么。

VPC 的角色

我们要创造的第一个角色是创造 VPC 本身的角色。我们将在即将到来的角色中配置/创建的所有内容都需要托管在 VPC 中,因此需要创建它,然后我们需要收集一些相关信息,以便我们可以继续进行剧本的其余部分。

要引导角色,请在工作文件夹中运行以下命令:

$ ansible-galaxy init roles/vpc

现在我们已经有了角色的文件,打开roles/vpc/tasks/main.yml并输入以下内容:

- name: ensure that the VPC is present
  ec2_vpc_net:
    region: "{{ ec2_region }}"
    name: "{{ environment_name }}"
    state: present
    cidr_block: "{{ vpc_cidr_block }}"
    resource_tags: { "Name" : "{{ environment_name }}", "Environment" : "{{ environment_name }}" }
  register: vpc_info

# - name: print the information we have registered
#   debug:
#     msg: "{{ vpc_info }}"

如您所见,我们正在使用一个名为ec2_vpc_net的 Ansible 模块;该模块取代了在 Ansible 2.5 中被弃用和删除的名为ec2_vpc的模块。

我们在任务中使用了三个变量;前两个,即ec2_regionenvironment_name,应该放在group_vars/common.yml中,因为我们将在创建的大多数角色中使用它们:

environment_name: "my-vpc"
ec2_region: "eu-west-1"

这两个变量都是不言自明的:第一个是我们将用来引用我们将在 AWS 中发布的各种元素的名称,第二个让 Ansible 知道我们希望在哪里创建 VPC。

第三个变量vpc_cidr_block应该放在roles/vpc/defaults/main.yml文件中:

vpc_cidr_block: "10.0.0.0/16"

这定义了我们想要使用的 CIDR;10.0.0.0/16表示我们希望保留 10.0.0.1 到 10.0.255.254,这为我们提供了大约 65,534 个可用 IP 地址的范围,这对我们的测试来说应该足够了。

在第一个任务结束时,我们使用 register 标志获取在创建 VPC 期间捕获的所有内容,并将其注册为变量。然后,我们使用调试模块将这些内容打印到屏幕上。

现在我们有了第一个角色,我们可以给我们的site.yml文件添加一些内容:

- name: Create and configure an Amazon VPC
  hosts: localhost
  connection: local
  gather_facts: True

  vars_files:
    - group_vars/common.yml
    - group_vars/firewall.yml
    - group_vars/secrets.yml
    - group_vars/words.yml
    - group_vars/keys.yml

  roles:
    - roles/vpc

然后使用以下方法运行行动手册:

$ ansible-playbook site.yml

这应该会给你类似以下的输出:

[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit
localhost does not match 'all'

PLAY [Create and configure an Amazon VPC] *******************************************************

TASK [Gathering Facts] **************************************************************************
ok: [localhost]

TASK [roles/vpc : ensure that the VPC is present] ***********************************************
changed: [localhost]

TASK [roles/vpc : print the information we have registered] *************************************
ok: [localhost] => {
 "msg": {
 "changed": true,
 "failed": false,
 "vpc": {
 "cidr_block": "10.0.0.0/16",
 "cidr_block_association_set": [
 {
 "association_id": "vpc-cidr-assoc-1eee5575",
 "cidr_block": "10.0.0.0/16",
 "cidr_block_state": {
 "state": "associated"
 }
 }
 ],
 "classic_link_enabled": false,
 "dhcp_options_id": "dopt-44851321",
 "id": "vpc-ccef75aa",
 "instance_tenancy": "default",
 "is_default": false,
 "state": "available",
 "tags": {
 "Environment": "my-vpc",
 "Name": "my-vpc"
 }
 }
 }
}

PLAY RECAP **************************************************************************************
localhost : ok=3 changed=1 unreachable=0 failed=0

检查 AWS 控制台的 VPC 部分应该会显示 VPC 已经创建,并且信息应该与 Ansible 捕获的信息相匹配:

如果您重新运行剧本,您将会注意到,与其再次创建 VPC,Ansible 认识到已经有一个名为my-vpc的 VPC,它会发现已经存在的 VPC 的信息并填充vpc_info变量。这很有用,因为我们将在下一个角色中使用收集的信息。

子网角色

现在我们有了我们的 VPC,我们可以开始填充它。我们首先要配置的是 10 个子网。如果您还记得,我们需要以下内容:

  • 三个 EC2 实例
  • 三个 ELB 实例
  • 两个无线电数据系统实例
  • 两个 EFS 实例

通过从工作目录运行以下命令来创建角色:

$ ansible-galaxy init roles/subnets

现在,在roles/subnets/defaults/main.yml中,输入以下内容:

the_subnets:
  - { use: 'ec2', az: 'a', subnet: '10.0.10.0/24' }
  - { use: 'ec2', az: 'b', subnet: '10.0.11.0/24' }
  - { use: 'ec2', az: 'c', subnet: '10.0.12.0/24' }
  - { use: 'elb', az: 'a', subnet: '10.0.20.0/24' }
  - { use: 'elb', az: 'b', subnet: '10.0.21.0/24' }
  - { use: 'elb', az: 'c', subnet: '10.0.22.0/24' }
  - { use: 'rds', az: 'a', subnet: '10.0.30.0/24' }
  - { use: 'rds', az: 'b', subnet: '10.0.31.0/24' }
  - { use: 'efs', az: 'b', subnet: '10.0.40.0/24' }
  - { use: 'efs', az: 'c', subnet: '10.0.41.0/24' }

如您所见,我们有一个变量列表,其中包含子网的用途(ec2elbrdsefs)、子网应该创建在哪个可用性区域(abc)以及子网本身。在这里,我们对每个可用性区域使用 a /24。

像这样分组子网应该可以消除创建子网时的一些重复。但是,并没有全部去掉,从roles/subnets/tasks/main.yml的内容可以看出:

- name: ensure that the subnets are present
  ec2_vpc_subnet:
    region: "{{ ec2_region }}"
    state: present
    vpc_id: "{{ vpc_info.vpc.id }}"
    cidr: "{{ item.subnet }}"
    az: "{{ ec2_region }}{{ item.az }}"
    resource_tags: 
      "Name" : "{{ environment_name }}_{{ item.use }}_{{ ec2_region }}{{ item.az }}"
      "Environment" : "{{ environment_name }}"
      "Use" : "{{ item.use }}"
  with_items: "{{ the_subnets }}"

任务开始非常简单:这里我们使用ec2_vpc_subnet模块通过循环the_subnets变量来创建子网。如您所见,我们正在使用我们在前面角色中注册的变量来正确地将子网部署到我们的 VPC;这是vpc_info.vpc.id

您可能已经注意到我们没有注册此任务的结果;这是因为,如果我们这样做了,我们将拥有所有十个子网的信息。相反,我们希望根据子网的用途来分解这些信息。为了找出这些信息,我们可以使用ec2_vpc_subnet_facts模块,根据我们在创建子网时设置的EnvironmentUse标签进行过滤,收集信息:

- name: gather information about the ec2 subnets
  ec2_vpc_subnet_facts:
    region: "{{ ec2_region }}"
    filters:
      "tag:Use": "ec2"
      "tag:Environment": "{{ environment_name }}"
  register: subnets_ec2

- name: gather information about the elb subnets
  ec2_vpc_subnet_facts:
    region: "{{ ec2_region }}"
    filters:
      "tag:Use": "elb"
      "tag:Environment": "{{ environment_name }}"
  register: subnets_elb

- name: gather information about the rds subnets
  ec2_vpc_subnet_facts:
    region: "{{ ec2_region }}"
    filters:
      "tag:Use": "rds"
      "tag:Environment": "{{ environment_name }}"
  register: subnets_rds

- name: gather information about the efs subnets
  ec2_vpc_subnet_facts:
    region: "{{ ec2_region }}"
    filters:
      "tag:Use": "efs"
      "tag:Environment": "{{ environment_name }}"
  register: subnets_efs

如您所见,这里我们过滤并注册了四组不同的信息:subnets_ec2subnets_elbsubnets_rdssubnets_efs。然而,我们还没有完全了解,因为我们只想知道子网标识,而不是每个子网的所有信息。

为此,我们需要使用set_fact模块和一些 Jinja2 过滤:

- name: register just the IDs for each of the subnets
  set_fact:
    subnet_ec2_ids: "{{ subnets_ec2.subnets | map(attribute='id') | list  }}"
    subnet_elb_ids: "{{ subnets_elb.subnets | map(attribute='id') | list  }}"
    subnet_rds_ids: "{{ subnets_rds.subnets | map(attribute='id') | list  }}"
    subnet_efs_ids: "{{ subnets_efs.subnets | map(attribute='id') | list  }}"

最后,我们可以通过将变量连接在一起,在一个大列表中将所有的标识打印到屏幕上:

# - name: print all the ids we have registered
#   debug:
#     msg: "{{ subnet_ec2_ids + subnet_elb_ids + subnet_rds_ids
      + subnet_efs_ids }}"

现在我们已经将我们角色的所有部分整合在一起,让我们运行它。更新site.yml文件,使其如下所示:

- name: Create and configure an Amazon VPC
  hosts: localhost
  connection: local
  gather_facts: True

  vars_files:
    - group_vars/common.yml

  roles:
    - roles/vpc
    - roles/subnets

然后使用以下方法运行行动手册:

$ ansible-playbook site.yml

在运行剧本之前,我评论了 VPC 角色中的debug任务。您的输出应该看起来像下面的输出;你可能已经注意到 VPC 的角色返回了一个ok因为我们的 VPC 在那里:

[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [Create and configure an Amazon VPC] *******************************************************

TASK [Gathering Facts] **************************************************************************
ok: [localhost]

TASK [roles/vpc : ensure that the VPC is present] ***********************************************
ok: [localhost]

TASK [roles/subnets : ensure that the subnets are present] **************************************
changed: [localhost] => (item={u'subnet': u'10.0.10.0/24', u'use': u'ec2', u'az': u'a'})
changed: [localhost] => (item={u'subnet': u'10.0.11.0/24', u'use': u'ec2', u'az': u'b'})
changed: [localhost] => (item={u'subnet': u'10.0.12.0/24', u'use': u'ec2', u'az': u'c'})
changed: [localhost] => (item={u'subnet': u'10.0.20.0/24', u'use': u'elb', u'az': u'a'})
changed: [localhost] => (item={u'subnet': u'10.0.21.0/24', u'use': u'elb', u'az': u'b'})
changed: [localhost] => (item={u'subnet': u'10.0.22.0/24', u'use': u'elb', u'az': u'c'})
changed: [localhost] => (item={u'subnet': u'10.0.30.0/24', u'use': u'rds', u'az': u'a'})
changed: [localhost] => (item={u'subnet': u'10.0.31.0/24', u'use': u'rds', u'az': u'b'})
changed: [localhost] => (item={u'subnet': u'10.0.40.0/24', u'use': u'efs', u'az': u'b'})
changed: [localhost] => (item={u'subnet': u'10.0.41.0/24', u'use': u'efs', u'az': u'c'})

TASK [roles/subnets : gather information about the ec2 subnets] *********************************
ok: [localhost]

TASK [roles/subnets : gather information about the elb subnets] *********************************
ok: [localhost]

TASK [roles/subnets : gather information about the rds subnets] *********************************
ok: [localhost]

TASK [roles/subnets : gather information about the efs subnets] *********************************
ok: [localhost]

TASK [roles/subnets : register just the IDs for each of the subnets] ****************************
ok: [localhost]

TASK [roles/subnets : print all the ids we have registered] *************************************
ok: [localhost] => {
 "msg": [
 "subnet-2951e761",
 "subnet-24ea4a42",
 "subnet-fce80ba6",
 "subnet-6744f22f",
 "subnet-64eb083e",
 "subnet-51f15137",
 "subnet-154ef85d",
 "subnet-19e9497f",
 "subnet-4340f60b",
 "subnet-5aea0900"
 ]
}

PLAY RECAP **************************************************************************************
localhost : ok=9 changed=1 unreachable=0 failed=0

唯一记录的变化是子网的增加;如果我们再次运行它,那么当子网存在时,这也将返回一个ok。如您所见,我们返回了十个子网标识,这也反映在 AWS 控制台中:

现在我们有了子网,我们需要确保 EC2 实例可以路由到互联网。

互联网网关角色

虽然互联网网关角色将只包含我们在common.yml中定义的变量,但是通过在之前的任务中收集信息,我们应该继续引导roles文件夹,就像我们一直在做的那样:

$ ansible-galaxy init roles/gateway

我们将在角色中使用两个模块;第一个,ec2_vpc_igw,创建互联网网关并标记它:

- name: ensure that there is an internet gateway
  ec2_vpc_igw:
    region: "{{ ec2_region }}"
    vpc_id: "{{ vpc_info.vpc.id }}"
    state: present
    tags:
      "Name": "{{ environment_name }}_internet_gateway"
      "Environment": "{{ environment_name }}"
      "Use": "gateway"
  register: igw_info

然后,我们将注册的互联网网关信息打印到屏幕上:

# - name: print the information we have registered
#   debug:
#     msg: "{{ igw_info }}"

在最终使用第二个模块ec2_vpc_route_table之前,我们创建了一条路由,使用我们在上一个角色中创建的标识列表,将所有去往0.0.0.0/0的流量发送到新创建的仅用于 EC2 子网的互联网网关:

- name: check that we can route through internet gateway
  ec2_vpc_route_table:
    region: "{{ ec2_region }}"
    vpc_id: "{{ vpc_info.vpc.id }}"
    subnets: "{{ subnet_ec2_ids + subnet_elb_ids }}"
    routes:
      - dest: 0.0.0.0/0
        gateway_id: "{{ igw_info.gateway_id }}"
    resource_tags:
      "Name": "{{ environment_name }}_outbound"
      "Environment": "{{ environment_name }}"

添加site.yml文件的作用:

- name: Create and configure an Amazon VPC
  hosts: localhost
  connection: local
  gather_facts: True

  vars_files:
    - group_vars/common.yml

  roles:
    - roles/vpc
    - roles/subnets
    - roles/gateway

然后运行剧本:

$ ansible-playbook site.yml

在这一点上,由于我们已经运行了三次剧本,我应该很快提到WARNING。这是因为我们没有使用库存文件,因为我们已经在site.yml文件的顶部定义了localhost。您应该会收到如下输出;我再次评论了以前角色的调试任务:

[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [Create and configure an Amazon VPC] *******************************************************

TASK [Gathering Facts] **************************************************************************
ok: [localhost]

TASK [roles/vpc : ensure that the VPC is present] ***********************************************
ok: [localhost]

TASK [roles/subnets : ensure that the subnets are present] **************************************
ok: [localhost] => (item={u'subnet': u'10.0.10.0/24', u'use': u'ec2', u'az': u'a'})
ok: [localhost] => (item={u'subnet': u'10.0.11.0/24', u'use': u'ec2', u'az': u'b'})
ok: [localhost] => (item={u'subnet': u'10.0.12.0/24', u'use': u'ec2', u'az': u'c'})
ok: [localhost] => (item={u'subnet': u'10.0.20.0/24', u'use': u'elb', u'az': u'a'})
ok: [localhost] => (item={u'subnet': u'10.0.21.0/24', u'use': u'elb', u'az': u'b'})
ok: [localhost] => (item={u'subnet': u'10.0.22.0/24', u'use': u'elb', u'az': u'c'})
ok: [localhost] => (item={u'subnet': u'10.0.30.0/24', u'use': u'rds', u'az': u'a'})
ok: [localhost] => (item={u'subnet': u'10.0.31.0/24', u'use': u'rds', u'az': u'b'})
ok: [localhost] => (item={u'subnet': u'10.0.40.0/24', u'use': u'efs', u'az': u'b'})
ok: [localhost] => (item={u'subnet': u'10.0.41.0/24', u'use': u'efs', u'az': u'c'})

TASK [roles/subnets : gather information about the ec2 subnets] *********************************
ok: [localhost]

TASK [roles/subnets : gather information about the elb subnets] *********************************
ok: [localhost]

TASK [roles/subnets : gather information about the rds subnets] *********************************
ok: [localhost]

TASK [roles/subnets : gather information about the efs subnets] *********************************
ok: [localhost]

TASK [roles/subnets : register just the IDs for each of the subnets] ****************************
ok: [localhost]

TASK [roles/gateway : ensure that there is an internet gateway] *********************************
changed: [localhost]

TASK [roles/gateway : print the information we have registered] *********************************
ok: [localhost] => {
 "msg": {
 "changed": true,
 "failed": false,
 "gateway_id": "igw-a74235c0",
 "tags": {
 "Environment": "my-vpc",
 "Name": "my-vpc_internet_gateway",
 "Use": "gateway"
 },
 "vpc_id": "vpc-ccef75aa"
 }
}

TASK [roles/gateway : check that we can route through internet gateway] *************************
changed: [localhost]

PLAY RECAP **************************************************************************************
localhost : ok=11 changed=2 unreachable=0 failed=0

回到 AWS 控制台。您应该能够查看互联网网关:

在前面的截图中,您可以看到默认的 VPC 互联网网关,也可以看到我们使用 Ansible 创建的网关。您还可以看到我们创建的路由表:

在这里,您可以看到 Ansible 路由与我们创建 VPC 时创建的默认路由一起配置。该默认路由被设置为主路由,允许在我们上一角色中添加的所有子网之间进行路由。

接下来,我们需要在 VPC 增加一些安全小组。

安全组角色

我们对这个角色有几个不同的目标。第一个很简单:创建一个安全小组,向世界开放港口80443,或者用知识产权术语来说0.0.0.0/0。第二个目的是创建一个规则,允许 SSH 访问,但仅限于我们,第三个目的是确保只有我们的 EC2 实例可以连接到 RDS 和 EFS。

第一个目标很简单,因为0.0.0.0/0是一个已知的量,其他的就没那么多了。我们的知识产权经常会改变,所以我们不想硬编码它。此外,我们还没有启动任何 EC2 实例,因此我们不知道它们的 IP 地址。

让我们引导角色并创建第一组规则:

$ ansible-galaxy init roles/securitygroups

我们将使用ec2_group模块在roles/securitygroups/tasks/main.yml中创建我们的第一个组:

- name: provision elb security group
  ec2_group:
    region: "{{ ec2_region }}"
    vpc_id: "{{ vpc_info.vpc.id }}"
    name: "{{ environment_name }}-elb"
    description: "opens port 80 and 443 to the world"
    tags:
      "Name": "{{ environment_name }}-elb"
      "Environment": "{{ environment_name }}"
    rules:
      - proto: "tcp"
        from_port: "80"
        to_port: "80"
        cidr_ip: "0.0.0.0/0"
        rule_desc: "allow all on port 80"
      - proto: "tcp"
        from_port: "443"
        to_port: "443"
        cidr_ip: "0.0.0.0/0"
        rule_desc: "allow all on port 443"
  register: sg_elb

这里我们创建了一个名为my-vpc-elb的规则,标记它,然后开放港口804430.0.0.0/0。如您所见,当您知道源 IP 地址很简单时,添加规则很容易。现在让我们看看为 EC2 实例添加规则;这个有点不同。

首先,我们不想让每个人都能访问我们实例上的 SSH,所以我们需要知道我们的 Ansible 控制器的 IP 地址。为此,我们将使用ipify_facts模块。

ipify is a free web API which simply returns the current public IP address of the device you use to query the API.

从下面的任务中可以看出,我们正在对 ipify 进行 API 调用,然后在将 IP 地址打印到屏幕之前设置一个包含 IP 地址的事实:

- name: find out your current public IP address using https://ipify.org/
  ipify_facts:
  register: public_ip

- name: set your public ip as a fact
  set_fact:
    your_public_ip: "{{ public_ip.ansible_facts.ipify_public_ip }}/32"

# - name: print your public ip address
#   debug:
#     msg: "Your public IP address is {{ your_public_ip }}"

现在我们知道了允许访问端口22的 IP 地址,我们可以创建一个名为my-vpc-ec2的规则:

- name: provision ec2 security group
  ec2_group:
    region: "{{ ec2_region }}"
    vpc_id: "{{ vpc_info.vpc.id }}"
    name: "{{ environment_name }}-ec2"
    description: "opens port 22 to a trusted IP and port 80 to the elb group"
    tags:
      "Name": "{{ environment_name }}-ec2"
      "Environment": "{{ environment_name }}"
    rules:
      - proto: "tcp"
        from_port: "22"
        to_port: "22"
        cidr_ip: "{{ your_public_ip }}"
        rule_desc: "allow {{ your_public_ip }} access to port 22"
      - proto: "tcp"
        from_port: "80"
        to_port: "80"
        group_id: "{{ sg_elb.group_id }}"
        rule_desc: "allow {{ sg_elb.group_id }} access to port 80"
  register: sg_ec2

my-vpc-ec2 安全组中还有第二条规则;该规则允许从任何附加了my-vpc-elb 安全组的来源访问端口80,在我们的例子中,该安全组将只是 ELBs。这意味着任何人都可以访问我们的 EC2 实例上的端口80的唯一方式是通过 ELB。

我们将使用相同的原则来创建 RDS 和 EFS 组,这一次只允许对端口33062049分别访问my-vpc-ec2安全组中的任何实例:

- name: provision rds security group
  ec2_group:
    region: "{{ ec2_region }}"
    vpc_id: "{{ vpc_info.vpc.id }}"
    name: "{{ environment_name }}-rds"
    description: "opens port 3306 to the ec2 instances"
    tags:
      "Name": "{{ environment_name }}-rds"
      "Environment": "{{ environment_name }}"
    rules:
      - proto: "tcp"
        from_port: "3306"
        to_port: "3306"
        group_id: "{{ sg_ec2.group_id }}"
        rule_desc: "allow {{ sg_ec2.group_id }} access to port 3306"
  register: sg_rds

- name: provision efs security group
  ec2_group:
    region: "{{ ec2_region }}"
    vpc_id: "{{ vpc_info.vpc.id }}"
    name: "{{ environment_name }}-efs"
    description: "opens port 2049 to the ec2 instances"
    tags:
      "Name": "{{ environment_name }}-efs"
      "Environment": "{{ environment_name }}"
    rules:
      - proto: "tcp"
        from_port: "2049"
        to_port: "2049"
        group_id: "{{ sg_ec2.group_id }}"
        rule_desc: "allow {{ sg_ec2.group_id }} access to port 2049"
  register: sg_efs

现在我们已经创建了我们的主要组,让我们添加一个debug任务来将安全组 id 打印到屏幕上:

# - name: print all the ids we have registered
#   debug:
#     msg: "ELB = {{ sg_elb.group_id }}, EC2 = {{ sg_ec2.group_id }}, RDS = {{ sg_rds.group_id }} and EFS = {{ sg_efs.group_id }}"

现在我们有了我们的全部角色,我们可以开始行动了。记得将- roles/securitygroups添加到site.yml文件中:

$ ansible-playbook site.yml

我再次指出了securitygroups角色之外的debug模块的任何输出:

[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [Create and configure an Amazon VPC] *******************************************************

TASK [Gathering Facts] **************************************************************************
ok: [localhost]

TASK [roles/vpc : ensure that the VPC is present] ***********************************************
ok: [localhost]

TASK [roles/subnets : ensure that the subnets are present] **************************************
ok: [localhost] => (item={u'subnet': u'10.0.10.0/24', u'use': u'ec2', u'az': u'a'})
ok: [localhost] => (item={u'subnet': u'10.0.11.0/24', u'use': u'ec2', u'az': u'b'})
ok: [localhost] => (item={u'subnet': u'10.0.12.0/24', u'use': u'ec2', u'az': u'c'})
ok: [localhost] => (item={u'subnet': u'10.0.20.0/24', u'use': u'elb', u'az': u'a'})
ok: [localhost] => (item={u'subnet': u'10.0.21.0/24', u'use': u'elb', u'az': u'b'})
ok: [localhost] => (item={u'subnet': u'10.0.22.0/24', u'use': u'elb', u'az': u'c'})
ok: [localhost] => (item={u'subnet': u'10.0.30.0/24', u'use': u'rds', u'az': u'a'})
ok: [localhost] => (item={u'subnet': u'10.0.31.0/24', u'use': u'rds', u'az': u'b'})
ok: [localhost] => (item={u'subnet': u'10.0.40.0/24', u'use': u'efs', u'az': u'b'})
ok: [localhost] => (item={u'subnet': u'10.0.41.0/24', u'use': u'efs', u'az': u'c'})

TASK [roles/subnets : gather information about the ec2 subnets] *********************************
ok: [localhost]

TASK [roles/subnets : gather information about the elb subnets] *********************************
ok: [localhost]

TASK [roles/subnets : gather information about the rds subnets] *********************************
ok: [localhost]

TASK [roles/subnets : gather information about the efs subnets] *********************************
ok: [localhost]

TASK [roles/subnets : register just the IDs for each of the subnets] ****************************
ok: [localhost]

TASK [roles/gateway : ensure that there is an internet gateway] *********************************
ok: [localhost]

TASK [roles/gateway : check that we can route through internet gateway] *************************
ok: [localhost]

TASK [roles/securitygroups : provision elb security group] **************************************
changed: [localhost]

TASK [roles/securitygroups : find out your current public IP address using https://ipify.org/] **
ok: [localhost]

TASK [roles/securitygroups : set your public ip as a fact] **************************************
ok: [localhost]

TASK [roles/securitygroups : print your public ip address] **************************************
ok: [localhost] => {
 "msg": "Your public IP address is 109.153.155.197/32"
}

TASK [roles/securitygroups : provision ec2 security group] **************************************
changed: [localhost]

TASK [roles/securitygroups : provision rds security group] **************************************
changed: [localhost]

TASK [roles/securitygroups : provision efs security group] **************************************
changed: [localhost]

TASK [roles/securitygroups : print all the ids we have registered] ******************************
ok: [localhost] => {
 "msg": "ELB = sg-97778eea, EC2 = sg-fa778e87, RDS = sg-8e7089f3 and EFS = sg-7b718806"
}

PLAY RECAP **************************************************************************************
localhost : ok=18 changed=4 unreachable=0 failed=0

您可以在 AWS 控制台中查看 Ansible 创建的组。在下面的截图中,可以看到my-vpc-ec2 安全组:

现在我们已经配置了基本的 VPC,我们可以开始在其中启动服务,从应用负载平衡器开始。

ELB 的角色

我们将在本章中看到的最后一个角色是启动应用负载平衡器。它会创建一个目标组,然后将其连接到应用负载平衡器。我们将用这个角色创建的负载平衡器是基本的;我们将在后面的章节中详细讨论。

像其他角色一样,我们首先需要引导文件:

$ ansible-galaxy init roles/elb

现在打开roles/elb/tasks/main.yml并使用elb_target_group模块创建目标组:

- name: provision the target group
  elb_target_group:
    name: "{{ environment_name }}-target-group"
    region: "{{ ec2_region }}"
    protocol: "http"
    port: "80"
    deregistration_delay_timeout: "15"
    vpc_id: "{{ vpc_info.vpc.id }}"
    state: "present"
    modify_targets: "false"

正如你所看到的,我们正在 VPC 创建目标群体,并将其称为my-vpc-target-group。现在我们有了目标群体,我们可以使用elb_application_lb模块启动应用弹性平衡器:

- name: provision an application elastic load balancer
  elb_application_lb:
    region: "{{ ec2_region }}"
    name: "{{ environment_name }}-elb"
    security_groups: "{{ sg_elb.group_id }}"
    subnets: "{{ subnet_elb_ids }}"
    listeners:
      - Protocol: "HTTP" 
        Port: "80"
        DefaultActions:
          - Type: "forward" 
            TargetGroupName: "{{ environment_name }}-target-group"
    state: present
  register: loadbalancer

这里,我们在 VPC 提供了一个名为my-vpc-elb的应用负载平衡器;我们正在传递我们使用subnet_elb_ids创建的 ELB 子网的标识。我们还使用sg_elb.group_id将 ELB 安全组添加到负载平衡器中,并在端口80上配置一个侦听器,该侦听器将流量转发到my-vpc-target-group

任务的最后一部分打印了我们捕获的关于 ELB 的信息:

# - name: print the information on the load balancer we have registered
#   debug:
#     msg: "{{ loadbalancer }}"

这就完成了我们最后的角色;更新site.yml文件,使其如下所示:

- name: Create and configure an Amazon VPC
  hosts: localhost
  connection: local
  gather_facts: True

  vars_files:
    - group_vars/common.yml

  roles:
    - roles/vpc
    - roles/subnets
    - roles/gateway
    - roles/securitygroups
    - roles/elb

现在,我们可以通过运行以下命令来运行我们的行动手册:

$ ansible-playbook site.yml

您可能会猜测剧本运行的输出如下所示:

[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [Create and configure an Amazon VPC] *******************************************************

TASK [Gathering Facts] **************************************************************************
ok: [localhost]

TASK [roles/vpc : ensure that the VPC is present] ***********************************************
ok: [localhost]

TASK [roles/subnets : ensure that the subnets are present] **************************************
ok: [localhost] => (item={u'subnet': u'10.0.10.0/24', u'use': u'ec2', u'az': u'a'})
ok: [localhost] => (item={u'subnet': u'10.0.11.0/24', u'use': u'ec2', u'az': u'b'})
ok: [localhost] => (item={u'subnet': u'10.0.12.0/24', u'use': u'ec2', u'az': u'c'})
ok: [localhost] => (item={u'subnet': u'10.0.20.0/24', u'use': u'elb', u'az': u'a'})
ok: [localhost] => (item={u'subnet': u'10.0.21.0/24', u'use': u'elb', u'az': u'b'})
ok: [localhost] => (item={u'subnet': u'10.0.22.0/24', u'use': u'elb', u'az': u'c'})
ok: [localhost] => (item={u'subnet': u'10.0.30.0/24', u'use': u'rds', u'az': u'a'})
ok: [localhost] => (item={u'subnet': u'10.0.31.0/24', u'use': u'rds', u'az': u'b'})
ok: [localhost] => (item={u'subnet': u'10.0.40.0/24', u'use': u'efs', u'az': u'b'})
ok: [localhost] => (item={u'subnet': u'10.0.41.0/24', u'use': u'efs', u'az': u'c'})

TASK [roles/subnets : gather information about the ec2 subnets] *********************************
ok: [localhost]

TASK [roles/subnets : gather information about the elb subnets] *********************************
ok: [localhost]

TASK [roles/subnets : gather information about the rds subnets] *********************************
ok: [localhost]

TASK [roles/subnets : gather information about the efs subnets] *********************************
ok: [localhost]

TASK [roles/subnets : register just the IDs for each of the subnets] ****************************
ok: [localhost]

TASK [roles/gateway : ensure that there is an internet gateway] *********************************
ok: [localhost]

TASK [roles/gateway : check that we can route through internet gateway] *************************
ok: [localhost]

TASK [roles/securitygroups : provision elb security group] **************************************
ok: [localhost]

TASK [roles/securitygroups : find out your current public IP address using https://ipify.org/] **
ok: [localhost]

TASK [roles/securitygroups : set your public ip as a fact] **************************************
ok: [localhost]

TASK [roles/securitygroups : provision ec2 security group] **************************************
ok: [localhost]

TASK [roles/securitygroups : provision rds security group] **************************************
ok: [localhost]

TASK [roles/securitygroups : provision efs security group] **************************************
ok: [localhost]

TASK [roles/elb : provision the target group] ***************************************************
changed: [localhost]

TASK [roles/elb : provision an application elastic load balancer] *******************************
changed: [localhost]

TASK [roles/elb : print the information on the load balancer we have registered] ****************
ok: [localhost] => {
 "msg": {
 "access_logs_s3_bucket": "",
 "access_logs_s3_enabled": "false",
 "access_logs_s3_prefix": "",
 "attempts": 1,
 "availability_zones": [
 {
 "subnet_id": "subnet-51f15137",
 "zone_name": "eu-west-1a"
 },
 {
 "subnet_id": "subnet-64eb083e",
 "zone_name": "eu-west-1c"
 },
 {
 "subnet_id": "subnet-6744f22f",
 "zone_name": "eu-west-1b"
 }
 ],
 "canonical_hosted_zone_id": "Z32O12XQLNTSW2",
 "changed": true,
 "created_time": "2018-04-22T16:12:31.780000+00:00",
 "deletion_protection_enabled": "false",
 "dns_name": "my-vpc-elb-374523105.eu-west-1.elb.amazonaws.com",
 "failed": false,
 "idle_timeout_timeout_seconds": "60",
 "ip_address_type": "ipv4",
 "listeners": [
 {
 "default_actions": [
 {
 "target_group_arn": "arn:aws:elasticloadbalancing:eu-west-1:687011238589:targetgroup/my-vpc-target-group/d5bab5efb2d314a8",
 "type": "forward"
 }
 ],
 "listener_arn": "arn:aws:elasticloadbalancing:eu-west-1:687011238589:listener/app/my-vpc-elb/98dd881c7a931ab3/3f4be2b480657bf9",
 "load_balancer_arn": "arn:aws:elasticloadbalancing:eu-west-1:687011238589:loadbalancer/app/my-vpc-elb/98dd881c7a931ab3",
 "port": 80,
 "protocol": "HTTP",
 "rules": [
 {
 "actions": [
 {
 "target_group_arn": "arn:aws:elasticloadbalancing:eu-west-1:687011238589:targetgroup/my-vpc-target-group/d5bab5efb2d314a8",
 "type": "forward"
 }
 ],
 "conditions": [],
 "is_default": true,
 "priority": "default",
 "rule_arn": "arn:aws:elasticloadbalancing:eu-west-1:687011238589:listener-rule/app/my-vpc-elb/98dd881c7a931ab3/3f4be2b480657bf9/c70feab5b31460c2"
 }
 ]
 }
 ],
 "load_balancer_arn": "arn:aws:elasticloadbalancing:eu-west-1:687011238589:loadbalancer/app/my-vpc-elb/98dd881c7a931ab3",
 "load_balancer_name": "my-vpc-elb",
 "routing_http2_enabled": "true",
 "scheme": "internet-facing",
 "security_groups": [
 "sg-97778eea"
 ],
 "state": {
 "code": "provisioning"
 },
 "tags": {},
 "type": "application",
 "vpc_id": "vpc-ccef75aa"
 }
}

PLAY RECAP ******************************************************************************************************************************
localhost : ok=19 changed=2 unreachable=0 failed=0

现在,您应该能够在 AWS 控制台的 EC2 部分看到 ELB:

While VPC's do not incur any cost, ELBs do; please ensure that you remove any unused resources as soon as you have completed your test.

VPC 剧本的这一章到此结束;我们将在下一章中使用这些元素,在这一章中,我们将使用 VPC 作为我们安装的基础,将我们的 WordPress 安装部署到 AWS 中。

摘要

在这一章中,我们已经采取了下一步,使用 Ansible 在公共云中启动资源。我们创建了一个 VPC,设置了应用所需的子网,提供了一个互联网网关,并设置了实例通过它路由输出流量,从而为自动化相当复杂的环境奠定了基础。

我们还配置了四个安全组,其中三个包含动态内容,以在最终将 ELB 配置到我们的 VPC 之前保护我们的服务。

在下一章中,我们将在本章奠定的基础上,推出一套更复杂的服务。

问题

  1. AWS 模块用来读取您的访问标识和密码的两个环境变量是什么?
  2. 对或错:每次你运行剧本,你会得到一个新的 VPC。
  3. 陈述并解释为什么我们不去登记创建子网的结果。
  4. 在安全组中定义规则时,使用cidr_ipgroup_id有什么区别?
  5. 对或错:使用定义了group_id的规则时,安全组的添加顺序无关紧要。
  6. 在现有的 VPC 旁边创建第二个 VPC,给它一个不同的名称,并让它使用 10.1.0.0/24。

进一步阅读

您可以在以下链接中找到我们在本章中使用的 AWS 技术的更多详细信息: