应用 Jenkins CI/CD Pipeline 通过 Terraform 和 Ansible 创立 AWS 基础设施。
首先,我要感激 Derek Morgan 和他的 Terraform、Ansible and Jenkins 课程。我最近实现了他的课程,再怎么举荐也不为过。它从小开始,到最初你有一个简单的工作我的项目。实现每门课程后,我都会尝试创立本人的我的项目来强化我所学的常识,而这篇文章正是记录了这一点。
该项目标最终后果是 Terraform 代码,该代码创立了一个带有公共子网和 EC2 实例的 AWS 环境,以及一个在新创建的 EC2 实例上装置 Docker 的 Ansible Playbook 剧本脚本文件。当代码被推送到 GitHub 存储库时,GitHub Webhook 将触发 Jenkins CI/CD pipeline,该 pipeline 将执行一系列操作,具体取决于咱们是将代码推送到开发分支还是主分支。
留神 :本文显示了实现的我的项目,但心愿您在构建它时测试每个局部以验证一切正常,而不是一下子就齐全构建它。对于这个,我不打算具体阐明我是如何做到的。例如,我不会具体介绍如何设置 Terraform,而后在构建基础设施时逐渐测试代码,以及如何在将 playbook 利用到 Jenkins 管道之前配置 Ansible 并在本地测试它们等。只是意识到这些步骤在开发过程中是典型的。依照我列出此文档的形式,如果您依照这种形式独自进行测试,您将遇到谬误。如果您想理解更多详细信息,我再次建议您查看 Derek 的课程。
免责申明:尽管本文将带您实现该项目标步骤,但它并不是一个教程,而是我最近的一个副我的项目的文档。如果您收到任何谬误,请不要发送怨恨邮件。
先决条件
- GitHub 帐户
- AWS CLI
- 装置 Terraform
- AWS 账户
- 具备管理员权限的 AWS 用户
- AWS Cloud9(您能够应用其余 IDE,但要意识到某些步骤可能会有所不同)
- Terraform Cloud 帐户
- 装置 Git
开始设置环境
创立您的 Amazon Cloud9 环境
应用除平台局部之外的所有默认值。抉择Ubuntu Server 18.04LTS
。
调配 Elastic IP
为了避免 Cloud9 实例在每次敞开和重新启动时更改其公共 IP 地址,请为其调配一个弹性 IP。这也将容许稍后将公共 IP 调配给平安组的 Terraform 变量。只需确保在删除 Cloud9 实例后删除弹性 IP,否则将收取费用。
- 在 AWS 控制台中导航到 EC2
- 在
Network
和Security
下抉择Elastic IP
- 单击调配
Allocate Elastic IP address
- 点击
Allocate
- 从
Action
下拉列表中抉择Associate Elastic IP Address
-
实例:抉择您的 Cloud9 实例,公有 IP 地址:抉择您的 Cloud9 公有 IP 地址。单击
Associate
。调整 Cloud9 实例的大小
Derek 的课程提供了一个很棒的大小调整脚本来扩充 Cloud9 实例的存储空间,这将是必须的。
- 在 Cloud9 中创立一个名为 resize.sh 的文件
- 将 Derek 存储库中的代码复制到您的 resize.sh 文件中
- 运行
chmod +x resize.sh
- 运行
./resize.sh
创立 SSH 密钥
- 在您的终端中运行
ssh-keygen -t rsa
- 输出保留密钥的文件(我命名为 mykey):/home/ubuntu/.ssh/<key name>
- 没有明码
- 通过运行
ls ~/.ssh
验证您的密钥是否已创立
装置 jq
运行sudo apt install jq
Fork Repo
如果您想 fork 并援用它,能够在这里找到我的代码:
https://github.com/troy-ingra…
Terraform Cloud
[Terraform Cloud]容许您将 Terraform 状态存储在近程平安地位,而不是将其存储在本地。这容许更好的安全性和更好的团队合作。
- 创立一个新的 workspace
- 抉择
CLI-driven workflow
- 命名您的 workspace,而后创立 workspace
- 复制 workspace 提供的示例代码并将其增加到 backends.tf 以替换以后后端
- 通过单击
Remote
并抉择Local
来设置Execution Mode
创立 Terraform Cloud 令牌
- 单击浏览器右上角的
Profile
,而后抉择User settings
- 在
User settings
下抉择Token
- 单击创立
Create API token
- 输出形容并单击创立
Create API token
- 复制提供的令牌并将其保留在平安的中央(如果您失落了它,您能够随时使令牌过期并创立另一个)
- 导航到 Cloud9 终端并运行
terraform login
- 键入
yes
,而后粘贴之前复制的 Terraform Cloud 令牌 - 运行
terraform init
这将咱们的 Terraform 代码连贯到咱们的 Terraform Cloud 的 workspace 的替换了 Terraform Cloud 的本地后端。Jenkins 将来在运行管道时也会拜访 Terraform Cloud。
Terraform
作为与云无关的产品,HashiCorp 的 Terraform 反对多云和本地。它是一个开源工具,具备企业版和社区版,应用 HashiCorp 本人的 HashiCorp 配置语言 (HCL)。Terraform 的 HCL 容许开发人员学习一种语言以应用多种云产品和本地提供商,而不用为每种语言学习新的服务和语言。HCL 是一种申明性语言,专一于最终状态,而不是过程语言,其中所有命令都按编写的程序执行。Hashicorp 为开发人员提供无关 Terraform 的文档,并且能够拜访官网和社区模板的 Terraform Registry。
更新 variables.tf
- 将 access_ip 变量批改为您的集体公共 IP CIDR
- 将 cloud9_ip 变量批改为您的 Cloud9 IP CIDR
更新 tfvars 文件
- 将 main.tfvars 和 dev.tfvars 中的 key_name 变量更新为您的密钥名称
- 将 main.tfvars 和 dev.tfvars 中的 public_key_path 变量更新为您的密钥名称
Terraform 源码
Terraform 代码能够在这 GitHub 的 repo 中找到:所以我不打算在这里探讨所有内容。我将重点介绍一些有助于使文件更加通用和整洁的注意事项。
Data Source
应用一个 AWS Available Zones 的 Data Source 来确定将要在其中启动资源的 Region 中以后可用的 AZ。应用 Local Value 来设置 AZ 的名称。而后应用 length 函数依据 AZ 的数量来确定计数。而后该计数用于索引 cidr_block 和 availability_zones,而不是为每一个独自创立一个 AZ 定义块。
locals {azs = data.aws_availability_zones.available.names}
data "aws_availability_zones" "available" {}
resource "aws_subnet" "public_subnet" {count = length(local.azs)
vpc_id = aws_vpc.vpc.id
cidr_block = var.public_cidr[count.index]
map_public_ip_on_launch = true
availability_zone = local.azs[count.index]
tags = {Name = "docker-public"}
}
Data Source 还用于指定在该 Region 行将要启动的映像的 AMI ID。这打消了对 AMI ID 进行硬编码或创立 AMI 和 Region 的映射的须要。请留神 ami 如何调用 Data Source 来确定 Ubuntu AMI ID。
data "aws_ami" "ubuntu" {
most_recent = true
owners = ["099720109477"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
}
resource "aws_instance" "web" {
count = var.instance_count
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
vpc_security_group_ids = [aws_security_group.sg.id]
subnet_id = aws_subnet.public_subnet[count.index].id
key_name = aws_key_pair.docker_auth.id
tags = {Name = "docker-instance"}
}
Ansible
Ansible 是一个简略的配置自动化工具,它应用 SSH 而不是在指标主机上安装代理。通常须要简单脚本来自动化的事件当初能够应用 Ansible 应用 Ansible Playbook 在几行代码中实现。Ansible Playbooks 应用 YAML,与大多数脚本语言相比,它是一种易于浏览的纯英语语言。应用 Ansible,您能够治理要跟踪或批改的主机的纯文本清单文件。这些主机能够组合在一起,也能够独自分组在不同的组题目下。在构建 Ansible Playbook 时,您能够辨别哪些组应该运行哪些模块。
装置 Ansible
在 Cloud9 实例上运行以下命令以装置 Ansible。
sudo apt update
sudo apt install software-properties-common
sudo add-apt-repository --yes --update ppa:ansible/ansible
sudo apt install ansible
配置 Ansible 的 Hosts
- 运行
sudo vim /etc/ansible/hosts
-
在文件顶部插入以下内容:
[hosts] localhost [hosts:vars] ansible_connection=local ansible_python_interpreter=/usr/bin/python3
-
退出并保留。
配置
为防止提醒输出 ECDSA 密钥指纹,请按如下配置 ansible.cfg 文件。
- 运行
sudo vim /etc/ansible/ansible.config
- 在“# host_key_checking = False”一行中去掉正文合乎“#”
-
退出并保留。
Ansible Playbook: docker.yml
与其手动运行命令来装置 Docker,不如应用上面的 docker.yml Ansible Playbook。Playbook 分为几个工作,每个工作都有一个形容所采取行动的名称。须要留神的一些主题是应用“{{变量名称}}”表示法而不是创立多个工作来应用 Ansible Playbook 变量。另请留神 ansible.builtin.apt 或 apt 用于包治理。Ansible 通过应用 State 来查看包是否存在于实例上。如果指标是删除一个包,那么 State 将从 preset 更改为 absent。
另请留神,hosts 援用的是 main 而不是 local 的。 -
name: Install Docker
hosts: main
become: yestasks:
- name: Update apt cache
apt: update_cache=yes cache_valid_time=3600 - name: Upgrade all apt packages
apt: upgrade=dist -
name: Install dependencies
apt:
name: “{{packages}}”
state: present
update_cache: yes
vars:
packages:- apt-transport-https
- ca-certificates
- curl
- software-properties-common
- gnupg-agent
- name: Add an apt signing key for Docker
apt_key:
url: https://download.docker.com/l…
state: present - name: Add apt repository for stable version
apt_repository:
repo: deb [arch=amd64] https://download.docker.com/l… focal stable
state: present -
name: Install Docker
apt:
name: “{{packages}}”
state: present
update_cache: yes
vars:
packages:- docker-ce
- docker-ce-cli
- containerd.io
- name: Update apt cache
Jenkins
Jenkins 是一个独立的开源自动化服务器,用于自动化与构建、测试和交付 / 部署软件相干的工作。Jenkins Pipeline 通过应用插件和 Jenkinsfile 将继续交付管道实现到 Jenkins。Jenkinsfile 能够是申明式的或脚本式的,并蕴含管道要遵循的步骤列表。
应用 Ansible 装置 Jenkins
您能够在 Cloud9 实例上手动装置 Jenkins,但为了取得更多练习,您也能够应用 Ansible。
运行ansible-playbook playbook/jenkins.yml
凋谢 8080 端口
- 在 AWS 控制台中导航到您的 Cloud9 的 Security Group。
- 凋谢 8080 端口,范畴 0.0.0.0/0,面向全世界。这是容许咱们拜访 Jenkins 服务器所必须的,而且它解决了咱们稍后会遇到的 GitHub Webhook 问题。这就是为什么它向世界凋谢,而不仅仅是一个 IP。
- 在浏览器中导航到 <Cloud9 Public IP>:8080 进行测试。
配置 Jenkins
- 依照屏幕上的阐明通过
sudo cat /var/lib/jenkins/secrets/initialAdminPassword
运行检索您的治理明码 - 将后果复制并粘贴到 Unlock Jenkins 字段中,而后单击
Continue
- 单击
Install suggested plugins
- 输出所需信息。保留并持续到 Jenkins。
- 点击
Manage Jenkins > Manage Plugins
抉择Available
标签,查找Ansible
并点击install without restart
- 点击
Manage Plugins
抉择Available
标签,查找Pipeline: AWS Steps
并点击install without restart
治理 Jenkins 凭证
GitHub App
- 在 GitHub 中单击浏览器右上角的个人资料图标,而后抉择
Settings
- 点击左侧导航底部的
Development settings
- 单击
GitHub Apps
,而后单击New GitHub App
- 依照 [GitHub App]() 文档中
Creating GitHub App
局部的阐明进行操作 - 将私钥下载到本地后,在下载文件夹中关上,而后拖放到 Cloud9 实例的顶层(这样能够确保您当前不会意外提交此文件)
-
在终端的顶层运行:
openssl pkcs8 -topk8 -inform PEM -outform PEM -in [key-in-your-downloads-folder-name].pem -out converted-github-app.pem -nocrypt
- 这将创立一个名为 converted-github-app.pem 的文件,该文件对 Jenkins 敌对
- 导航到 Jenkins 点击
Manage Jenkins > Manage Credentials > Jenkins
- 点击
Global > Global credentials
- 点击
Add Credentials
- Kind = GitHub App
- ID = [名称]
- Description = GitHub App Credentials
- App ID =(这能够在 GitHub App 中找到。
`
Settings > Developer settings > GitHub Apps > [您的 App 名称]) - Key = 复制并粘贴之前转换的密钥的 converted-github-app.pem 文件内容,而后单击
OK
- 导航回您的 GitHub App 并通过单击
Install
来装置该应用程序 - 回到 Jenkins,点击你的
GitHub App Credentials
。单击Update
,而后单击Test connection
以验证一切正常。
Terraform Cloud 凭证
- 要获取 Terraform Cloud 凭据,请在终端中运行
cat /home/ubuntu/.terraform.d/credentials.tfrc.json
- 复制输入。创立一个本地 txt 文件并粘贴输入并保留
- 导航回 Jenkins 并点击
Add a new credentials
- Kind = Secret file
- File = (抉择刚刚保留的 txt 文件)
- ID = tf-creds(在上面援用到)
- Description = Terraform Cloud Credentials
SSH Key
- 在 Cloud9 终端上运行
`
cat /home/ubuntu/.ssh/<key name> - 复制输入
- 返回 Jenkins 并点击
Add a new credentials
- Kind = SSH Username with private key
- ID = ec2-ssh-key (在上面援用到)
- Description = SSH key for EC2 instances
- Username = ubuntu
- Private Key = 抉择
Enter directly
,粘贴后面的复制 - 点击
OK
GitHub Webhook
- 导航到我的项目 GitHub 存储库并单击
Settings
- 点击
Webhooks
- 点击
Add webhook
Paylod URL = <Jenkins URL>/github-webhook/
留神:确保开端有 ”/”。Content type = application/json
Which events would you like to trigger this webhook? = Just the push event
创立 Jenkins Pipeline
- 导航到 Jenkins 的 Dashboard 并单击
New Item
- 命名 pipeline 并抉择
Multi-Branch Pipeline
,而后单击OK
- 接下来配置 pipeline:
- Display Name = <Name of pipeline>
- Branches Sources = GitHub
- GitHub Credentials = 抉择
GitHub App Credentials
- Repository HTTPS URL = <Your repo name>.git
留神 :保留后,您将被带到扫描存储库日志屏幕,即便它已实现,它看起来也像是在进行中。在输入底部看到SUCCESS
后,您就能够返 Dashbord 了。
Jenkinsfile
Jenkins 须要在咱们代码的根目录下应用 Jenkinsfile 来创立 Jenkins pipeline。上面是这个我的项目应用的 Jenkinsfile。
pipeline {
agent any
environment {
TF_IN_AUTOMATION = 'true'
TF_CLI_CONFIG_FILE = credentials('tf-creds')
AWS_SHARED_CREDENTIALS_FILE='/home/ubuntu/.aws/credentials'
}
stages {stage('Init') {
steps {
sh 'ls'
sh 'cat $BRANCH_NAME.tfvars'
sh 'terraform init -no-color'
}
}
stage('Plan') {
steps {sh 'terraform plan -no-color -var-file="$BRANCH_NAME.tfvars"'}
}
stage('Validate Apply') {
when {
beforeInput true
branch "dev"
}
input {
message "Do you want to apply this plan?"
ok "Apply plan"
}
steps {echo 'Apply Accepted'}
}
stage('Apply') {
steps {sh 'terraform apply -auto-approve -no-color -var-file="$BRANCH_NAME.tfvars"'}
}
stage('Inventory') {
steps {sh '''printf \\"\\n$(terraform output -json instance_ips | jq -r \'.[]\')" \\
>> aws_hosts'''
}
}
stage('EC2 Wait') {
steps {
sh '''aws ec2 wait instance-status-ok \\
--instance-ids $(terraform output -json instance_ids | jq -r \'.[]\') \\
--region us-east-1'''
}
}
stage('Validate Ansible') {
when {
beforeInput true
branch "dev"
}
input {
message "Do you want to run Ansible?"
ok "Run Ansible"
}
steps {echo 'Ansible Approved'}
}
stage('Ansible') {
steps {ansiblePlaybook(credentialsId: 'ec2-ssh-key', inventory: 'aws_hosts', playbook: 'playbooks/docker.yml')
}
}
stage('Validate Destroy') {
input {
message "Do you want to destroy?"
ok "Destroy"
}
steps {echo 'Destroy Approved'}
}
stage('Destroy') {
steps {sh 'terraform destroy -auto-approve -no-color -var-file="$BRANCH_NAME.tfvars"'}
}
}
post {
success {echo 'Success!'}
failure {sh 'terraform destroy -auto-approve -no-color -var-file="$BRANCH_NAME.tfvars"'}
aborted {sh 'terraform destroy -auto-approve -no-color -var-file="$BRANCH_NAME.tfvars"'}
}
}
我不会介绍这个文件的所有内容,但会强调一些乏味的点。
$BRANCH_NAME 环境变量
当咱们执行 Jenkinsfile 时,应用这个环境变量能够辨别咱们的分支。例如,上面的示例执行 shell 脚本并输入启动 GitHub Webhook 的分支。对于打算阶段,这容许咱们调用 main.tfvars 或 dev.tfvars 文件,这将笼罩咱们的默认 variables.tf 文件。
条件和输出
如果分支等于 dev,则应用 when
的条件来确定是否运行输出。如果分支不等于 dev,则跳过输出。
输出用于暂停 pipeline 并期待手动抉择 Apply plan
或Abort
。
EC2 期待
这可能是我从课程中学到的最不便的物品之一。在 Inventory 和 EC2 期待阶段下方都应用 shell 脚本来获取 Terraform 输入,并应用 jq 来获取实例 ID 和实例的 IP。Inventory 阶段传递从 Terraform 代码创立的实例 IP,并将它们附加到 Ansible 将应用的 aws_hosts 文件中,从而创立一个动静清单文件。EC2 期待阶段执行雷同的操作,但传递实例 ID 并执行期待命令。这将验证实例是否已实现初始化,而后再转到执行 Ansible 的下一步。
整合所有
dev 分支
- 通过运行
git checkout -b dev
创立一个 dev 分支。 - 运行
git add
。 - 通过运行
git commit -m "initial commit"
提交文件。 - 通过运行
git push -u origin dev
将代码推送到 dev 分支并通过 GitHub 进行身份验证。 - 导航到您的 Jenkins 仪表板。
- 单击位于浏览器左下角的
Build Executor Status
下的running pipeline
。 - 将鼠标悬停在
Validate Apply
步骤上,单击Apply plan
。 - 而后 pipeline 将利用 Terraform 代码,运行清单步骤,并期待 EC2 实例初始化,这样 Ansible 就不会在尝试拜访实例时出错。
- 一旦达到
Validate Ansible
步骤,再次将鼠标悬停在该步骤上并抉择Run Ansible
。 - 期待时,单击
Build History
旁边的绿色复选标记以查看输入。您将看到 Ansible 正在运行,但您也能够向上滚动并查看整个 pipeline 的所有输入。 - 当 pipeline 在
Validate Destroy
步骤中期待时,让咱们查看咱们的实例以确保 Docker 装置正确。在输入中向上滚动并找到instance_ips
输入并向下复制实例的公共 IP。 - 返回 Cloud9 终端并应用您的 SSH 密钥 SSH 进入实例。
ssh -i /home/ubuntu/.ssh/[key name] [email protected][instance ip]
- 验证运行
docker --version 13
. 返回您的 Jenkinspipeline 并抉择Destroy
。
main 分支
因为 dev 分支曾经过验证,代码能够推送到主分支。
- 通过运行
git checkout main
切换到主分支。 - 通过运行
git merge dev
合并 dev 分支。 - 通过运行
git push -u origin main
将代码推送到 main 并通过 GitHub 进行身份验证。 - 切换回 Jenkins 并查看咱们的 pipeline。这次你应该看到 main 的另一个分支。
- 单击 main 以查看运行中的 pipeline。因为代码曾经在开发 pipeline 中进行了测试,Jenkinsfile 将依据设置的条件跳过验证步骤。pipeline 应该始终挪动到
Validate Destroy
阶段。 - 获取 Docker 实例 IP,相似于测试开发 pipeline 的形式并验证 Docker 是否已装置。一旦确认返回 pipeline 并销毁。
附加测试
转到 dev.tfvars 文件和 main.tfvars 文件并将实例计数更新为 2 或 3,并验证 pipeline 是否仍在运行并且是否创立了多个实例。
故障排除
留神 :请再次查看本文结尾的免责申明。如果您偏离上述步骤,那么您可能会失去不同的后果。例如,如果您在 Cloud9 上应用您最喜爱的 IDE,或者如果您应用 Amazon Linux 而不是 Ubuntu。所有这些都可能扭转你的后果和应该采取的步骤。
我已尽力捕获过程中的所有步骤。所有这些因素都会扭转所需的步骤。如果您始终在跟进并遇到问题,请返回您的步骤并验证您是否批改了指定的变量。还要查看创立的所有凭据。当我最后经验这所有时,这仿佛就是我所有的问题所在。如果有谬误,我倡议查看日志并查看谬误以找到解决方案。我是人,我有可能错过了一步。