应用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 updatesudo apt install software-properties-commonsudo add-apt-repository --yes --update ppa:ansible/ansiblesudo apt install ansible
配置Ansible的Hosts
- 运行
sudo vim /etc/ansible/hosts
在文件顶部插入以下内容:
[hosts]localhost[hosts:vars]ansible_connection=localansible_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。所有这些都可能扭转你的后果和应该采取的步骤。
我已尽力捕获过程中的所有步骤。所有这些因素都会扭转所需的步骤。如果您始终在跟进并遇到问题,请返回您的步骤并验证您是否批改了指定的变量。还要查看创立的所有凭据。当我最后经验这所有时,这仿佛就是我所有的问题所在。如果有谬误,我倡议查看日志并查看谬误以找到解决方案。我是人,我有可能错过了一步。