(接上文)

前面的部分介绍了如何为我的博客打包 Docker 镜像,接下来就是重头戏 —— 部署到 Kubernetes。

Google Kubernetes Engine

没错,我现在自用的 Kubernetes 集群就是 Google 家的 GKE。暂时没有遇到什么太大的坑,各方面集成做得不错,适合我这种小白上手。

Helm

有 Kubernetes 当然少不了 Helm。在我的理解中,Helm 除了是一个模板引擎之外,还能把一整个「应用」所需要的 YAML 文件组合为一个整体(Chart),多个 Chart 之间可以互相依赖,组合成为一整个系统;搭配 helm-diffhelm-secret 等插件还可以展示 YAML 文件变更、管理敏感密钥信息等。

首先,我使用 helm create 创建了一个 Chart,并将它直接放在了 deploy/chart 目录。

默认 Chart 内的 YAML 模板基本没有修改,只有 values.yaml 改了一些值:

image:
  repository: wi1dcard/blog

正如你看到的,镜像被修改成了 wi1dcard/blog(上文中推送到 Docker Hub 的镜像)。同时,我启用了 Ingress,还给 Ingress 添加了一些注解:

ingress:
  enabled: true
  annotations:
    kubernetes.io/tls-acme: "true"
    ingress.kubernetes.io/ssl-redirect: "true"
    ingress.kubernetes.io/browser-xss-filter: "true" # X-XSS-Protection: 1; mode=block
    ingress.kubernetes.io/content-type-nosniff: "true" # X-Content-Type-Options: nosniff
    ingress.kubernetes.io/referrer-policy: strict-origin-when-cross-origin # Referrer-Policy: same-origin
    ingress.kubernetes.io/custom-frame-options-value: SAMEORIGIN # X-Frame-Options: SAMEORIGIN
    ingress.kubernetes.io/custom-response-headers: X-Powered-By:Wi1dcard Kubernetes Engine

我使用的是 Traefik Ingress Controller,相比于 Nginx Ingress Controller 给 Nginx 加个中间层来说,Traefik 本身既是 Ingress Controller 又是 Ingress 的具体实现;而且我没有四层协议的需求,性能更是完全无所谓(当然 Traefik 不一定比 Nginx 差),所以直接选择了为「云原生」设计的 Traefik。

这些 Annotations 多数都是我从 Traefik Kubernetes Ingress Docs 抄过来的😂,除了强制跳转 HTTPS 之外,它们还可以让 Traefik Ingress 返回的 HTTP 响应包含特定的「安全头信息(Security Headers)」,对限制浏览器权限、保障用户浏览站点时的安全性有一定作用。

更多有关 Security Headers 的说明,可以参考 https://securityheaders.com/

另外,值得注意的是 kubernetes.io/tls-acme: "true",这条注解是为了配置自动申请证书使用,我使用的是 cert-manager,它能够帮我解决「老大难」的 Let’s Encrypt 证书申请、续期问题。在此不再展开,待有空时另开博客详细说明。

Helmfile

不少人说 Helm 用于管理 Kubernetes 的 YAML,而 Helmfile 就是用来管理 Helm 的 Helm… 😂

算是个比较形象,带有玩笑成分的比喻吧。Helmfile 的功能很多,以我现在的知识量无法一一列出;至少在部署我的博客的过程中,它可以提供以下帮助:

  1. Helm v2 Tillerless(此处注明版本是因为 Helm v3 已经进入 Alpha 阶段,升级后完全取消 Tiller 概念,因此 Tillerless 暂不详述,有兴趣可自行 Google 了解)。
  2. 输出 YAML 文件变更(通过 helm-diff 插件)。
  3. 管理 Helm Chart 仓库,自动保持更新。
  4. 最重要的一点 —— 通过环境变量覆盖特定 Values 的值。

helmfile.yaml 内可以查看到我的 Helmfile 配置,最主要的部分就是 releases 小节:

