暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

基于Packer+Ansible实现云平台黄金镜像统一构建和发布

民生运维人 2020-12-23
1683

黄金镜像(Golden Image)是云平台虚拟机启动的镜像模板,通常由管理员基于ISO镜像或者各操作系统发行版发行的Base镜像之上添加安全合规基线配置以及预装必要的agent,如cloud-init、qemu-guest-agent、监控agent等。

手动做镜像一直是非常繁琐的事,首先需要准备一个KVM/QEMU虚拟机环境启动虚拟机,当然也可以直接在云平台基于一个Base镜像启动虚拟机。然后手动执行一系列脚本,安装各种必要工具及配置,接着把虚拟机关机,生成快照镜像,最后上传到云平台镜像仓库,预计至少需要半个小时以上完成整个黄金镜像的制作和发布。

这种方式至少存在如下几个问题:

  • 很难维护,一旦黄金镜像需要修改某个配置,就需要重新走整个制作镜像流程,非常耗费时间。如果只是简单配置修改,倒也是可以通过把镜像文件通过loop设备挂载到本地,然后chroot文件系统去直接改。
  • 镜像是一个二进制制品文件,没法做版本管理,无法跟踪变化,无法代码化。
  • 基本无法串联到任何pipeline中,比如镜像漏洞扫描等。

因此还在采用如上这种效率极低的手动方式的已经很少了,往往都会借助一些镜像构建工具,本文接下来会分别介绍OpenStack私有云和AWS公有云场景下的镜像构建工具,最后将介绍多云或者混合云场景下基于Packer+Ansible的镜像构建最佳实践。

备选方案:OpenStack diskimage-builder

OpenStack维护的DIB(disk image builder)项目,目前是TripleO项目的子项目,主要用于构建OpenStack镜像。

DIB的原理就是把一些操作封装成Shell脚本,比如创建用户(devuser)、安装cloud-init(cloud-init)、配置yum源(yum)、部署tgtadm(deploy-tgtadm)等,这些脚本称为Element,位于目录diskimage-builder/diskimage_builder/elements
,你可以根据自己的需求自己定制elements,elements之间会有依赖,依赖通过element-deps文件指定,比如elements centos7的element-deps为:

  • cache-url
  • redhat-common
  • rpm-distro
  • source-repositories
  • yum

devuser
Element为例,该Element为创建一个操作系统用户,脚本如下:

#!/bin/bash# ...省略部分代码user_shell_args=if [ -n "${DIB_DEV_USER_SHELL}" ]; then    user_shell_args="-s ${DIB_DEV_USER_SHELL}"fiuseradd -m ${DIB_DEV_USER_USERNAME} $user_shell_argsif [ -n "${DIB_DEV_USER_PASSWORD}" ]; then    echo "Setting password."    echo "${DIB_DEV_USER_USERNAME}:${DIB_DEV_USER_PASSWORD}" | chpasswdfiif [ -n "${DIB_DEV_USER_PWDLESS_SUDO}" ]; then    cat > /etc/sudoers.d/${DIB_DEV_USER_USERNAME} << EOF${DIB_DEV_USER_USERNAME} ALL=(ALL) NOPASSWD:ALLEOF    chmod 0440 /etc/sudoers.d/${DIB_DEV_USER_USERNAME}    visudo -c || rm /etc/sudoers.d/${DIB_DEV_USER_USERNAME}fiif [ -f /tmp/in_target.d/devuser-ssh-authorized-keys ]; then    mkdir -p /home/${DIB_DEV_USER_USERNAME}/.ssh    cp /tmp/in_target.d/devuser-ssh-authorized-keys /home/${DIB_DEV_USER_USERNAME}/.ssh/authorized_keysfichown -R ${DIB_DEV_USER_USERNAME}:${DIB_DEV_USER_USERNAME} /home/${DIB_DEV_USER_USERNAME}

可见该脚本就是使用Shell命令完成创建用户、设置密码以及配置sudo权限等工作。

