构建(Build)镜像的相关概念
运行在OpenShift上应用最终必须是以容器的形式运行。在传统方式中,应用容器镜像必须由用户配置Dockerfile,然后运行docker build后才能得到。为了简化这种基于Dockerfile构建镜像的过程,OpenShift提出了构建(即Build)的概念,用它和相关对象来定义、管理镜像构建。这其中就包括非常易于使用的Source-2-Image(简称S2I)的工具,它可以根据最佳实践,直接从应用源码构建出应用容器镜像,因此S2I功能成为OpenShift优于Kubernetes的一大功能。
为了运行构建过程和管理构建,OpenShift使用了两种内部对象:BuildConfig和Build。
BuildConfig
BuildConfig是用来定义构建策略的对象,即定义做什么以及怎么做。BuildConfig支持以下四种构建策略:
1. Dockerfile – 即直接使用Dockerfile构建出镜像。
2. S2I – 使用S2I工具从应用源码或应用包构建出镜像。S2I的基础镜像一般也称为Builder Image,OpenShift为目前主流的开发语言(例如Java、PHP、GO、NodeJS、Python、Ruby、HTML等)都提供了对应的Builder Image。S2I会根据要部署应用源码的特点自动找到对应的Builder Image,然后将其和应用源码构建成目标容器镜像。
3. Pipeline – 使用Jenkins或Tekon(Kubernetes原生的CI/CD功能,在OpenShift 4已经是默认CI/CD环境)的Pipeline构建出镜像。
4. Custom – 可以定义特定的Builder Image,而不是用缺省的Builder Image。
在OpenShift中除了可以使用YAML定义BuildConfig外还可以使用”oc new-build”命令创建一个构建的配置。
Build
还记得我们在《OpenShift 4 Hands-on Lab (1) – 部署应用》中的“部署应用代码”一节中,在使用S2I命令后用查看build的日志么?Build是用来记录每次构建运行情况的对象,每运行一次构建,OpenShift都会生成一个Build对象与之对应。在OpenShift中用以下命令可以对Build过程进行操作:
1. oc start-build:启动一次构建过程。
2. oc cancel-build:暂时停止一个构建运行。
OpenShift是在一个专门的Pod中运行构建过程的。当构建过程完成后,该Pod即变成Completed的状态。其实,当我们查看Build日志的时候,看的就是这个Pod的日志。
Builder Image
运行一次构建过程的输入是BuildConfig和原始的基础镜像(即Builder Image),构建过程的输出是结果镜像。OpenShift缺省为主要的语言开发的应用提供了Builder Image,同时用户还可根据需要定制自己的Builder Image。
Build Trigger和Build Chain
我们除了可以使用上述命令手动触发运行Build外,Build还支持基于事件自动触发构建过程。OpenShift支持以下三种构建触发器(Build Trigger):
1. Webhook:将构建对应的Webhook地址注册到Code Repository上,例如最常用的Github。当Github上的代码发生变化后,Github可以使用Webhook从远程触发启动一次构建过程。
2. Builder Image变化:当一个Builder Image发生变化后,OpenShift可以触发构建过程,以便更新所有依赖这个Builder Image的镜像。
3. BuildConfig:当BuildConfig被创建后,OpenShift自动触发一次构建过程,从而完成S2I过程。
在OpenShift中,由Build Trigger实现的当基础镜像变化后自动触发构建依赖它的镜像,于是在这两个镜像之间就形成了Build Chain(构建链)的关系。
触发Build,创建或更新镜像
由于在Pipeline中除了能实现Build外还可实现其它各种CI/CD功能,因此我们放在下面章节单独Pipeline介绍,而在本节我们先介绍如何配置Webhook和Build Trigger。
基于Webhook的Build Trigger
在开始之前需要确保在Github上有一个账户“**MY_GITHUB**”。
1. 将复制到自己的Github账号中。
2. 在OpenShift中新建一个项目webhook-USER-ID。
$ oc new-project USER-IDwebhook
3. 执行以下命令,创建基于PHP的php-helloworld应用
$ oc new-app php~
4. 查看buildconifg,找到最下面,确认基于该buildconfig中只有一个build,即php-helloworld-1。然后复制“Webhook GitHub”的URL后面地址,我们把这个地址称为“**WEBHOOK_URL**”。
$ oc describe bc/php-helloworldName: php-helloworldNamespace: user-1-webhookCreated: 38 seconds agoLabels: app=php-helloworld app.kubernetes.io/component=php-helloworld app.kubernetes.io/instance=php-helloworldAnnotations: openshift.io/generated-by=OpenShiftNewAppLatest Version: 1 Strategy: SourceURL: From Image: ImageStreamTag openshift/php:7.2Output to: ImageStreamTag php-helloworld:latest Build Run Policy: SerialTriggered by: Config, ImageChangeWebhook GitHub: URL: Webhook Generic: URL: AllowEnv: falseBuilds History Limit: Successful: 5 Failed: 5 Build Status Duration Creation Timephp-helloworld-1 running running for 38s 2020-02-11 15:36:59 +0000 UTC Events: <none>
5. 执行命令查看buildconfig的配置,找到“triggers”部分中的“github”,然后用“secret”后面的字符串替换“**WEBHOOK_URL**”字符串中的“**\<secret>**”区域。
$ oc get bc/php-helloworld -o yaml。。。 triggers: -- github: secret: bhZIQnufyQZ3246OhM9Q type: GitHub -- generic: secret: -4UndTQhFrASVoqgNNUt type: Generic -- type: ConfigChange -- imageChange: lastTriggeredImageID: image-registry.openshift-image-registry.svc:5000/openshift/php@sha256:a5aaaae5baf98cb674ac2352429e0450591b45d3674e44c516612a9ee67282d5 type: ImageChange
6. 执行命令查看build,确认php-helloworld-1的“STATUS”是已经“Complate”状态。
$ oc get buildNAME TYPE FROM STATUS STARTED DURATIONphp-helloworld-1 Source Git@8cb7578 Complete About an hour ago 1m49s
7. 执行命令查看pod,可以看到除了一个正在运行应用的Pod外,专门负责Build的Pod(php-helloworld-1-build)已经是Completed状态。
$ oc get podNAME READY STATUS RESTARTS AGEphp-helloworld-1-build 0/1 Completed 0 9hphp-helloworld-1-deploy 0/1 Completed 0 9hphp-helloworld-2-z4r4l 1/1 Running 1 7h53m
8. 生成route,然后访问页面。
$ oc expose svc/php-helloworldroute.route.openshift.io/php-helloworld exposed$ curl get route php-helloworld -o template='{{.spec.host}}')
9. 进入github的php-helloworld的repository,点击“Setting”标签。
10. 在设置中进入Webhooks,然后点击“Add webhook”按钮。将**WEBHOOK_URL**复制到Payload URL中,并选择Content type为application/json,然后将SSL verification选为Disable,最后点击Add webhook按钮。可以看到这个webhook显示为下图“绿色”状态。
11. 在github上修改php-helloworld的index.php页面,然后提交修改。提交后,github会通过webhook在OpenShift上触发一次新的build运行。
12. 执行以下命令,确认php-helloworld-2的“STATUS”为“Complete”状态。
$ oc get buildNAME TYPE FROM STATUS STARTED DURATIONphp-helloworld-1 Source Git@8cb7578 Complete About an hour ago 1m32sphp-helloworld-2 Source Git@e78c498 Complete About an hour ago 1m18s
13. 再次访问route,确认页面已经更新。
$ curl get route php-helloworld -o template='{{.spec.host}}')
14. 查看名为php-helloworld-2的build,确认“Build trigger cause”是“GitHub WebHook”。
$ oc describe build/php-helloworld-2。。Build trigger cause: GitHub WebHookCommit: e78c498 (change)Author/Committer: liuxiaoyu-git / GitHub
利用Build Trigger自动更新Image
设想你的容器镜像是多层的(如下图),其实每个相邻的下层镜像都是上层镜像的基础镜像(即Builder Image)。那么当下层的镜像发生更新后,依赖它的上层镜像需要重新构建才能使得整个镜像是最新状态,这种自动化触发构建的过程需要使用OpenShift中Builder Image类型的Build Trigger实现。
1. 新建一个项目,然后执行以下命令,基于一个Docker新建一个Build。**注意**:我们可以从返回结果看到当运行new-build命令后,OpenShift会自动创建BuildConfig,然后在以此运行一次构建过程。在构建过程中,OpenShift先根据识别出此次构建的Buider Image是名为busybox的镜像,通过“Every time “busybox:latest” changes a new build will be triggered”的说明我们可以知道OpenShift会自动在构建的Builder Image和目标镜像之间建立起Build Trigger以实现自动更新。另外,OpenShift还会创建Buider Image(即busybox)和构建的输出镜像(即ops)对应的ImageStream对象。
$ oc new-project USER-ID-build-trigger$ oc new-build --name=ops --context-dir=sh--> Found container image 6d5fcfe (6 weeks old) from docker.io for "docker.io/busybox" * An image stream tag will be created as "busybox:latest" that will track the source image * A Docker build using source code from will be created * The resulting image will be pushed to image stream tag "ops:latest" * Every time "busybox:latest" changes a new build will be triggered --> Creating resources with label build=ops ... imagestream.image.openshift.io "busybox" created imagestream.image.openshift.io "ops" created buildconfig.build.openshift.io "ops" created--> Success
2. 执行命令查看由Builder Trigger形成的和名为busybox的ImageStream相关的build-chain(构建链)。下面说明当”busybox:latest”镜像变化后会触发“bc/ops”来更新镜像“ops:latest”。
$ oc adm build-chain busyboxistag/busybox:latest bc/ops istag/ops:latest
3. 执行以下命令基于另一个Docker新建另一个名为foo的Build。
$ oc new-build --name=foo --context-dir=foo
4. 查看BuildConfig和Build的情况,当前运行了2个构建,它们分别是根据对应的BuildConfig运行的。
$ oc get bcNAME TYPE FROM LATESTfoo Docker Git 1ops Docker Git 1$ oc get buildNAME TYPE FROM STATUS STARTED DURATIONops-1 Docker Git@359ea3d Complete 2 minutes ago 27sfoo-1 Docker Git@359ea3d Complete About a minute ago 23s
5. 执行命令查看由Builder Trigger形成的和名为busybox的ImageStream相关的build-chain。和上一次查看相比,在“ops:latest”镜像和“foo:latest”镜像之间又形成了新的build-chain关系。
$ oc adm build-chain busybox -n build-triggeristag/busybox:latest bc/ops istag/ops:latest bc/foo istag/foo:latest
6. 执行以下命令分别查看和名为ops、foo的ImageStream相关的build-chain。
$ oc adm build-chain ops -n build-triggeristag/ops:latest bc/foo istag/foo:latest$ oc adm build-chain foo -n build-triggeristag/foo:latest
7. 查看BuildConfig中的Trigger。可以看到每个BuildConfig包括4种Trigger,其中目前只有“TYPE”为“image”的有对应的Trigger。
$ oc set triggers bc/foo -n build-triggerNAME TYPE VALUE AUTObuildconfigs/foo config truebuildconfigs/foo image ops:latest truebuildconfigs/foo webhook <secret>buildconfigs/foo github <secret>$ oc set triggers bc/ops -n build-triggerNAME TYPE VALUE AUTObuildconfigs/ops config truebuildconfigs/ops image busybox:latest truebuildconfigs/ops webhook <secret>buildconfigs/ops github <secret>
8. 针对ops,再运行一次新的build。
$ oc start-build bc/opsbuild.build.openshift.io/ops-2 started
9. 执行以下命令会发现有4个构建的记录,这是因为在ops-2完成构建后会根据build-chain自动触发执行foo-2的构建。
$ oc get build -wNAME TYPE FROM STATUS STARTED DURATIONops-1 Docker Git@359ea3d Complete 12 minutes ago 27sfoo-1 Docker Git@359ea3d Complete 11 minutes ago 23sops-2 Docker Git@359ea3d Complete 4 minutes ago 27sfoo-2 Docker Git@359ea3d Complete 3 minutes ago 23s
10. 执行以下命令,删除build-chain在foo和ops之间的Trigger。
$ oc set triggers bc/foo --from-image=ops:latest --removebuildconfig.build.openshift.io/foo triggers updated
11. 再次查看Build Chain,发现ops:latest后不再有foo的BuildConfig过程
oc adm build-chain busyboxistag/busybox:latest bc/ops istag/ops:latest
12. 再次新运行一次ops的build,然后查看确认只有5个build记录。这是因为我们在上一步删除foo和ops之间的Build Trigger,因此新构建ops-3不会自动触发foo-3的构建了。
$ oc start-build bc/opsbuild.build.openshift.io/ops-3 started$ oc get buildNAME TYPE FROM STATUS STARTED DURATIONops-1 Docker Git@359ea3d Complete 29 minutes ago 27sfoo-1 Docker Git@359ea3d Complete 27 minutes ago 23sops-2 Docker Git@359ea3d Complete 20 minutes ago 27sfoo-2 Docker Git@359ea3d Complete 20 minutes ago 23sops-3 Docker Git@359ea3d Complete 47 seconds ago 27s
13. 执行以下命令,重新恢复foo和ops之间的Build Chain。
$ oc set triggers bc/foo --from-image=ops:latest --remove=falsebuildconfig.build.openshift.io/foo triggers updated
14. 再次新运行一次ops的build,然后查看确认有8个build记录。不但有ops-4和foo-4,还有上面因为Build Chain断掉后缺少的foo-3。
$ oc start-build bc/opsbuild.build.openshift.io/ops-4 started$ oc get buildNAME TYPE FROM STATUS STARTED DURATIONops-1 Docker Git@359ea3d Complete 43 minutes ago 27sfoo-1 Docker Git@359ea3d Complete 42 minutes ago 23sops-2 Docker Git@359ea3d Complete 35 minutes ago 27sfoo-2 Docker Git@359ea3d Complete 34 minutes ago 23sops-3 Docker Git@359ea3d Complete 15 minutes ago 27sfoo-3 Docker Git@359ea3d Complete 9 minutes ago 19sops-4 Docker Git@359ea3d Complete About a minute ago 28sfoo-4 Docker Git@359ea3d Complete 51 seconds ago 24s