在 Knative 中进行应用程序的构建和部署
原文:Building and deploying applications to Knative
Knative 有三个高级子系统:Serving 用来协调服务 Pod 的自动伸缩以及路由;Build 提供了将代码转换为镜像的工具链;Eventing 则会使用事件的发布订阅来触发松耦合服务。
前一篇文章中我们将一个构件好的容器镜像发布到了 Knative Serving 中。
本篇文章将使用 Knative Build 把我们的应用通过 Dockerfile 以及 Cloud Foundry buildpack 在发布过程中转化为容器镜像。我们还会尝试从本地文件系统以及远程 Git 仓库中进行部署的方式。
前面的操作都很简单,看得出在 Kubernetes 环境中安装 Knative、运行现有容器镜像、使用 curl 与应用进行交互都不难。总结一下就是:
knctl install [--node-ports] [--exclude-monitoring]
knctl deploy --service <service-name> --image <image-name>
knctl curl --service <service-name>
后面的两步 - 把镜像部署为 Kubernetes 并运行,为每个部署进行版本化处理,使用 HTTP 路由进行交互,都是 Serving 子系统的功劳。
Knative 还支持用于构建容器镜像的弹性子系统,构建生成的镜像将会以 Kubernetes Pod 的形式运行。
Knative Build 子系统非常有弹性。我们会探讨集中用例:
- 上传一个本地目录,使用 Dockerfile 进行构建。
- 上传一个本地目录,使用 Buildpack 进行构建。
- 用上面两种方式来构建来自于远程 Git 仓库的代码。
命名空间
前一篇文章中我们在每个 knctl
命令中使用了 --namespace helloworld
参数来显式的指定命名空间。对我来说这样显得更清晰。可能有别的用户希望设置一个缺省命名空间,从而能够缩短命令。
可以给 knctl
配置一个当前命名空间:
kubectl create ns my-simple-app
export KNCTL_NAMESPACE=my-simple-app
所有的 knctl
命令都会使用这一命名空间。
$ knctl service list
Services in namespace 'my-simple-app'
Name Domain Annotations Age
0 services
可以在 kubectl
命令中复用 $KNCTL_NAMESPACE
:
kubectl get pods -n $KNCTL_NAMESPACE
上传一个带有 Dockerfile 的本地目录
在所有 Knative Build 的示例中,都会产生一个副产品——容器镜像。这些镜像必须放在什么地方,例如 Docker Hub、GCP 容器库、Azure 容器库,或者借助 Harbor 之类的软件自建的私库。
我们需要在每个应用所在的 Kubernetes 命名空间中给 Knative 配置一个镜像库,然后 knctl basic-auth-secret create
给 Knative 配置一个 Secret。
以 Docker Hub 为例,使用 --docker-hub
:
knctl basic-auth-secret create -s registry --docker-hub -u <username> -p <password>
GCP 容器仓库可以使用 --gcr
选项:
knctl basic-auth-secret create -s registry --gcr -u <username> -p <password>
其它私库可以使用 --type
和 --url
:
knctl basic-auth-secret create -s registry --type docker --url https://registry.domain.com/ -u <username> -p <password>
下一步把镜像库 Secret 映射到 Kubernetes Service account,它会在 Knative Build 的 Pod 中提供上面的登录信息。
knctl service-account create --service-account build -s registry
这个操作会体现在 Kubernetes 的 Service account 中:
$ kubectl get serviceaccount -n $KNCTL_NAMESPACE
NAME SECRETS AGE
build 2 37s
default 1 3h
这样我们就准备好使用 Knative Build 创建新容器镜像所需的认证凭据了。
Clone 一个 Go 应用作为样例,并从它的本地目录推送到 Docker hub:
git clone https://github.com/cppforlife/simple-app
cd simple-app
DOCKER_IMAGE=index.docker.io/<your hub.docker.com org or user>/knative-simple-app
knctl deploy \
--service simple-app \
--directory=$PWD \
--service-account build \
--image ${DOCKER_IMAGE:?required} \
--env SIMPLE_MSG="Built from local directory using Dockerfile"
容器的显式命名事实上只是从 Build 到 Serve 的工作过程中的一个中间步骤的副产品,但是必须提供。
knctl deploy
的输出大概是这样的:
Name simple-app
Waiting for new revision to be created...
Tagging new revision 'simple-app-00001' as 'latest'
Tagging new revision 'simple-app-00001' as 'previous'
[2018-10-15T13:18:31+10:00] Uploading source code...
[2018-10-15T13:19:59+10:00] Finished uploading source code...
Watching build logs...
build-step-build-and-push | INFO[0000] Downloading base image golang:1.10.1
build-step-build-and-push | ERROR: logging before flag.Parse: E1015 03:20:01.547607 1 metadata.go:142] while reading 'google-dockercfg' metadata: http status code: 404 while fetching url http://metadata.google.internal./computeMetadata/v1/instance/attributes/google-dockercfg
build-step-build-and-push | ERROR: logging before flag.Parse: E1015 03:20:01.550268 1 metadata.go:159] while reading 'google-dockercfg-url' metadata: http status code: 404 while fetching url http://metadata.google.internal./computeMetadata/v1/instance/attributes/google-dockercfg-url
build-step-build-and-push | INFO[0001] Executing 0 build triggers
build-step-build-and-push | INFO[0001] Extracting layer 0
build-step-build-and-push | INFO[0003] Extracting layer 1
build-step-build-and-push | INFO[0004] Extracting layer 2
build-step-build-and-push | INFO[0004] Extracting layer 3
build-step-build-and-push | INFO[0007] Extracting layer 4
build-step-build-and-push | INFO[0010] Extracting layer 5
build-step-build-and-push | INFO[0015] Extracting layer 6
build-step-build-and-push | INFO[0015] Taking snapshot of full filesystem...
build-step-build-and-push | INFO[0027] WORKDIR /go/src/github.com/mchmarny/simple-app/
build-step-build-and-push | INFO[0027] cmd: workdir
build-step-build-and-push | INFO[0027] Changed working directory to /go/src/github.com/mchmarny/simple-app/
build-step-build-and-push | INFO[0027] Creating directory /go/src/github.com/mchmarny/simple-app/
build-step-build-and-push | INFO[0027] COPY . .
build-step-build-and-push | INFO[0027] RUN CGO_ENABLED=0 GOOS=linux go build -v -o app
build-step-build-and-push | INFO[0027] cmd: /bin/sh
build-step-build-and-push | INFO[0027] args: [-c CGO_ENABLED=0 GOOS=linux go build -v -o app]
build-step-build-and-push | net
build-step-build-and-push | vendor/golang_org/x/net/lex/httplex
build-step-build-and-push | vendor/golang_org/x/net/proxy
build-step-build-and-push | net/textproto
build-step-build-and-push | crypto/x509
build-step-build-and-push | crypto/tls
build-step-build-and-push | net/http/httptrace
build-step-build-and-push | net/http
build-step-build-and-push | github.com/mchmarny/simple-app
build-step-build-and-push | INFO[0030] Taking snapshot of full filesystem...
build-step-build-and-push | INFO[0034] Storing source image from stage 0 at path /kaniko/stages/0
build-step-build-and-push | INFO[0038] trying to extract to /kaniko/0
build-step-build-and-push | INFO[0038] Extracting layer 0
build-step-build-and-push | INFO[0040] Extracting layer 1
build-step-build-and-push | INFO[0041] Extracting layer 2
build-step-build-and-push | INFO[0041] Extracting layer 3
build-step-build-and-push | INFO[0043] Extracting layer 4
build-step-build-and-push | INFO[0046] Extracting layer 5
build-step-build-and-push | INFO[0051] Extracting layer 6
build-step-build-and-push | INFO[0051] Extracting layer 7
build-step-build-and-push | INFO[0051] Deleting filesystem...
build-step-build-and-push | INFO[0053] No base image, nothing to extract
build-step-build-and-push | INFO[0053] Taking snapshot of full filesystem...
build-step-build-and-push | INFO[0062] COPY --from=0 /go/src/github.com/mchmarny/simple-app/app .
build-step-build-and-push | INFO[0063] Taking snapshot of files...
build-step-build-and-push | INFO[0063] EXPOSE 8080
build-step-build-and-push | INFO[0063] cmd: EXPOSE
build-step-build-and-push | INFO[0063] Adding exposed port: 8080/tcp
build-step-build-and-push | INFO[0063] ENTRYPOINT ["/app"]
build-step-build-and-push | ERROR: logging before flag.Parse: E1015 03:21:04.751338 1 metadata.go:142] while reading 'google-dockercfg' metadata: http status code: 404 while fetching url http://metadata.google.internal./computeMetadata/v1/instance/attributes/google-dockercfg
build-step-build-and-push | ERROR: logging before flag.Parse: E1015 03:21:04.753927 1 metadata.go:159] while reading 'google-dockercfg-url' metadata: http status code: 404 while fetching url http://metadata.google.internal./computeMetadata/v1/instance/attributes/google-dockercfg-url
build-step-build-and-push | 2018/10/15 03:21:06 pushed blob sha256:72a682eea3309941d5e8e6f993a07ae4d33a413b8b7fa2762f8e969310b5996a
build-step-build-and-push | 2018/10/15 03:21:07 pushed blob sha256:9c24aa788ba416c5e1e631d8af3e3115519ad7ca0f659ac10f40682524c6d9cd
build-step-build-and-push | 2018/10/15 03:21:07 index.docker.io/drnic/knative-simple-app:latest: digest: sha256:b5823ead77d9544998b5bc844f049d1a7dfb0aefe7461b74b3e4f67fb5481fa1 size: 428
nop | Nothing to push
Succeeded
Knative Build 的调试
目前 knctl deploy
没有显示任何来自 Knative Build 系统的内部错误或者警告。只需要看着 Waiting for new revision to be created...
坐享其成就可以了。
一个调试方法就是使用 kail 工具处理来自 Knative Build 子系统的消息:
kail -n knative-build
这样就会看到大量的日志,可以再其中查找错误信息,例如 "msg":"Failed the resource specific validation{error 25 0 serviceaccounts \"build\" not found}"
。
使用 Buildpack 进行构建
我本人很喜欢 Cloud Foundry 和 Heroku 的镜像构建方式,幸运的是,Knative Build 通过自定义构建模板的方式提供了这种支持。
首先用 buildpack
这个名字在活动命名空间中注册一个构建模板:
kubectl -n $KNCTL_NAMESPACE apply -f \
https://raw.githubusercontent.com/knative/build-templates/master/buildpack/buildpack.yaml
加入 --template buildpack
就可以使用这一自定义模板了。构建模板所需的附加环境变量都可以用 --template-env NAME=value
的方式进行植入。
例如 Cloud Foundry Go Buildpack 需要 $GOPACKNAME
(参考文档):
knctl deploy \
--service simple-app \
--directory=$PWD \
--service-account build \
--image ${DOCKER_IMAGE:?required} \
--env SIMPLE_MSG="Built from local directory using Buildpack template" \
--template buildpack \
--template-env GOPACKAGENAME=main
输出内容和 Cloud Foundry buildpack 是一致的:
Name simple-app
Waiting for new revision (after revision 'simple-app-00001') to be created...
Tagging new revision 'simple-app-00002' as 'latest'
Tagging older revision 'simple-app-00001' as 'previous'
[2018-10-15T13:40:41+10:00] Uploading source code...
[2018-10-15T13:42:08+10:00] Finished uploading source code...
Watching build logs...
build-step-build | -----> Go Buildpack version 1.8.26
build-step-build | -----> Installing godep 80
build-step-build | Download [https://buildpacks.cloudfoundry.org/dependencies/godep/godep-v80-linux-x64-cflinuxfs2-06cdb761.tgz]
build-step-build | -----> Installing glide 0.13.1
build-step-build | Download [https://buildpacks.cloudfoundry.org/dependencies/glide/glide-v0.13.1-linux-x64-cflinuxfs2-aab48c6b.tgz]
build-step-build | -----> Installing dep 0.5.0
build-step-build | Download [https://buildpacks.cloudfoundry.org/dependencies/dep/dep-v0.5.0-linux-x64-cflinuxfs2-52c14116.tgz]
build-step-build | -----> Installing go 1.8.7
build-step-build | Download [https://buildpacks.cloudfoundry.org/dependencies/go/go1.8.7.linux-amd64-cflinuxfs2-fff10274.tar.gz]
build-step-build | **WARNING** Installing package '.' (default)
build-step-build | -----> Running: go install -tags cloudfoundry -buildmode pie .
build-step-export | 2018/10/15 03:47:58 mounted blob: sha256:1124eb40dd68654b8ca8f5d9ec7e439988a4be752a58c8f4e06d60ab1589abdb
build-step-export | 2018/10/15 03:47:58 mounted blob: sha256:6be38da025345ffb57d1ddfcdc5a2bc052be5b9491825f648b49913d51e41acb
build-step-export | 2018/10/15 03:47:58 mounted blob: sha256:a5733e6358eec8957e81b1eb93d48ef94d649d65c69a6b1ac49f616a34a74ac1
build-step-export | 2018/10/15 03:47:58 mounted blob: sha256:21324a9f04e76c93078f3a782e3198d2dded46e4ec77958ddd64f701aecb69c0
build-step-export | 2018/10/15 03:47:59 pushed blob sha256:efa2d34b82bc07588a1a8fd4526322257408109547ee089a792b3f51c383f8e6
build-step-export | 2018/10/15 03:47:59 pushed blob sha256:d495696b33936c79216ec8178726b9fbe915fafbffdd0911a7fdabce4297d9a4
build-step-export | 2018/10/15 03:48:00 index.docker.io/drnic/knative-simple-app:latest: digest: sha256:e5ef1d4d255b4bcbb38d4b43bb6302423c33e6eeabd0e20d5fda4e5ce4c46668 size: 1082
nop | Nothing to push
现在就能看到应用已经部署成功:
$ knctl curl -s simple-app
<h1>Built from local directory using Buildpack template</h1>
私有 Git Secret
前面两节我们从本地上传了源码然后构建了 Docker 镜像(使用 Dockerfile 或 Cloud Foundry buildpack),最后运行应用。
Knative 还能从 Git 仓库获取源码(正式的说法是,Knative Build 只支持从 Git 仓库获取源码,本地代码的支持是 knctl
提供的)。
让 Knative Build 获取 Git 仓库中的代码,需要用 --git-url
和 --git-revision
来取代 --directory=$PWD
。
如果你的 Git 仓库是私有的,那就还需要在 Service account(在上面的例子中的 build
)里包含 Git ssh 凭据。knctl ssh-auth-secret create
能够协助用户创建一个 kubernetes.io/ssh-auth secret。
$ knctl ssh-auth-secret create --secret git --github --private-key "$(cat ~/.ssh/id_rsa)"
Name git
Type kubernetes.io/ssh-auth
$ kubectl get secrets -n $KNCTL_NAMESPACE
NAME TYPE DATA AGE
...
git kubernetes.io/ssh-auth 1 5m
registry kubernetes.io/basic-auth 2 3h
现在需要把 git
secret 加入到我们的 build
Service account 之中了。
在成文之时,knctl
还没有提供 knctl serviceaccounts update
这样的命令,所以需要删除重新创建:
kubectl delete serviceaccounts -n $KNCTL_NAMESPACE build
knctl service-account create --service-account build -s registry -s git
从 Git 部署
用 --git-url
和 --git-revision
替代 --directory
来进行 Git 部署:
knctl deploy \
--service simple-app \
--git-url git@github.com:cppforlife/simple-app.git \
--git-revision master \
--service-account build \
--image ${DOCKER_IMAGE:?required} \
--env SIMPLE_MSG="Built from Git repo using Buildpack template" \
--template buildpack \
--template-env GOPACKAGENAME=main
总结
knctl deploy
命令在 Knative 的基础上提供了创建新镜像的良好体验,可以从本地目录或者 Git 仓库开始,使用 Dockerfile 或 Cloud Foundry buildpack 进行构建,并支持不同的镜像仓库。