DIB执行时会首先下载一个Base镜像,然后通过用户指定的elements列表,一个一个chroot进去执行,从而完成了镜像的制作,整个过程不需要启动虚拟机。这有点类似Dockerfile的构建过程,Dockerfile的每个指令都会生成一个临时的容器,然后在容器里面执行命令。DIB则每个elements都会chroot到文件系统中执行elements中的脚本。

比如制作Ubuntu 18.04镜像:

export DIB_RELEASE=bionicexport DIB_DEV_USER_USERNAME=ubuntuexport DIB_DEV_USER_PASSWORD=secretexport DIB_DEV_USER_PWDLESS_SUDO=YESdisk-image-create -o ubuntu-18.04.qcow2 vm ubuntu cloud-init-datasources devuser

DIB工具实现了OpenStack镜像自动化构建,相对手动制作镜像大大提高了效率。存在的问题是Elements是通过Shell脚本实现的,容易出错且不太好维护,很多判断逻辑都是和操作系统版本有关,很难做到完全兼容。另外,DIB通过chroot方式修改镜像,因此只支持主流的Linux操作系统,不支持Windows。

备选方案:AWS EC2 Image Builder

EC2 Image Builder是AWS上的镜像构建服务,它的原理是基于AWS已有托管的一个Source Base镜像启动一个EC2虚拟机实例,然后SSH到虚拟机执行一系列脚本安装软件和配置,最后创建快照制作成AMI镜像。

aws ec2 image builder

和DIB有点类似,EC2 Image Builder也是把一些操作封装成Shell脚本,在DIB中称为Element,而AWS上称为Component,这些Comontents可以由用户自己写,AWS也提供一些托管的Components可以直接使用,比如安装amazon-cloudwatch-agent-linux、预装apache-tomcat-9-linux等。

一个最简单的HelloWorld Component如下:

name: HelloWorldComponentdescription: This is hello world testing component.schemaVersion: 1.0phases:  - name: build    steps:      - name: HelloWorldStep        action: ExecuteBash        inputs:          commands:            - echo "Hello World! Build."  - name: validate    steps:      - name: HelloWorldStep        action: ExecuteBash        inputs:          commands:            - echo "Hello World! Validate."  - name: test    steps:      - name: HelloWorldStep        action: ExecuteBash        inputs:          commands:            - echo "Hello World! Test."

和DIB不一样,DIB是通过chroot到镜像文件系统,注定无法制作Windows镜像,而EC2 Image Builder启动了一个虚拟机,Windows虚拟机可以通过WinRM执行PowerShell脚本,因此EC2 Image Builder是支持Windows的。

EC2 Image Builder除了执行脚本外,还封装了一些高级Action模块,比如从S3上上传下载文件、修改文件权限、复制文件等,通过这些高级模块不需要自己写脚本,可以简化Component。

EC2 Image Builder可以与其他生态服务结合完成自动构建,比如一旦Component代码更新或者Base镜像更新后,EC2 Image Builder会自动触发镜像构建,因此能够保证黄金镜像是最新的。

推荐方案:Packer+Ansible

Packer是Hashicorp开源的镜像构建工具,Hashicorp这个公司除了开源了Packer项目,还包括Consul、Terraform、Vault、Vagrant等非常流行的工具。

如果说DIB解决了OpenStack私有云镜像自动化构建问题,EC2 Image Builder解决了AWS公有云镜像构建,那么Packer则同时解决了私有云、公有云、混合云的黄金镜像统一构建问题,它不仅支持主流公有云如AWS、阿里云、腾讯云、Google云、Azure等,还支持如QEMU、VirtualBox、VMware、OpenStack等私有云环境。

Packer的原理和EC2 Image Builder比较类似都是通过启动一个虚拟机,然后通过SSH(Linux)或者WinRM(Windows)在虚拟机环境中执行脚本完成自动化配置,最后生成镜像模板。比如制作AWS黄金镜像会创建一个EC2实例,而如果是QEMU,则通过qemu-system-x86
命令配合kickstart启动一个初始化虚拟机。

Packer支持并行在多平台上构建镜像,在混合云场景下,Packer能基于同样的标准同时并行构建公有云镜像和私有云镜像,保持多平台的镜像一致性。

