int32bit
作者int32bit·2020-12-22 20:45
研发工程师·民生银行

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

字数 10053阅读 6740评论 0赞 2

黄金镜像(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}"
    fi
    useradd -m ${DIB_DEV_USER_USERNAME} $user_shell_args
    if [ -n "${DIB_DEV_USER_PASSWORD}" ]; then
        echo "Setting password."
        echo "${DIB_DEV_USER_USERNAME}:${DIB_DEV_USER_PASSWORD}" | chpasswd
    fi
    if [ -n "${DIB_DEV_USER_PWDLESS_SUDO}" ]; then
        cat > /etc/sudoers.d/${DIB_DEV_USER_USERNAME} << EOF
    ${DIB_DEV_USER_USERNAME} ALL=(ALL) NOPASSWD:ALL
    EOF
        chmod 0440 /etc/sudoers.d/${DIB_DEV_USER_USERNAME}
        visudo -c || rm /etc/sudoers.d/${DIB_DEV_USER_USERNAME}
    fi
    if [ -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_keys
    fi
    chown -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=bionic
export DIB_DEV_USER_USERNAME=ubuntu
export DIB_DEV_USER_PASSWORD=secret
export DIB_DEV_USER_PWDLESS_SUDO=YES
disk-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镜像。

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

一个最简单的HelloWorld Component如下:

    name: HelloWorldComponent
    description: This is hello world testing component.
    schemaVersion: 1.0
    phases:
      - 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

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


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

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

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

mkdir -p packer_template/ansible/roles
cd packer_template/ansible/roles
ansible-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

输出如图:

不同的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,实现镜像的统一构建和发布。

如果觉得我的文章对您有用,请点赞。您的支持将鼓励我继续创作!

2

添加新评论0 条评论

Ctrl+Enter 发表

作者其他文章

相关文章

相关问题

相关资料

X社区推广