## 使用Buildpacks构建Docker镜像
![](../assets/运维手册-Buildpacks-Buildpacks.io.png)
### Buildpacks简介
与Dockerfile相比,Buildpacks为构建应用程序提供了更高层次的抽象。具体来说,Buildpacks:
* 提供一个平衡的控制,减少开发人员的操作负担,并支持企业运营商在规模上管理应用程序。
* 确保应用程序满足安全性和法规遵从性要求,而无需开发人员干预。
* 提供操作系统级和应用程序级依赖项升级的自动交付,有效地处理第二天的应用程序操作,这些操作通常很难用Dockerfile进行管理。
* 依赖兼容性保证安全地应用补丁,而不必重建工件,也不必意外地更改应用程序行为。
Buildpacks是可插入的、模块化的工具,通过提供比Dockerfile更高级别的抽象,将源代码转换为容器就绪的构件。通过这样做,他们提供了一种控制的平衡,最小化了最初的生产时间,减少了开发者的操作负担,并支持大规模管理应用程序的企业运营商。
Cloud Native Buildpacks
2018年10月云原生Buildpacks项目加入CNCF沙箱
CNB流程分为四个步骤,每个步骤都有各自的重要目标,最终产出就是 OCI 镜像。CNB让开发和运维人员能够把创建各种软件的过程中所需的构建、补丁和重新打包的工作自动化成适合机器执行的重复任务。
![](../assets/运维手册-Buildpacks-CNB流程.png)
检测:对源码以及其它内容进行检测,查找与其匹配的可用 Buildpacks。假设提供一套Go源文件,就会检测到Go Buildpack适用于这一输入。
分析:CNB 会在应用的生命周期中运行多次,在这一步骤里会对前一次的打包内容进行分析,分析过程会对文件的变更进行优化,从而减少构建时间和文件传输。这里会使用多个镜像层来对内容进行组织。
构建:如果镜像层或者目录需要进行替换,构建过程就会生成新的层。这里会提供缓存来加速构建过程。
导出:这个步骤中会生成最终镜像并推送到镜像仓库之中。传输、磁盘使用和更新时间都会用镜像层的更新操作来完成。另外 CVE 补丁也可以同时应用到多个镜像之中。
所以还等什么?现在开始使用Buildpacks尝尝鲜吧。
### 应用程序从源代码到镜像的短暂之旅
#### 构建Go Web应用Docker镜像
使用pack和buildpacks从源代码创建可运行的应用程序镜像。这意味着需要确保安装了pack程序包,注意:pack只是Cloud Native Buildpacks平台规范的一个实现。
先决条件:
安装Docker
安装pack
#### 构建一个app
将应用程序从源代码转换为可运行镜像的基础知识。
1.选择builder
2.构建app
3.运行app
环境变量
环境变量是在构建时配置各种构建包的常用方法。
指定构建包
指定构建过程中使用的构建包。
在我们开始之前,您需要了解buildpacks的基本知识以及它们是如何工作的。
buildpack的工作是收集应用程序构建和运行所需的所有信息,它通常会快速而安静地完成这项工作。
当平台针对您的应用程序的源代码依次测试buildpacks组时,就会发生自动检测。第一个认为自己适合您的源代码的组将成为应用程序选定的buildpacks。
检测标准对每个buildpack是特定的 -例如,一个Go buildpack可能会寻找Go的源文件。
#### 创建buildpack
这是一个使用简单bash脚本创建云原生构建包的逐步教程。
设置本地环境
构建云原生Buildpack的块
检测应用程序:
下一步,您将需要实际检测您正在构建的应用程序是一个GO应用程序。为了做到这一点,你需要检查一个go.mod文件。
构建应用程序
使应用程序可运行
通过缓存提高性能:
我们可以通过缓存构建之间的依赖关系来提高性能,只在必要时重新下载。
使您的构建包可配置
#### 打包buildpack
了解如何使用标准的OCI注册表打包buildpack以进行发布。
0.获取样本代码仓
1.创建package.toml:
我们需要创造一个package.toml文件,以便告诉pack在何处查找要打包的buildpack的依赖项。
2.指定您的构建包:
让我们从指定要打包的buildpack的位置开始。
3.指定依赖的构建包
4.打包您的构建包
#### 什么是builder?
builder是一个镜像,它打包了有关如何构建应用程序的所有bits和信息,例如buildpacks和build-time镜像,
并针对应用程序源代码执行buildpacks。
#### 构建Go应用程序
在shell中运行以下命令来克隆和构建一个简单的Go应用程序。
```shell
# clone the repo
git clone http://gitlab.ebcpaas.com/buildpacks/samples.git
# go to the app directory
cd samples/apps/cnb-go
# build the app
pack build cnb-go --builder cnbs/sample-builder:bionic \
--buildpack ../../buildpacks/cnb-go
```
注意:这是您第一次为应用程序cnb-go运行pack build,因此您会注意到该生成可能需要比平时更长的时间。后续的构建将利用各种形式的缓存。
展示Terminal日志信息:
```
bionic: Pulling from cnbs/sample-builder
...
Status: Downloaded newer image for cnbs/sample-builder:bionic
bionic: Pulling from cnbs/sample-stack-run
...
Status: Image is up to date for cnbs/sample-stack-run:bionic
===> DETECTING
[detector] samples/cnb-go 0.0.1
===> ANALYZING
[analyzer] Previous image with name "index.docker.io/library/cnb-go:latest" not found
[analyzer] Restoring metadata for "samples/cnb-go:golang" from cache
===> RESTORING
[restorer] Restoring data for "samples/cnb-go:golang" from cache
===> BUILDING
[builder] ---> Golang buildpack
[builder] + env_dir=/platform/env
[builder] + layers_dir=/layers/samples_cnb-go
[builder] + plan_path=/tmp/plan.387032741/samples_cnb-go/plan.toml
[builder] + git_version=2.10.1
[builder] + git_url=https://github.com/git/git/archive/v2.10.1.tar.gz
[builder] + golang_version=1.13
[builder] ---> Installing golang
[builder] + golang_url=http://25.38.21.19:8080/go1.13.linux-amd64.tar.gz
[builder] + compgen -G '/platform/env/*'
[builder] + export PATH=/layers/samples_cnb-go/git/git/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
[builder] + PATH=/layers/samples_cnb-go/git/git/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
[builder] + echo '---> Installing golang'
[builder] + [[ -f /layers/samples_cnb-go/golang.toml ]]
[builder] ++ yj -t
[builder] ++ jq -r .metadata.url
[builder] ++ cat /layers/samples_cnb-go/golang.toml
[builder] + cached_golang_url=http://25.38.21.19:8080/go1.13.linux-amd64.tar.gz
[builder] + [[ http://25.38.21.19:8080/go1.13.linux-amd64.tar.gz != '' ]]
[builder] + rm -rf /layers/samples_cnb-go/golang
[builder] + mkdir -p /layers/samples_cnb-go/golang/gopath
[builder] + wget -qO go.tgz http://25.38.21.19:8080/go1.13.linux-amd64.tar.gz
[builder] + tar -C /layers/samples_cnb-go/golang -xzf go.tgz
[builder] + rm go.tgz
[builder] + ls -la /layers/samples_cnb-go/golang/go/bin/
[builder] total 18196
[builder] drwxr-xr-x 2 cnb cnb 4096 Sep 3 2019 .
[builder] drwxr-xr-x 10 cnb cnb 4096 Sep 3 2019 ..
[builder] -rwxr-xr-x 1 cnb cnb 15075523 Sep 3 2019 go
[builder] -rwxr-xr-x 1 cnb cnb 3543823 Sep 3 2019 gofmt
[builder] + /layers/samples_cnb-go/golang/go/bin/go version
[builder] go version go1.13 linux/amd64
[builder] + echo 'launch = false'
[builder] + echo 'build = true'
[builder] + echo 'cache = true'
[builder] + echo -e '[metadata]\n version = "1.13"\n url = "http://25.38.21.19:8080/go1.13.linux-amd64.tar.gz"'
[builder] + export PATH=/layers/samples_cnb-go/golang/go/bin:/layers/samples_cnb-go/git/git/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
[builder] + PATH=/layers/samples_cnb-go/golang/go/bin:/layers/samples_cnb-go/git/git/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
[builder] + export GOROOT=/layers/samples_cnb-go/golang/go
[builder] + GOROOT=/layers/samples_cnb-go/golang/go
[builder] + ln -s /layers/samples_cnb-go/golang /home/cnb/go
[builder] + export GOPATH=/home/cnb/go
[builder] + GOPATH=/home/cnb/go
[builder] + GOPACKAGENAME=workspace
[builder] + mkdir -p /home/cnb/go/src /home/cnb/go/bin
[builder] + chmod -R 777 /home/cnb/go
[builder] + ln -s /workspace /home/cnb/go/src/workspace
[builder] + env
[builder] HOSTNAME=54425b122133
[builder] CNB_STACK_ID=io.buildpacks.samples.stacks.bionic
[builder] GOPATH=/home/cnb/go
[builder] PWD=/workspace
[builder] HOME=/home/cnb
[builder] GOROOT=/layers/samples_cnb-go/golang/go
[builder] SHLVL=1
[builder] PATH=/layers/samples_cnb-go/golang/go/bin:/layers/samples_cnb-go/git/git/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
[builder] _=/usr/bin/env
[builder] + cd /home/cnb/go/src/workspace
[builder] + go build -o workspace
[builder] + echo 'processes = [{ type = "web", command = "./workspace"}]'
===> EXPORTING
[exporter] Adding layer 'launcher'
[exporter] Adding 1/1 app layer(s)
[exporter] Adding layer 'config'
[exporter] *** Images (edd743e6c495):
[exporter] index.docker.io/library/cnb-go:latest
[exporter] Adding cache layer 'samples/cnb-go:golang'
Successfully built image cnb-go
```
就这样!您的本地Docker守护进程上现在有一个名为cnb-go的可运行应用程序镜像。我们说过这毕竟是一次短暂的旅行。请注意,您的应用程序是在不需要额外安装Go语言包或配置构建环境的情况下构建的。pack和buildpacks帮你搞定了。
要在本地测试您的新应用程序映像,您可以使用Docker运行它:
```shell
docker run --rm -p 8089:8080 cnb-go
```
现在通过浏览器访问localhost:8089查看应用
![](../assets/运维手册-Buildpacks-通过浏览器查看应用.png)
### Buildpacks组件
Buildpacks组件:
Builder
Buildpack
Lifecycle
Stack
#### Builder
Builder由以下组件组成:
Buildpacks
Lifecycle
Stack’s build image
![](../assets/运维手册-Buildpacks-Builder-Image.png)
创建builder
创建自定义builder允许您控制使用哪些buildpacks以及应用程序基于哪些镜像。
0.获取样本代码仓
1.Builder配置
2.创建builder
3.使用你的builder
4.运行app
#### 什么是Buildpack?
buildpack是一个工作单元,它检查你的应用程序源代码并制定一个计划来构建和运行你的应用程序。
它们是将源代码转换为可运行的应用程序镜像的核心。
有两个基本阶段允许buildpack创建可运行镜像:
检测
构建
典型的构buildpack至少由三个文件组成:
buildpack.toml–提供有关buildpack的元数据
bin/detect –确定是否应该应用buildpack
bin/build – 执行buildpack(构建包)逻辑
buildpack.toml
```
# Buildpack API version
api = "0.2"
# Buildpack ID and metadata
[buildpack]
id = "samples/cnb-go"
name = "Sample cnb-go Buildpack"
version = "0.0.1"
# Stacks that the buildpack will work with
[[stacks]]
id = "io.buildpacks.samples.stacks.bionic"
[[stacks]]
id = "io.buildpacks.samples.stacks.alpine"
```
bin/detect
```
#!/usr/bin/env bash
set -eo pipefail
if test -f "go.mod" ||
test -f "main.go" ||
test -f "./vendor"
then
exit 0
fi
exit 1
```
bin/build
```
#!/usr/bin/env bash
echo "---> Golang buildpack"
set -eo pipefail
set -x
env_dir="$2/env"
layers_dir="$1"
plan_path="$3"
git_version="2.10.1"
git_url="https://github.com/git/git/archive/v${git_version}.tar.gz"
golang_version=1.13
#golang_url="https://golang.google.cn/dl/go${golang_version}.linux-amd64.tar.gz"
golang_url="http://25.38.21.19:8080/go1.13.linux-amd64.tar.gz"
# Load user-provided build-time environment variables
if compgen -G "$env_dir/*" > /dev/null; then
for var in "$env_dir"/*; do
declare "$(basename "$var")=$(<"$var")"
done
fi
# echo "---> Installing git"
#
# if [[ -f $layers_dir/git.toml ]]; then
# cached_git_url=$(cat "$layers_dir/git.toml" | yj -t | jq -r .metadata.url 2>/dev/null || echo 'Golang TOML parsing failed')
# fi
# if [[ $git_url != $cache_git_url ]] ; then
# rm -rf "$layers_dir/git"
# mkdir -p "$layers_dir/git"
# wget -qO git.tgz "$git_url";
# tar -C "$layers_dir/git" -xzf git.tgz
# pushd "${layers_dir}/git/git-${git_version}"
# make configure
# ./configure --prefix "${layers_dir}/git"
# make all
# make install
# popd
# rm git.tgz
# ls -la $layers_dir/git
# echo "launch = false" > "$layers_dir"/git.toml
# echo "build = true" >> "$layers_dir"/git.toml
# echo "cache = true" >> "$layers_dir"/git.toml
# echo -e "[metadata]\n version = \"$git_version\"\n url = \"$git_url\"" >> "$layers_dir"/git.toml
# fi