除此之外,Packer和前面介绍的两个镜像构建工具不一样的是,除了支持常规的Shell或者Powershll脚本,Packer更推荐结合自动化配置管理工具构建镜像模板,比如大家熟悉的Ansible、Puppet、Chef、Salt等。

本文接下来主要简要介绍如何使用Packer + Ansible构建OpenStack以及AWS镜像。

首先介绍Packer的最主要的两个概念:

  • Builder:Builder就是告诉Packer要构建什么镜像(AWS Or OpenStack)以及一些必要的环境配置信息,比如AWS的AccessKey/AccessSecret、Source AMI、规格等,OpenStack的AuthURL、Project、Domain、Username、Password、Source Image等。
  • Provisioner:Provisioner就是告诉Packer要如何构建镜像,你可以告诉Packer执行一些Shell脚本、执行Ansible playbook等。

以构建OpenStack镜像为例,样例模板如下:

{  "variables": {    "region""RegionOne",    "flavor""m1.micro",    "network_id""695c8e70-ae94-4c39-86f0-c15dcaea7dd2",    "source_image""4e61e55b-c635-44e7-a722-674b2a454927",    "ssh_username""ubuntu"  },  "builders": [    {      "type""openstack",      "region""{{user `region`}}",      "ssh_username""{{user `ssh_username`}}",      "image_name""Packer-ubuntu-20.04-x86",      "source_image""{{user `source_image`}}",      "flavor""{{user `flavor` }}",      "networks": [        "{{user `network_id`}}"      ]    }  ],  "provisioners": [    {      "type""shell",      "inline": [        "sudo useradd -m -r -s /bin/bash int32bit",        "echo 'int32bit:1sReAe7nUGO5M' | sudo chpasswd -e"      ]    }  ]}

这个模板只配置了一个OpenStack Builder,Provisioner也比较简单,仅通过Shell脚本创建了一个int32bit
用户。

这种JSON格式模板对于程序是友好的,但是对于程序员编写不太方便,Packer模板还支持HCL(Hashicorp Configuration Language)语言,目前还处于Beta阶段,不过亲测可以用。

Terraform使用的就是HCL,因此使用过Terraform的对HCL语言肯定不陌生了,前面的例子转化成HCL格式为:

variable "flavor" {  type    = string  default = "m1.micro"}variable "network_id" {  type    = string  default = "unset"}variable "region" {  type    = string  default = "RegionOne"}variable "source_image" {  type    = string  default = "unset"}variable "ssh_username" {  type    = string  default = "ubuntu"}source "openstack" "test_openstack" {  flavor       = "${var.flavor}"  image_name   = "Packer-ubuntu-20.04-x86"  networks     = ["${var.network_id}"]  region       = "${var.region}"  source_image = "${var.source_image}"  ssh_username = "${var.ssh_username}"}build {  sources = ["source.openstack.test_openstack"]  provisioner "shell" {    inline = [        "sudo useradd -m -r -s bin/bash int32bit",        "echo 'int32bit:1sReAe7nUGO5M' | sudo chpasswd -e"    ]  }}

调用packer命令即可构建如上镜像:

packer build -var source_image=xxxx -var network_id=xxxx ubuntu.pkr.hcl

packer openstack packer

如上的Provisioner通过内嵌Shell脚本进行配置,简单的功能还好,复杂的配置则更推荐使用自动化配置工具,接下来以Ansible为例,介绍如何构建黄金镜像。

前面介绍的DIB、EC2 Image Builder按照功能分别拆分的Element和Component,这样便于模块化管理。使用Ansible我们可以把不同的功能划分为不同的Ansible Role。

以安装cloud-init为例,首先使用ansible-galaxy
初始化Role:

mkdir -p packer_template/ansible/rolescd packer_template/ansible/rolesansible-galaxy role init cloud-init

Role的playbook如下:

# cat ansible/roles/cloud-init/tasks/main.yml---# tasks file for cloud-init- name: Install cloud-init  package:    name: cloud-init    state: present- name: Enable cloud-init service  service:    name: cloud-init    enabled: true

创建入口playbook build_image.yaml
如下:

# cat ansible/build_image.yaml---- name: Config Task For Build Image  hosts: all  gather_facts: true  become: yes  become_method: sudo  tasks:  - name: Setup cloud-init    include_role:      name: cloud-init

创建Packer template,这里我们同时构建AWS和OpenStack镜像,为了使代码简单,去掉了variable
部分,OpenStack的认证信息通过环境变量获取,AWS的认证通过STS Role指定,对应template模板文件如下:

source "openstack" "test_openstack" {  image_name    = "Packer-test-ubuntu"  flavor        = "m1.micro"  ports         = ["695c8e70-ae94-4c39-86f0-c15dcaea7dd2"]  region        = "RegionOne"  source_image  = "4e61e55b-c635-44e7-a722-674b2a454927"  ssh_username  = "ubuntu"}source "amazon-ebs" "test_aws" {  ami_name      = "Packer-test-ubuntu"  region        = "cn-northwest-1"  source_ami    = "ami-04effa29f4d91541f"  instance_type = "t2.micro"  ssh_username  = "ubuntu"  vpc_id        = "vpc-02f7b6239c82c9cd1"  subnet_id     = "subnet-053ab0880cea4e85c"}build {  sources = [    "source.openstack.test_openstack",    "source.amazon-ebs.test_aws"  ]  provisioner "ansible" {    playbook_file = "ansible/build_image.yaml"    user          = "ubuntu"  }
}

与前面的例子不同的是:

  • sources指定了多个builder,这里分别为OpenStack和AWS。
  • provisioner指定为ansible
    类型,指定了playbook路径。

使用packer命令执行:

packer build ubuntu.pkr.hcl

输出如图:

packer openstack and aws

不同的builder通过颜色区分,最后会输出生成的所有镜像制品ID:

==> Wait completed after 5 minutes 53 seconds

==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs.test_aws: AMIs were created:
cn-northwest-1: ami-071e19313c601b374
--> openstack.test_openstack: An image was created: 
583a0bac-b973-47aa-8424-8929dfa99288

以上例子通过Packer+Ansible使用同一个Provision配置并行构建了OpenStack私有云镜像和AWS公有云镜像,实现黄金镜像的构建自动化和代码化。

结论

本文首先介绍了私有云场景下OpenStack DIB工具以及公有云AWS EC2 Image Builder服务两个主流镜像构建方案,对比了其优缺点,然后介绍了适合多云或者混合云场景下的Packer镜像构建工具,最后引入Packer+Ansible镜像最佳构建实践。

通过Packer可同时并行构建私有云和公有云镜像,并且由于Provision是一样的,最后构建的镜像也是完全一样的,通过Packer实现了混合云模式下镜像的统一构建和发布。

同时通过Ansible自动化工具简化了代码的编写,Ansible playbook相对Shell脚本更易于维护和管理,不需要自己脚本判断操作系统类型,Ansible会自己判断。

镜像构建完全代码化,因此可以很容易地通过代码仓库做版本控制,集成企业CICD,实现镜像的统一构建和发布。


作者简介:

付广平:任职民生银行云技术管理中心,负责云基础架构、运维以及云计算相关技术研究。从2013年开始从事OpenStack相关开发工作,OpenStack 、Cloud Custodian、Harbor等开源项目Contributor,微信公众号int32bit以及知乎专栏《OpenStack》作者。对Ceph、Docker、Kubernetes等技术也有一定的了解。

高岩:任职民生科技智能云技术部,目前致力于云平台工作,熟悉Python、Shell、操作系统、虚拟化等相关技术。

刘婉辉:任职民生银行云技术管理中心,目前致力于云平台落地、推广、云平台运维管理等工作。

蔡泽宇:任职民生银行云技术管理中心,毕业于北京邮电大学,目前致力于民生银行运维相关工作,熟悉Python、Shell等编程语言。

崔增顺:任职民生银行云技术管理中心,目前致力于云平台工作,熟悉私有云、公有云架构以及解决方案。

刘文静:任职民生银行云技术管理中心,目前主要致力于民生银行云管平台建设和运维工作。

编辑:民生运维文化建设小组

文章转载自民生运维人,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论