## 使用Buildpacks构建Docker镜像


### Buildpacks简介
与Dockerfile相比,Buildpacks为构建应用程序提供了更高层次的抽象。具体来说,Buildpacks:
* 提供一个平衡的控制,减少开发人员的操作负担,并支持企业运营商在规模上管理应用程序。
* 确保应用程序满足安全性和法规遵从性要求,而无需开发人员干预。
* 提供操作系统级和应用程序级依赖项升级的自动交付,有效地处理第二天的应用程序操作,这些操作通常很难用Dockerfile进行管理。
* 依赖兼容性保证安全地应用补丁,而不必重建工件,也不必意外地更改应用程序行为。
Buildpacks是可插入的、模块化的工具,通过提供比Dockerfile更高级别的抽象,将源代码转换为容器就绪的构件。通过这样做,他们提供了一种控制的平衡,最小化了最初的生产时间,减少了开发者的操作负担,并支持大规模管理应用程序的企业运营商。
Cloud Native Buildpacks
2018年10月云原生Buildpacks项目加入CNCF沙箱
CNB流程分为四个步骤,每个步骤都有各自的重要目标,最终产出就是 OCI 镜像。CNB让开发和运维人员能够把创建各种软件的过程中所需的构建、补丁和重新打包的工作自动化成适合机器执行的重复任务。


检测:对源码以及其它内容进行检测,查找与其匹配的可用 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
export PATH=layers_dir/git/git/bin:PATH
echo "---> Installing golang"
if [[ -f $layers_dir/golang.toml ]]; then
cached_golang_url=(cat "layers_dir/golang.toml" | yj -t | jq -r .metadata.url 2>/dev/null || echo 'Golang TOML parsing failed')
fi
if [[ golang_url != cache_golang_url ]] ; then
rm -rf "$layers_dir/golang"
mkdir -p "$layers_dir/golang/gopath"
wget -qO go.tgz "$golang_url";
tar -C "$layers_dir/golang" -xzf go.tgz
rm go.tgz
ls -la $layers_dir/golang/go/bin/
"$layers_dir"/golang/go/bin/go version
echo "launch = false" > "$layers_dir"/golang.toml
echo "build = true" >> "$layers_dir"/golang.toml
echo "cache = true" >> "$layers_dir"/golang.toml
echo -e "[metadata]\n version = \"golang_version\\"\\n url = \\"golang_url\"" >> "$layers_dir"/golang.toml
fi
export PATH=layers_dir/golang/go/bin:PATH
export GOROOT=$layers_dir/golang/go
ln -s layers_dir/golang HOME/go
export GOPATH="$HOME/go"
GOPACKAGENAME=${PWD##*/}
mkdir -p "GOPATH/src" "GOPATH/bin" && chmod -R 777 "$GOPATH"
ln -s "PWD" "GOPATH/src/$GOPACKAGENAME"
env
cd "GOPATH/src/GOPACKAGENAME"
go build -o $GOPACKAGENAME
#go_binary=${PWD##*/}
echo "processes = [{ type = \"web\", command = \"./GOPACKAGENAME\\"}\]" \> "layers_dir/launch.toml"
```
#### Lifecycle
lifecycle安排buildpack的执行,后将生成的构件组装到最终的应用程序镜像中。
阶段
检测:查找要在构建阶段使用的有序buildpacks构建包组。
分析:可用于优化构建和导出阶段的buildpacks构建包还原文件。
构建:将应用程序源代码转换为可运行的构件,这些构件可以打包到容器中。
导出:创建最终的OCI镜像。
#### 什么是Stack?
Stack以镜像的形式为buildpack生命周期提供构建时和运行时环境。
Stacks的使用
栈由builders使用,并通过builders的配置文件进行配置:
```
\[buildpacks\]
...
\[order\]
...
stack
id = "com.example.stack"
build-image = "example/build"
run-image = "example/run"
run-image-mirrors = ["gcr.io/example/run", "registry.example.com/example/run"]
```
通过提供所需的[stack]部分,builder作者可以配置stack的ID、构建映像和运行映像(包括任何镜像)。
### Buildpacks操作
Build
Rebase
#### Build构建
构建说明


构建是对应用程序的源代码执行一个或多个buildpacks构建包以生成可运行的OCI镜像的过程。每个构建包都检查源代码并提供相关的依赖项。然后从应用程序的源代码和这些依赖项生成一个镜像。
Buildpack与一个或多个stacks兼容。stack指定构建镜像和运行镜像。在构建过程中,stacks的构建镜像成为执行Buildpack的环境,其运行镜像成为最终应用程序镜像的基础。
Buildpack可以与特定stack的构建镜像绑定,从而生成builder镜像(请注意"er"结尾)。builder为给定stack发布构建包提供了最方便的方法。
#### Rebase变基
Rebase允许应用程序开发或运维人员在应用程序stack的运行镜像发生更改时快速更新应用程序映像。通过使用image层变基,此命令避免了完全重建应用程序的需要。

从根本上讲,image变基是一个简单的过程。通过检查应用程序镜像,rebase可以确定应用程序的基本镜像是否存在较新版本(本地或注册表中)。如果是,rebase将更新应用程序image的层元数据以引用较新的基本image版本。
示例:变基应用程序image
考虑一个应用程序image my-app:my-tag最初是使用默认builder生成的。该builder栈有一个名为pack/run的运行镜像。运行以下命令将更新带最新版本pack/run的my-app:my-tag基础镜像 。
```shell
pack rebase my-app:my-tag
```
https://github.com/paketo-buildpacks/go-mod/blob/master/buildpack.toml
https://paketo.io/docs/create-paketo-buildpack/
Go buildpack | Cloud Foundry Docs