releases:
  - name: blog # Helm Release 名称
    namespace: blog # 部署到的 Kubernetes 命名空间
    chart: ./chart # Helm Chart 的名称或本地路径
    values: # 覆盖 Chart 的默认 Values
      - image:
          tag: {{ requiredEnv "DOCKER_TAG" }}
        ingress:
          hosts:
            - host: {{ requiredEnv "INGRESS_HOST" }}
              paths: [ / ]
            - host: wi1dcard.dev
              paths: [ / ]
          tls:
            - secretName: ingress-tls
              hosts: [ {{ requiredEnv "INGRESS_HOST" }} ]

与 Helm 类似,Helmfile 会将 helmfile.yaml 作为 Go Template 渲染。如你所见,在以上片段中,我使用了 {% raw %}{{ ... }}{% endraw %} 语法和 requiredEnv 函数。

requiredEnv 是 Helmfile 提供的功能之一,能够获取指定环境变量的值,并参与模板渲染;例如 {% raw %}{{ requiredEnv "INGRESS_HOST" }}{% endraw %} 即为读取 INGRESS_HOST 变量。

配合覆盖 Chart 默认的 Values,便能够实现上文中提到的第「4」点。

持续交付(CD)

这样一来,借助于 Helmfile,我可以在 CI/CD 过程中灵活地控制部署的镜像($DOCKER_TAG)、隐藏 CDN 背后的裸域名($INGRESS_HOST)…

最后一步,CD。在 deploy/deploy.sh 中,我使用了 Helmfile 的官方镜像:

echo "Applying helm releases..."
docker run --rm -v "$PWD/deploy:/deploy" \
    -e HELM_TILLER_STORAGE=configmap \
    -e KUBECONFIG_BASE64="$KUBECONFIG_BASE64" \
    -e DOCKER_TAG="$DOCKER_TAG" \
    -e INGRESS_HOST="$INGRESS_HOST" \
    quay.io/roboll/helmfile:v0.82.0 /deploy/helmfile.sh

这不是规范的做法!若是后期迁移到类似 GitLab CI 等 Dockerized CI,那么只需要声明另一个 Job 即可,而不必自行启动容器。

在容器内,我使用了 deploy/helmfile.sh 脚本;它除了帮我解码 $KUBECONFIG_BASE64 并填到 ~/.kube/config 之外,只有两条命令与真正意义上的「部署」有关:

# 初始化 Helm,但不安装 Tiller。
helm init --client-only
# 部署。
helmfile -f deploy/helmfile.yaml apply --suppress-secrets

没错,到最后部署只剩下一行命令。其中:

  • -f deploy/helmfile.yaml 用于指定 helmfile.yaml 的路径。
  • apply 是 Helmfile 的子命令,直译为「应用」,你可以理解为「部署」。
  • --suppress-secrets 表示隐藏 Secrets,避免密钥内容被直接输出到 CI 日志中。

最终效果

剩下的都交给 Helmfile、Helm、Kubernetes 就好。它们紧密协作,根据你的配置,最终将应用部署到集群中:

$ kubectl get pods -n blog

NAME                    READY   STATUS    RESTARTS   AGE
blog-74d758cb84-w5z9k   1/1     Running   0          21h

你可以在 这里 查看完整的构建和部署日志,以及 历史

以及我的博客:

$ curl -I https://wi1dcard.dev/

HTTP/2 200
date: Thu, 12 Sep 2019 13:05:37 GMT
content-type: text/html
...
x-powered-by: Wi1dcard Kubernetes Engine

😄

结语

首先感谢 Xiangxuan Liu(@nauxliu)在我学习 Kubernetes 过程中的帮助和指导,节省了大量试错成本,少绕了很多弯路,算得上是受益匪浅吧。在公司不好意思说,就还是写在博客里吧… 咳咳。

文中涉及的技术栈均取自(或衍生自)我司现有的 Kubernetes 相关技术,感兴趣的话,简历丢来吧:https://join.rightcapital.com/(虽然暂时不招 DevOps 😂,不过前后端都在招呀)。

最后,Kubernetes 是个非常繁复精美的项目,我最近刚刚入门;本文的目的并不是为大家提供详尽的参考,而是在我漫漫学习长路中的一些随笔记录吧。

当然,新手 ≠ 不追求专业,如文中有任何错误敬请指出,我会尽快修改。