1658959380
今天我们将通过构建一个项目来了解 Terraform。
Terraform 不仅仅是提高运营团队生产力的工具。您有机会通过实施 Terraform 将您的开发人员转变为操作员。
这有助于提高整个工程团队的效率并改善开发人员和操作员之间的沟通。
在本文中,我将向您展示如何使用带有自定义烘焙映像的 Terraform 在 AWS 云上完全自动化部署 Jenkins 服务。
什么是 Terraform?
HashiCorp 的 Terraform 是一种基础架构即代码解决方案。它允许您在可以重用和共享的可读配置文件中指定云和本地资源。它是一个强大的 DevOps 配置工具。
为什么要使用 Terraform?
Terraform 有许多用例,包括:
Terraform 的工作原理
让我们来看看 Terraform 在高层次上是如何工作的。
Terraform 是用 Go 编程语言开发的。Go 代码被编译成terraform,一个单一的二进制文件。您可以使用此二进制文件从您的笔记本电脑、构建服务器或几乎任何其他计算机上部署基础架构,并且您无需运行任何额外的基础架构来执行此操作。
这是因为 Terraform 二进制文件代表您向一个或多个提供商发出 API 调用,其中包括 Azure、AWS、Google Cloud、DigitalOcean 等。这使得 Terraform 可以利用这些提供商已经为他们的 API 服务器准备好的基础设施,以及他们需要的身份验证过程。
但是 Terraform 不知道要发出什么 API 请求——那么它是怎么知道的呢?Terraform 配置是声明性语言的文本文件,用于指定要生成的基础设施,就是答案。“基础设施即代码”中的“代码”就是这些设置。
您可以完全控制您的基础架构,包括服务器、数据库、负载平衡器、网络拓扑等。Terraform 二进制文件会代表您解析您的代码,并尽快将其转换为一系列 API 调用。
什么是过程语言与声明性语言?
过程语言允许您指定整个过程并列出完成它所需的步骤。您只需给出说明并指定如何执行该过程。Chef 和 Ansible 鼓励这种方法。
另一方面,声明性语言允许您简单地设置命令或命令并将其留给系统来执行。你不需要进入这个过程;你只需要结果。例如 Terraform、cloudFormation 和 Puppeteer。
理论够了...
现在是将 Terraform 的高可用性、安全性、性能和可靠性付诸实践的时候了。
在这里,我们谈论的是 Amazon Web Services 上基于 Terraform 的 Jenkins 服务器。我们正在从头开始建立网络,所以让我们开始吧。
您需要设置和安装一些东西才能跟随本教程:
我们将使用模块化开发策略将我们的 Jenkins 集群部署分离为多个模板文件(而不是开发一个大型模板文件)。
每个文件负责执行目标基础设施组件或 AWS 资源。
为了创建和实施基础设施设置,Terraform 利用了一种类似于 JSON 的配置语言的语法,称为 HCL(HashiCorp 配置语言)。
文件/文件夹结构
为了遵循最佳实践,我们将把 Terraform 状态文件存储在我们的云存储中。这对于团队协作尤其重要。
Terraform 状态文件是包含项目中 Terraform 资源的文件。
在 backend-state 文件夹的 main.tf 文件中,添加以下代码:
variable "aws_region" {
default = "us-east-1"
}
variable "aws_secret_key" {}
variable "aws_access_key" {}
provider "aws" {
region = var.aws_region
access_key = var.aws_access_key
secret_key = var.aws_secret_key
}
resource "aws_s3_bucket" "terraform_state" {
bucket = "terraform-state-caesar-tutorial-jenkins"
lifecycle {
prevent_destroy = true
}
versioning {
enabled = true
}
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
}
后端状态/main.tf
让我们确保我们知道上面代码中发生了什么。
我们使用变量来存储数据,在 Terraform 中,您使用 variable 关键字后跟名称来声明一个变量。变量块可以采用一些属性,例如默认值、描述、类型等,也可以没有。你会经常看到这个。
现在我们将变量声明 为variable "variable_name"{}
并在任何资源/数据块中使用它们作为var.variable_name
. 稍后您将看到我们将如何在 secrets.tfvars 文件中为这些变量赋值。
要使用 Terraform,您需要告诉它将与之通信的提供者并传递其所需的属性以进行身份验证。在这里,我们有 AWS 区域、访问权限和密钥(您应该根据先决条件将这些下载到您的系统上)。
在 terraform 中,我们需要的每个资源都定义在资源块中。资源是创建我们的云服务的基础设施。它遵循语法resource "terraform-resource-name" "custom-name" {}
。
Terraform 在 terraform 文档中为特定提供者提供了大量资源(如果您有任何问题,请始终参考文档)。
接下来,我们正在创建 aws_s3_bucket。这将存储我们的远程状态。它具有以下属性:
我们的状态后端已准备就绪。但是在我们使用 Terraform 对其进行初始化、计划和应用之前,让我们将变量分配给它的值。
在 secrets.tfvars 中,从您的 AWS 账户添加以下信息:
aws_region = "us-east-1
aws_secret_key = "enter-your-secret"
aws_access_key = "enter-your-access
后端状态/secrets.tfvars
在同一后端状态文件夹中的终端中,运行terraform init
.
终端上的地形状态
然后terraform apply -var-file=secrets.tfvars
:
终端上的地形状态
在您的AWS 控制台中,您将看到以下内容:
aws s3 存储桶上的 terraform 状态
现在我们的状态已经准备好了,让我们进入下一部分。
为了保护我们的 Jenkins 集群,我们将在虚拟私有云 (VPC) 和私有子网中部署该架构。您可以在 AWS 默认 VPC 中部署集群。
为了完全控制网络拓扑,我们将从头开始创建一个 VPC。
variable "cidr_block" {}
variable "aws_access_key" {}
variable "aws_secret_key" {}
variable "aws_region" {}
provider "aws" {
region = var.aws_region
access_key = var.aws_access_key
secret_key = var.aws_secret_key
}
terraform {
backend "s3" {
bucket = "terraform-state-caesar-tutorial-jenkins"
key = "tutorial-jenkins/development/network/terraform.tfstate"
region = "us-east-1"
encrypt = true
}
}
resource "aws_vpc" "main_vpc" {
cidr_block = var.cidr_block
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "jenkins-instance-main_vpc"
}
}
开发/main.tf
output "vpc_id" {
value = aws_vpc.main_vpc.id
}
output "vpc_cidr_block" {
value = aws_vpc.main_vpc.cidr_block
}
开发/输出.tf
cidr_block = "172.0.0.0/16"
aws_region = "us-east-1"
aws_secret_key = "enter-your-secret"
aws_access_key = "enter-your-access"
开发/secrets.tfvars
output "custom_output_name" { value = "resource-name"}
. 它接受一个值键,该键接受传递的资源。这里我们输出 vpc_id 和 cidr_block。现在,在终端中运行terraform init
并terraform apply
创建资源。您可以在terraform plan
之前运行以查看您实际创建的资源。这是命令:terraform apply -var-file=secrets.tfvars
和输出:
您应该在AWS 控制台中看到您的vpc_id 和 vpc_cidr_block:
aws 上的 vpc
特定目录中的一组典型配置文件组成了一个 Terraform 模块。Terraform 模块将用于单个操作的资源放在一起。这减少了创建相同基础架构组件所需的代码量。
使用以下语法,您可以将一个 Terraform 模块资源转移到另一个以供使用。
module "custom-module-name" {
source = "path-to-modules-resources"
}
terraform-modules 语法
并且要在另一个资源模块中使用模块资源输出,这是命令:module.custom-module-name.resource-output-value
。
创建一个 VPC 是不够的——我们还需要一个子网才能在这个隔离的网络上安装 Jenkins 实例。我们必须传递我们之前输出的 VPC ID,因为这个子网属于之前构建的 VPC。
为了弹性,我们将在不同的可用区中使用两个公共子网和两个私有子网。每个子网都有自己的 CIDR 块,它是我们从 VPC 资源中获得的 VPC CIDR 块的子集。
resource "aws_subnet" "public_subnets" {
vpc_id = var.vpc_id
cidr_block = cidrsubnet(var.vpc_cidr_block, 8, 2 + count.index)
availability_zone = element(var.availability_zones, count.index)
map_public_ip_on_launch = true
count = var.public_subnets_count
tags = {
Name = "jenkins-instance-public-subnet"
}
}
resource "aws_subnet" "private_subnets" {
vpc_id = var.vpc_id
cidr_block = cidrsubnet(var.vpc_cidr_block, 8, count.index)
availability_zone = element(var.availability_zones, count.index)
map_public_ip_on_launch = false
count = var.private_subnets_count
tags = {
Name = "jenkins-instance-private-subnet"
}
}
模块/子网.tf
好的,这段代码发生了什么?
现在让我们更新我们的模块变量:
variable "vpc_id" {}
variable "vpc_cidr_block" {}
variable "private_subnets_count" {}
variable "public_subnets_count" {}
variable "availability_zones" {}
开发/模块/变量.tf
像这样更新 secrets.tfvars:
private_subnets_count = 2
public_subnets_count = 2
秘密.tfvars
您必须建立私有和公共路由表来指定 VPC 子网中的流量路由方法。在我们对我们的资源执行terraform apply之前,让我们这样做。
我们将开发用于细粒度交通管理的私有和公共路由表。这将使部署在私有子网中的实例能够访问互联网,而不会暴露给公众。
首先,我们需要建立一个Internet 网关资源并将其链接到我们之前生成的 VPC。然后我们需要定义一个公共路由表和一个将所有流量(0.0.0.0/0)指向互联网网关的路由。最后,我们需要将其与 VPC 中的公共子网链接,以便通过创建路由表关联将来自这些子网的流量路由到 Internet 网关。
/*** Internet Gateway - Provides a connection between the VPC and the public internet, allowing traffic to flow in and out of the VPC and translating IP addresses to public* addresses.*/
resource "aws_internet_gateway" "igw" {
vpc_id = var.vpc_id
tags = {
Name = "igw_jenkins"
}
}
/*** A route from the public route table out to the internet through the internet* gateway.*/
resource "aws_route_table" "public_rt" {
vpc_id = var.vpc_id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
tags = {
Name = "public_rt_jenkins"
}
}
/*** Associate the public route table with the public subnets.*/
resource "aws_route_table_association" "public" {
count = var.public_subnets_count
subnet_id = element(var.public_subnets.*.id, count.index)
route_table_id = aws_route_table.public_rt.id
}
开发/模块/public_rt.tf
现在我们的公共路由表已经完成,让我们创建私有路由表。
为了让我们的 Jenkins 实例在部署在私有子网上时能够连接到互联网,我们将在公共子网中构建一个NAT 网关资源。
之后将弹性 IP地址添加到 NAT 网关,并添加一个带有路由 (0.0.0.0/0) 的私有路由表,该路由将所有流量定向到您建立的 NAT 网关的 ID。然后我们通过创建路由表关联将私有子网附加到私有路由表。
/*** An elastic IP address to be used by the NAT Gateway defined below. The NAT* gateway acts as a gateway between our private subnets and the public* internet, providing access out to the internet from within those subnets,* while denying access to them from the public internet. This IP address* acts as the IP address from which all the outbound traffic from the private* subnets will originate.*/
resource "aws_eip" "eip_for_the_nat_gateway" {
vpc = true
tags = {
Name = "jenkins-tutoral-eip_for_the_nat_gateway"
}
}
/*** A NAT Gateway that lives in our public subnet and provides an interface* between our private subnets and the public internet. It allows traffic to* exit our private subnets, but prevents traffic from entering them.*/
resource "aws_nat_gateway" "nat_gateway" {
allocation_id = aws_eip.eip_for_the_nat_gateway.id
subnet_id = element(var.public_subnets.*.id, 0)
tags = {
Name = "jenkins-tutorial-nat_gateway"
}
}
/*** A route from the private route table out to the internet through the NAT * Gateway.*/
resource "aws_route_table" "private_rt" {
vpc_id = var.vpc_id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat_gateway.id }
tags = {
Name = "private_rt_${var.vpc_name}"
Author = var.author
}
}
/*** Associate the private route table with the private subnet.*/
resource "aws_route_table_association" "private" {
count = var.private_subnets_count
subnet_id = element(aws_subnet.private_subnets.*.id, count.index)
route_table_id = aws_route_table.private_rt.id
}
开发/模块/private_rt.tf
现在让我们运行terraform apply
。但是我们需要更新我们的 main.tf文件(因为这是我们的入口 terraform 文件)以了解我们的子网和模块变量以及secrets.tfvars(用于我们的变量)。
variable "vpc_id" {}
variable "vpc_cidr_block" {}
variable "private_subnets_count" {}
variable "public_subnets_count" {}
variable "availability_zones" {}
variable "public_subnets" {}
开发/模块/变量.ftvars
variable "private_subnets_count" {}
variable "public_subnets_count" {}
variable "availability_zones" {}
module "subnet_module" {
source = "./modules"
vpc_id = aws_vpc.main_vpc.id
vpc_cidr_block = aws_vpc.main_vpc.cidr_block
availability_zones = var.availability_zones
public_subnets_count = var.public_subnets_count
private_subnets_count = var.private_subnets_count
}
开发/main.tf
availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c", "us-east-1d", "us-east-1e"]
开发/secrets.tf
我们的子网和相应的证券已准备就绪。现在我们可以使用 Terraform 对其进行初始化、计划和应用。
我们将运行terraform apply来创建资源。您可以先运行 terraform plan 以查看您实际创建的资源。
在终端运行。 terraform apply -var-file=secrets.tfvars
请记住,此处添加的资源数量可能与您的有所不同。
这是 AWS 控制台(子网、弹性地址、路由表):
子网弹性 ip
路由表
我们在私有子网中部署了 Jenkins 集群。由于集群缺少公共 IP,实例将无法通过 Internet 公开可用。因此,为了解决这个问题,我们将设置一个堡垒主机,以便我们可以安全地访问 Jenkins 实例。
在 bastion.tf 文件中添加以下资源和安全组:
/*** A security group to allow SSH access into our bastion instance.*/
resource "aws_security_group" "bastion" {
name = "bastion-security-group"
vpc_id = var.vpc_id
ingress {
protocol = "tcp"
from_port = 22
to_port = 22
cidr_blocks = ["0.0.0.0/0"]
}
egress {
protocol = -1
from_port = 0
to_port = 0
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "aws_security_group.bastion_jenkins"
}
}
/*** The public key for the key pair we'll use to ssh into our bastion instance.*/
resource "aws_key_pair" "bastion" {
key_name = "bastion-key-jenkins"
public_key = var.public_key
}
/*** This parameter contains the AMI ID for the most recent Amazon Linux 2 ami,* managed by AWS.*/
data "aws_ssm_parameter" "linux2_ami" {
name = "/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-ebs"
}
/*** Launch a bastion instance we can use to gain access to the private subnets of* this availabilty zone.*/
resource "aws_instance" "bastion" {
ami = data.aws_ssm_parameter.linux2_ami.value
key_name = aws_key_pair.bastion.key_name
instance_type = "t2.large"
associate_public_ip_address = true
subnet_id = element(aws_subnet.public_subnets, 0).id
vpc_security_group_ids = [aws_security_group.bastion.id]
tags = {
Name = "jenkins-bastion"
}
}
output "bastion" { value = aws_instance.bastion.public_ip }
堡垒.tf
让我们看看这里的代码发生了什么:
现在,让我们使用作为变量传递的新public_key更新模块和 main.tf中的变量:
variable "public_key"{}
开发/模块/变量/tfvars
varable "public_key" {}
module "subnet_module" {
source = "./modules"
...
publc_key = var.public_key
}
开发/main.tf
public_key = "enter-your-public-key"
开发/secrets.tf
我们将运行terraform apply来创建资源。您可以先运行 terraform plan 以查看您实际创建的资源。
在终端上,让我们运行terraform apply -var-file=secrets.tfvars
:
终端资源
这是 AWS 控制台中的输出:
aws 控制台实例
到目前为止,我们已经成功地设置了我们的 VPC 和网络拓扑。最后,我们将创建我们的 Jenkins EC2 实例,该实例将使用 Packer 烘焙的 Jenkins 主 AMI。
您可以在 freecodecamp.org 上查看我之前的文章:通过在 AWS 中构建自定义机器映像来学习基础设施即代码。 无论如何,如果您有自定义图像,您可以使用任何自定义图像。
/*** This parameter contains our baked AMI ID fetch from the Amazon Console*/ data "aws_ami" "jenkins-master" {
most_recent = true owners = ["self"]
}
resource "aws_security_group" "jenkins_master_sg" {
name = "jenkins_master_sg"
description = "Allow traffic on port 8080 and enable SSH"
vpc_id = var.vpc_id
ingress {
from_port = "22"
to_port = "22"
protocol = "tcp"
security_groups = [aws_security_group.bastion.id]
}
ingress {
from_port = "8080"
to_port = "8080"
protocol = "tcp"
security_groups = [aws_security_group.lb.id]
}
ingress {
from_port = "8080"
to_port = "8080"
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = "0"
to_port = "0"
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "jenkins_master_sg"
}
}
开发/模块/jenkins_master.tf
将安全组附加到实例将启用端口 8080(Jenkins Web 仪表板)和仅来自堡垒服务器和 VPC CIDR 块的 SSH 上的入站流量。
resource "aws_key_pair" "jenkins" {
key_name = "key-jenkins"
public_key = var.public_key
}
resource "aws_instance" "jenkins_master" {
ami = data.aws_ami.jenkins-master.id
instance_type = "t2.large"
key_name = aws_key_pair.jenkins.key_name
vpc_security_group_ids = [aws_security_group.jenkins_master_sg.id]
subnet_id = element(aws_subnet.private_subnets, 0).id
root_block_device {
volume_type = "gp3"
volume_size = 30
delete_on_termination = false
}
tags = {
Name = "jenkins_master"
}
}
开发/模块/jenkins_master.tf
接下来,我们创建一个变量并定义我们用于部署 EC2 实例的实例类型。我们不会在 master 上分配 executor 或 worker,因此 t2.large(8 GB 内存和 2vCPU)对于简单来说应该足够了。
因此,构建作业不会导致 Jenkins master 过度拥挤。但是 Jenkins 的内存要求会因项目的构建要求和这些构建中使用的工具而异。它需要两到三个线程,或至少 2 MB 内存,才能连接到每个构建节点。
请注意:考虑安装 Jenkins 工人以防止主人过度工作。因此,通用实例可以托管 Jenkins 主服务器并在计算和内存资源之间提供平衡。为了保持文章的简单性,我们不会这样做。
要访问 Jenkins 仪表板,我们将在 EC2 实例前创建一个公共负载均衡器。
此 Elastic Load Balancer 将接受端口 80 上的 HTTP 流量并将其转发到端口 8080 上的 EC2 实例。此外,它会自动检查端口 8080 上已注册 EC2 实例的运行状况。如果 Elastic Load Balancing (ELB) 发现实例运行状况不佳,它停止向 Jenkins 实例发送流量。
/*** A security group to allow SSH access into our load balancer*/ resource "aws_security_group" "lb" {
name = "ecs-alb-security-group"
vpc_id = var.vpc_id
ingress {
protocol = "tcp"
from_port = 80
to_port = 80
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "jenkins-lb-sg"
}
}
/***Load Balancer to be attached to the ECS cluster to distribute the load among instances*/
resource "aws_elb" "jenkins_elb" {
subnets = [for subnet in aws_subnet.public_subnets : subnet.id]
cross_zone_load_balancing = true
security_groups = [aws_security_group.lb.id]
instances = [aws_instance.jenkins_master.id]
listener {
instance_port = 8080
instance_protocol = "http"
lb_port = 80
lb_protocol = "http"
}
health_check {
healthy_threshold = 2
unhealthy_threshold = 2
timeout = 3
target = "TCP:8080"
interval = 5
}
tags = {
Name = "jenkins_elb"
}
}
output "load-balancer-ip" {
value = aws_elb.jenkins_elb.dns_name
}
开发/模块/负载均衡器.tf
之前,我们进行 terraform 应用,让我们更新 development/output.tf 文件夹以输出负载均衡器 DNS:
output "load-balancer-ip" {
value = module.subnet_module.load-balancer-ip
}
开发/输出.tf
在终端上,运行以下命令:terraform apply -var-file="secrets.tfvars"
. 这会给你这个:
负载均衡器输出
使用 Terraform 应用更改后,Jenkins 主负载均衡器 URL 应显示在终端会话中。
将您喜欢的浏览器指向该 URL,您应该可以访问 Jenkins Web 仪表板。
詹金斯实例
然后只需按照屏幕说明解锁。
解锁詹金斯
为了避免运行 AWS 服务的不必要成本,您需要运行以下命令来销毁所有已创建和正在运行的资源terraform destroy -var-file="secrets.tfvars"
: 应该会给出以下输出:
破坏资源
多么有趣,对吧?只需几行代码,我们就可以破坏和启动我们的资源。
在本教程中,您学习了如何在较高级别使用 Terraform。您还通过在 AWS 云平台上配置 Jenkins 服务器了解了它的一个应用程序。
您还了解了 Terraform 后端状态和模块的最佳实践。
快乐学习!
来源:https ://www.freecodecamp.org/news/learn-terraform-by-deploying-jenkins-server-on-aws/
1658959380
今天我们将通过构建一个项目来了解 Terraform。
Terraform 不仅仅是提高运营团队生产力的工具。您有机会通过实施 Terraform 将您的开发人员转变为操作员。
这有助于提高整个工程团队的效率并改善开发人员和操作员之间的沟通。
在本文中,我将向您展示如何使用带有自定义烘焙映像的 Terraform 在 AWS 云上完全自动化部署 Jenkins 服务。
什么是 Terraform?
HashiCorp 的 Terraform 是一种基础架构即代码解决方案。它允许您在可以重用和共享的可读配置文件中指定云和本地资源。它是一个强大的 DevOps 配置工具。
为什么要使用 Terraform?
Terraform 有许多用例,包括:
Terraform 的工作原理
让我们来看看 Terraform 在高层次上是如何工作的。
Terraform 是用 Go 编程语言开发的。Go 代码被编译成terraform,一个单一的二进制文件。您可以使用此二进制文件从您的笔记本电脑、构建服务器或几乎任何其他计算机上部署基础架构,并且您无需运行任何额外的基础架构来执行此操作。
这是因为 Terraform 二进制文件代表您向一个或多个提供商发出 API 调用,其中包括 Azure、AWS、Google Cloud、DigitalOcean 等。这使得 Terraform 可以利用这些提供商已经为他们的 API 服务器准备好的基础设施,以及他们需要的身份验证过程。
但是 Terraform 不知道要发出什么 API 请求——那么它是怎么知道的呢?Terraform 配置是声明性语言的文本文件,用于指定要生成的基础设施,就是答案。“基础设施即代码”中的“代码”就是这些设置。
您可以完全控制您的基础架构,包括服务器、数据库、负载平衡器、网络拓扑等。Terraform 二进制文件会代表您解析您的代码,并尽快将其转换为一系列 API 调用。
什么是过程语言与声明性语言?
过程语言允许您指定整个过程并列出完成它所需的步骤。您只需给出说明并指定如何执行该过程。Chef 和 Ansible 鼓励这种方法。
另一方面,声明性语言允许您简单地设置命令或命令并将其留给系统来执行。你不需要进入这个过程;你只需要结果。例如 Terraform、cloudFormation 和 Puppeteer。
理论够了...
现在是将 Terraform 的高可用性、安全性、性能和可靠性付诸实践的时候了。
在这里,我们谈论的是 Amazon Web Services 上基于 Terraform 的 Jenkins 服务器。我们正在从头开始建立网络,所以让我们开始吧。
您需要设置和安装一些东西才能跟随本教程:
我们将使用模块化开发策略将我们的 Jenkins 集群部署分离为多个模板文件(而不是开发一个大型模板文件)。
每个文件负责执行目标基础设施组件或 AWS 资源。
为了创建和实施基础设施设置,Terraform 利用了一种类似于 JSON 的配置语言的语法,称为 HCL(HashiCorp 配置语言)。
文件/文件夹结构
为了遵循最佳实践,我们将把 Terraform 状态文件存储在我们的云存储中。这对于团队协作尤其重要。
Terraform 状态文件是包含项目中 Terraform 资源的文件。
在 backend-state 文件夹的 main.tf 文件中,添加以下代码:
variable "aws_region" {
default = "us-east-1"
}
variable "aws_secret_key" {}
variable "aws_access_key" {}
provider "aws" {
region = var.aws_region
access_key = var.aws_access_key
secret_key = var.aws_secret_key
}
resource "aws_s3_bucket" "terraform_state" {
bucket = "terraform-state-caesar-tutorial-jenkins"
lifecycle {
prevent_destroy = true
}
versioning {
enabled = true
}
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
}
后端状态/main.tf
让我们确保我们知道上面代码中发生了什么。
我们使用变量来存储数据,在 Terraform 中,您使用 variable 关键字后跟名称来声明一个变量。变量块可以采用一些属性,例如默认值、描述、类型等,也可以没有。你会经常看到这个。
现在我们将变量声明 为variable "variable_name"{}
并在任何资源/数据块中使用它们作为var.variable_name
. 稍后您将看到我们将如何在 secrets.tfvars 文件中为这些变量赋值。
要使用 Terraform,您需要告诉它将与之通信的提供者并传递其所需的属性以进行身份验证。在这里,我们有 AWS 区域、访问权限和密钥(您应该根据先决条件将这些下载到您的系统上)。
在 terraform 中,我们需要的每个资源都定义在资源块中。资源是创建我们的云服务的基础设施。它遵循语法resource "terraform-resource-name" "custom-name" {}
。
Terraform 在 terraform 文档中为特定提供者提供了大量资源(如果您有任何问题,请始终参考文档)。
接下来,我们正在创建 aws_s3_bucket。这将存储我们的远程状态。它具有以下属性:
我们的状态后端已准备就绪。但是在我们使用 Terraform 对其进行初始化、计划和应用之前,让我们将变量分配给它的值。
在 secrets.tfvars 中,从您的 AWS 账户添加以下信息:
aws_region = "us-east-1
aws_secret_key = "enter-your-secret"
aws_access_key = "enter-your-access
后端状态/secrets.tfvars
在同一后端状态文件夹中的终端中,运行terraform init
.
终端上的地形状态
然后terraform apply -var-file=secrets.tfvars
:
终端上的地形状态
在您的AWS 控制台中,您将看到以下内容:
aws s3 存储桶上的 terraform 状态
现在我们的状态已经准备好了,让我们进入下一部分。
为了保护我们的 Jenkins 集群,我们将在虚拟私有云 (VPC) 和私有子网中部署该架构。您可以在 AWS 默认 VPC 中部署集群。
为了完全控制网络拓扑,我们将从头开始创建一个 VPC。
variable "cidr_block" {}
variable "aws_access_key" {}
variable "aws_secret_key" {}
variable "aws_region" {}
provider "aws" {
region = var.aws_region
access_key = var.aws_access_key
secret_key = var.aws_secret_key
}
terraform {
backend "s3" {
bucket = "terraform-state-caesar-tutorial-jenkins"
key = "tutorial-jenkins/development/network/terraform.tfstate"
region = "us-east-1"
encrypt = true
}
}
resource "aws_vpc" "main_vpc" {
cidr_block = var.cidr_block
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "jenkins-instance-main_vpc"
}
}
开发/main.tf
output "vpc_id" {
value = aws_vpc.main_vpc.id
}
output "vpc_cidr_block" {
value = aws_vpc.main_vpc.cidr_block
}
开发/输出.tf
cidr_block = "172.0.0.0/16"
aws_region = "us-east-1"
aws_secret_key = "enter-your-secret"
aws_access_key = "enter-your-access"
开发/secrets.tfvars
output "custom_output_name" { value = "resource-name"}
. 它接受一个值键,该键接受传递的资源。这里我们输出 vpc_id 和 cidr_block。现在,在终端中运行terraform init
并terraform apply
创建资源。您可以在terraform plan
之前运行以查看您实际创建的资源。这是命令:terraform apply -var-file=secrets.tfvars
和输出:
您应该在AWS 控制台中看到您的vpc_id 和 vpc_cidr_block:
aws 上的 vpc
特定目录中的一组典型配置文件组成了一个 Terraform 模块。Terraform 模块将用于单个操作的资源放在一起。这减少了创建相同基础架构组件所需的代码量。
使用以下语法,您可以将一个 Terraform 模块资源转移到另一个以供使用。
module "custom-module-name" {
source = "path-to-modules-resources"
}
terraform-modules 语法
并且要在另一个资源模块中使用模块资源输出,这是命令:module.custom-module-name.resource-output-value
。
创建一个 VPC 是不够的——我们还需要一个子网才能在这个隔离的网络上安装 Jenkins 实例。我们必须传递我们之前输出的 VPC ID,因为这个子网属于之前构建的 VPC。
为了弹性,我们将在不同的可用区中使用两个公共子网和两个私有子网。每个子网都有自己的 CIDR 块,它是我们从 VPC 资源中获得的 VPC CIDR 块的子集。
resource "aws_subnet" "public_subnets" {
vpc_id = var.vpc_id
cidr_block = cidrsubnet(var.vpc_cidr_block, 8, 2 + count.index)
availability_zone = element(var.availability_zones, count.index)
map_public_ip_on_launch = true
count = var.public_subnets_count
tags = {
Name = "jenkins-instance-public-subnet"
}
}
resource "aws_subnet" "private_subnets" {
vpc_id = var.vpc_id
cidr_block = cidrsubnet(var.vpc_cidr_block, 8, count.index)
availability_zone = element(var.availability_zones, count.index)
map_public_ip_on_launch = false
count = var.private_subnets_count
tags = {
Name = "jenkins-instance-private-subnet"
}
}
模块/子网.tf
好的,这段代码发生了什么?
现在让我们更新我们的模块变量:
variable "vpc_id" {}
variable "vpc_cidr_block" {}
variable "private_subnets_count" {}
variable "public_subnets_count" {}
variable "availability_zones" {}
开发/模块/变量.tf
像这样更新 secrets.tfvars:
private_subnets_count = 2
public_subnets_count = 2
秘密.tfvars
您必须建立私有和公共路由表来指定 VPC 子网中的流量路由方法。在我们对我们的资源执行terraform apply之前,让我们这样做。
我们将开发用于细粒度交通管理的私有和公共路由表。这将使部署在私有子网中的实例能够访问互联网,而不会暴露给公众。
首先,我们需要建立一个Internet 网关资源并将其链接到我们之前生成的 VPC。然后我们需要定义一个公共路由表和一个将所有流量(0.0.0.0/0)指向互联网网关的路由。最后,我们需要将其与 VPC 中的公共子网链接,以便通过创建路由表关联将来自这些子网的流量路由到 Internet 网关。
/*** Internet Gateway - Provides a connection between the VPC and the public internet, allowing traffic to flow in and out of the VPC and translating IP addresses to public* addresses.*/
resource "aws_internet_gateway" "igw" {
vpc_id = var.vpc_id
tags = {
Name = "igw_jenkins"
}
}
/*** A route from the public route table out to the internet through the internet* gateway.*/
resource "aws_route_table" "public_rt" {
vpc_id = var.vpc_id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
tags = {
Name = "public_rt_jenkins"
}
}
/*** Associate the public route table with the public subnets.*/
resource "aws_route_table_association" "public" {
count = var.public_subnets_count
subnet_id = element(var.public_subnets.*.id, count.index)
route_table_id = aws_route_table.public_rt.id
}
开发/模块/public_rt.tf
现在我们的公共路由表已经完成,让我们创建私有路由表。
为了让我们的 Jenkins 实例在部署在私有子网上时能够连接到互联网,我们将在公共子网中构建一个NAT 网关资源。
之后将弹性 IP地址添加到 NAT 网关,并添加一个带有路由 (0.0.0.0/0) 的私有路由表,该路由将所有流量定向到您建立的 NAT 网关的 ID。然后我们通过创建路由表关联将私有子网附加到私有路由表。
/*** An elastic IP address to be used by the NAT Gateway defined below. The NAT* gateway acts as a gateway between our private subnets and the public* internet, providing access out to the internet from within those subnets,* while denying access to them from the public internet. This IP address* acts as the IP address from which all the outbound traffic from the private* subnets will originate.*/
resource "aws_eip" "eip_for_the_nat_gateway" {
vpc = true
tags = {
Name = "jenkins-tutoral-eip_for_the_nat_gateway"
}
}
/*** A NAT Gateway that lives in our public subnet and provides an interface* between our private subnets and the public internet. It allows traffic to* exit our private subnets, but prevents traffic from entering them.*/
resource "aws_nat_gateway" "nat_gateway" {
allocation_id = aws_eip.eip_for_the_nat_gateway.id
subnet_id = element(var.public_subnets.*.id, 0)
tags = {
Name = "jenkins-tutorial-nat_gateway"
}
}
/*** A route from the private route table out to the internet through the NAT * Gateway.*/
resource "aws_route_table" "private_rt" {
vpc_id = var.vpc_id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat_gateway.id }
tags = {
Name = "private_rt_${var.vpc_name}"
Author = var.author
}
}
/*** Associate the private route table with the private subnet.*/
resource "aws_route_table_association" "private" {
count = var.private_subnets_count
subnet_id = element(aws_subnet.private_subnets.*.id, count.index)
route_table_id = aws_route_table.private_rt.id
}
开发/模块/private_rt.tf
现在让我们运行terraform apply
。但是我们需要更新我们的 main.tf文件(因为这是我们的入口 terraform 文件)以了解我们的子网和模块变量以及secrets.tfvars(用于我们的变量)。
variable "vpc_id" {}
variable "vpc_cidr_block" {}
variable "private_subnets_count" {}
variable "public_subnets_count" {}
variable "availability_zones" {}
variable "public_subnets" {}
开发/模块/变量.ftvars
variable "private_subnets_count" {}
variable "public_subnets_count" {}
variable "availability_zones" {}
module "subnet_module" {
source = "./modules"
vpc_id = aws_vpc.main_vpc.id
vpc_cidr_block = aws_vpc.main_vpc.cidr_block
availability_zones = var.availability_zones
public_subnets_count = var.public_subnets_count
private_subnets_count = var.private_subnets_count
}
开发/main.tf
availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c", "us-east-1d", "us-east-1e"]
开发/secrets.tf
我们的子网和相应的证券已准备就绪。现在我们可以使用 Terraform 对其进行初始化、计划和应用。
我们将运行terraform apply来创建资源。您可以先运行 terraform plan 以查看您实际创建的资源。
在终端运行。 terraform apply -var-file=secrets.tfvars
请记住,此处添加的资源数量可能与您的有所不同。
这是 AWS 控制台(子网、弹性地址、路由表):
子网弹性 ip
路由表
我们在私有子网中部署了 Jenkins 集群。由于集群缺少公共 IP,实例将无法通过 Internet 公开可用。因此,为了解决这个问题,我们将设置一个堡垒主机,以便我们可以安全地访问 Jenkins 实例。
在 bastion.tf 文件中添加以下资源和安全组:
/*** A security group to allow SSH access into our bastion instance.*/
resource "aws_security_group" "bastion" {
name = "bastion-security-group"
vpc_id = var.vpc_id
ingress {
protocol = "tcp"
from_port = 22
to_port = 22
cidr_blocks = ["0.0.0.0/0"]
}
egress {
protocol = -1
from_port = 0
to_port = 0
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "aws_security_group.bastion_jenkins"
}
}
/*** The public key for the key pair we'll use to ssh into our bastion instance.*/
resource "aws_key_pair" "bastion" {
key_name = "bastion-key-jenkins"
public_key = var.public_key
}
/*** This parameter contains the AMI ID for the most recent Amazon Linux 2 ami,* managed by AWS.*/
data "aws_ssm_parameter" "linux2_ami" {
name = "/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-ebs"
}
/*** Launch a bastion instance we can use to gain access to the private subnets of* this availabilty zone.*/
resource "aws_instance" "bastion" {
ami = data.aws_ssm_parameter.linux2_ami.value
key_name = aws_key_pair.bastion.key_name
instance_type = "t2.large"
associate_public_ip_address = true
subnet_id = element(aws_subnet.public_subnets, 0).id
vpc_security_group_ids = [aws_security_group.bastion.id]
tags = {
Name = "jenkins-bastion"
}
}
output "bastion" { value = aws_instance.bastion.public_ip }
堡垒.tf
让我们看看这里的代码发生了什么:
现在,让我们使用作为变量传递的新public_key更新模块和 main.tf中的变量:
variable "public_key"{}
开发/模块/变量/tfvars
varable "public_key" {}
module "subnet_module" {
source = "./modules"
...
publc_key = var.public_key
}
开发/main.tf
public_key = "enter-your-public-key"
开发/secrets.tf
我们将运行terraform apply来创建资源。您可以先运行 terraform plan 以查看您实际创建的资源。
在终端上,让我们运行terraform apply -var-file=secrets.tfvars
:
终端资源
这是 AWS 控制台中的输出:
aws 控制台实例
到目前为止,我们已经成功地设置了我们的 VPC 和网络拓扑。最后,我们将创建我们的 Jenkins EC2 实例,该实例将使用 Packer 烘焙的 Jenkins 主 AMI。
您可以在 freecodecamp.org 上查看我之前的文章:通过在 AWS 中构建自定义机器映像来学习基础设施即代码。 无论如何,如果您有自定义图像,您可以使用任何自定义图像。
/*** This parameter contains our baked AMI ID fetch from the Amazon Console*/ data "aws_ami" "jenkins-master" {
most_recent = true owners = ["self"]
}
resource "aws_security_group" "jenkins_master_sg" {
name = "jenkins_master_sg"
description = "Allow traffic on port 8080 and enable SSH"
vpc_id = var.vpc_id
ingress {
from_port = "22"
to_port = "22"
protocol = "tcp"
security_groups = [aws_security_group.bastion.id]
}
ingress {
from_port = "8080"
to_port = "8080"
protocol = "tcp"
security_groups = [aws_security_group.lb.id]
}
ingress {
from_port = "8080"
to_port = "8080"
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = "0"
to_port = "0"
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "jenkins_master_sg"
}
}
开发/模块/jenkins_master.tf
将安全组附加到实例将启用端口 8080(Jenkins Web 仪表板)和仅来自堡垒服务器和 VPC CIDR 块的 SSH 上的入站流量。
resource "aws_key_pair" "jenkins" {
key_name = "key-jenkins"
public_key = var.public_key
}
resource "aws_instance" "jenkins_master" {
ami = data.aws_ami.jenkins-master.id
instance_type = "t2.large"
key_name = aws_key_pair.jenkins.key_name
vpc_security_group_ids = [aws_security_group.jenkins_master_sg.id]
subnet_id = element(aws_subnet.private_subnets, 0).id
root_block_device {
volume_type = "gp3"
volume_size = 30
delete_on_termination = false
}
tags = {
Name = "jenkins_master"
}
}
开发/模块/jenkins_master.tf
接下来,我们创建一个变量并定义我们用于部署 EC2 实例的实例类型。我们不会在 master 上分配 executor 或 worker,因此 t2.large(8 GB 内存和 2vCPU)对于简单来说应该足够了。
因此,构建作业不会导致 Jenkins master 过度拥挤。但是 Jenkins 的内存要求会因项目的构建要求和这些构建中使用的工具而异。它需要两到三个线程,或至少 2 MB 内存,才能连接到每个构建节点。
请注意:考虑安装 Jenkins 工人以防止主人过度工作。因此,通用实例可以托管 Jenkins 主服务器并在计算和内存资源之间提供平衡。为了保持文章的简单性,我们不会这样做。
要访问 Jenkins 仪表板,我们将在 EC2 实例前创建一个公共负载均衡器。
此 Elastic Load Balancer 将接受端口 80 上的 HTTP 流量并将其转发到端口 8080 上的 EC2 实例。此外,它会自动检查端口 8080 上已注册 EC2 实例的运行状况。如果 Elastic Load Balancing (ELB) 发现实例运行状况不佳,它停止向 Jenkins 实例发送流量。
/*** A security group to allow SSH access into our load balancer*/ resource "aws_security_group" "lb" {
name = "ecs-alb-security-group"
vpc_id = var.vpc_id
ingress {
protocol = "tcp"
from_port = 80
to_port = 80
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "jenkins-lb-sg"
}
}
/***Load Balancer to be attached to the ECS cluster to distribute the load among instances*/
resource "aws_elb" "jenkins_elb" {
subnets = [for subnet in aws_subnet.public_subnets : subnet.id]
cross_zone_load_balancing = true
security_groups = [aws_security_group.lb.id]
instances = [aws_instance.jenkins_master.id]
listener {
instance_port = 8080
instance_protocol = "http"
lb_port = 80
lb_protocol = "http"
}
health_check {
healthy_threshold = 2
unhealthy_threshold = 2
timeout = 3
target = "TCP:8080"
interval = 5
}
tags = {
Name = "jenkins_elb"
}
}
output "load-balancer-ip" {
value = aws_elb.jenkins_elb.dns_name
}
开发/模块/负载均衡器.tf
之前,我们进行 terraform 应用,让我们更新 development/output.tf 文件夹以输出负载均衡器 DNS:
output "load-balancer-ip" {
value = module.subnet_module.load-balancer-ip
}
开发/输出.tf
在终端上,运行以下命令:terraform apply -var-file="secrets.tfvars"
. 这会给你这个:
负载均衡器输出
使用 Terraform 应用更改后,Jenkins 主负载均衡器 URL 应显示在终端会话中。
将您喜欢的浏览器指向该 URL,您应该可以访问 Jenkins Web 仪表板。
詹金斯实例
然后只需按照屏幕说明解锁。
解锁詹金斯
为了避免运行 AWS 服务的不必要成本,您需要运行以下命令来销毁所有已创建和正在运行的资源terraform destroy -var-file="secrets.tfvars"
: 应该会给出以下输出:
破坏资源
多么有趣,对吧?只需几行代码,我们就可以破坏和启动我们的资源。
在本教程中,您学习了如何在较高级别使用 Terraform。您还通过在 AWS 云平台上配置 Jenkins 服务器了解了它的一个应用程序。
您还了解了 Terraform 后端状态和模块的最佳实践。
快乐学习!
来源:https ://www.freecodecamp.org/news/learn-terraform-by-deploying-jenkins-server-on-aws/
1619263860
Terraform is a tool for building, changing, and versioning infrastructure safely and efficiently. Terraform can manage existing and popular service providers as well as custom in-house solutions.
#terraform-aws #terraform #aws #aws-ec2
1620959460
We’re continuing our series on Terraform AWS with a post that breaks down the basics. The world of Terraform AWS can be described as complex — from AWS storage to AWS best practices, there’s a depth of knowledge necessary to get familiar with Terraform AWS.
Whether you’re an expert at Terraform AWS or just getting started, it’s our goal at InfraCode to provide you with clear and easy-to-understand information at every level. The number of resources out there is abundant but overwhelming. That’s why we create simplified guides that are immediately usable and always understandable.
In this article, we’ll dive into:
#aws-ec2 #aws #terraform #terraform aws
1597945860
A few months ago, I was working on a Terraform module to manage all the roles and their permissions in our AWS accounts. This on the surface seems like a straight forward project, but there was a curveball that required some research, trial & error, and finesse to address.
The teams/permissions were not consistent across the AWS accounts. TeamA might have read/write access to s3 in account A, but only have read access to s3 in account B. Team A does not even exist in account C. Multiply this conundrum by 10+ teams across 10+ accounts.
In thinking about how to best tackle this issue, there were a couple bad ways to solve this that immediately come to mind:
This approach is horrible. It would have been tedious, hard to maintain, and the amount of repeated code would have been astronomical, but it would have worked.
This on the surface seems reasonable but it is not. First, your code is dictating business logic/function. Secondly, the principle of least privilege means that you should only allow enough access to perform the required job. Third, there are AWS accounts which certain teams should not have access to (e.g. secops, networking, & IT accounts). Last, the business would never agree to it.
The right approach needed to something that could account for all the variability across the accounts. Additionally, the end result needed to be clean, easy to maintain/update, and easy to use without requiring a deep understanding of how the module worked.
What I envisioned was something that allowed me to define the permissions as part of the config. This design addressed the variability issues across the accounts by allowing me to define the permissions per iteration of the module. Additionally, it was easy to understand and manage (even if you didn’t know what the module was doing).
This looked something like:
module usermap {
source = "../modules/example-module"
role_map_aws_policies = {
TeamA = ["AdministratorAccess"]
TeamB = ["AmazonS3FullAccess", "AmazonEC2FullAccess"]
TeamC = ["AdministratorAccess"]
TeamD = ["ReadOnlyAccess", "AmazonInspectorFullAccess"]
}
}
#aws #aws-iam #automating-aws-iam #terraform #terraform-modules
1601341562
Bob had just arrived in the office for his first day of work as the newly hired chief technical officer when he was called into a conference room by the president, Martha, who immediately introduced him to the head of accounting, Amanda. They exchanged pleasantries, and then Martha got right down to business:
“Bob, we have several teams here developing software applications on Amazon and our bill is very high. We think it’s unnecessarily high, and we’d like you to look into it and bring it under control.”
Martha placed a screenshot of the Amazon Web Services (AWS) billing report on the table and pointed to it.
“This is a problem for us: We don’t know what we’re spending this money on, and we need to see more detail.”
Amanda chimed in, “Bob, look, we have financial dimensions that we use for reporting purposes, and I can provide you with some guidance regarding some information we’d really like to see such that the reports that are ultimately produced mirror these dimensions — if you can do this, it would really help us internally.”
“Bob, we can’t stress how important this is right now. These projects are becoming very expensive for our business,” Martha reiterated.
“How many projects do we have?” Bob inquired.
“We have four projects in total: two in the aviation division and two in the energy division. If it matters, the aviation division has 75 developers and the energy division has 25 developers,” the CEO responded.
Bob understood the problem and responded, “I’ll see what I can do and have some ideas. I might not be able to give you retrospective insight, but going forward, we should be able to get a better idea of what’s going on and start to bring the cost down.”
The meeting ended with Bob heading to find his desk. Cost allocation tags should help us, he thought to himself as he looked for someone who might know where his office is.
#aws #aws cloud #node js #cost optimization #aws cli #well architected framework #aws cost report #cost control #aws cost #aws tags