如何使用CircleCI自动部署到DigitalOceanKubernetes

来自菜鸟教程
跳转至:导航、​搜索

作为 Write for DOnations 计划的一部分,作者选择了 Tech Education Fund 来接受捐赠。

介绍

具有自动化部署过程是可扩展且有弹性的应用程序的要求,GitOps 或 Git-based DevOps 已迅速成为使用 Git 组织 CI/CD 的流行方法存储库作为“单一事实来源”。 CircleCI 等工具与您的 GitHub 存储库集成,允许您在每次更改存储库时自动测试和部署代码。 当这种 CI/CD 与 Kubernetes 基础架构的灵活性相结合时,您可以构建一个可以根据不断变化的需求轻松扩展的应用程序。

在本文中,您将使用 CircleCI 将示例应用程序部署到 DigitalOcean Kubernetes (DOKS) 集群。 阅读本教程后,您将能够应用这些相同的技术来部署其他可构建为 Docker 映像的 CI/CD 工具。

先决条件

要遵循本教程,您需要具备:

在本教程中,您将使用 Kubernetes 版本 1.22.7 和 kubectl 版本 1.23.5。

第 1 步 — 创建您的 DigitalOcean Kubernetes 集群

注意: 如果您已经有一个正在运行的 DigitalOcean Kubernetes 集群,您可以跳过此部分。


在第一步中,您将创建 DigitalOcean Kubernetes (DOKS) 集群,您将从中部署示例应用程序。 从本地机器执行的 kubectl 命令将直接从 Kubernetes 集群更改或检索信息。

转到您 DigitalOcean 帐户上的 Kubernetes 页面。

点击【X6X】创建Kubernetes集群【X37X】,或者点击页面右上角绿色的【X63X】创建【X73X】按钮,在下拉菜单中选择【X126X】Kubernetes【X140X】。

下一页是您要指定集群详细信息的地方。 在 选择 Kubernetes 版本 选择版本 1.22.7-do.0。 如果此版本不可用,请选择最新的推荐版本。

对于 Choose a datacenter region,选择离您最近的区域。 本教程将使用 San Francisco。

然后,您可以选择构建 节点池 。 在 Kubernetes 上,节点是工作机器,其中包含运行 pod 所需的服务。 在 DigitalOcean 上,每个节点都是一个 Droplet。 您的节点池将由单个 Basic 节点 组成。 选择1GB/1vCPU配置,节点数改为1 Node。

如果需要,您可以添加额外的标签; 如果您打算使用 DigitalOcean API 或只是为了更好地组织节点池,这将很有用。

在 上选择一个名称 ,对于本教程,使用 kubernetes-deployment-tutorial。 这将使您在阅读下一部分时更容易理解。 最后,点击绿色的 Create Cluster 按钮来创建你的集群。 等到集群创建完成。

创建集群后,会有连接到集群的说明。 按照 Automated (recommended) 选项卡上的说明进行操作,或下载 Manual 选项卡下的 kubeconfig 文件。 这是您将用于验证您要针对集群运行的 kubectl 命令的文件。 将其下载到您的 kubectl 机器上。

使用该文件的默认方法是始终在使用 kubectl 运行的所有命令上传递 --kubeconfig 标志和指向它的路径。 例如,如果您将配置文件下载到 Desktop,您将像这样运行 kubectl get pods 命令:

kubectl --kubeconfig ~/Desktop/kubernetes-deployment-tutorial-kubeconfig.yaml get pods

这将产生以下输出:

OutputNo resources found.

这意味着您访问了您的集群。 No resources found. 消息是正确的,因为您的集群上没有任何 pod。

如果您不维护任何其他 Kubernetes 集群,您可以将 kubeconfig 文件复制到主目录中名为 .kube 的文件夹中。 如果该目录不存在,请创建该目录:

mkdir -p ~/.kube

然后将配置文件复制到新创建的.kube目录并重命名为config:

cp current_kubernetes-deployment-tutorial-kubeconfig.yaml_file_path ~/.kube/config

配置文件现在应该有路径 ~/.kube/config。 这是kubectl在运行任何命令时默认读取的文件,所以不再需要传递--kubeconfig。 运行以下命令:

kubectl get pods

您将收到以下输出:

OutputNo resources found in default namespace.

现在使用以下命令访问集群:

kubectl get nodes

您将收到集群上的节点列表。 输出将与此类似:

OutputNAME                   STATUS   ROLES    AGE   VERSION
pool-upkissrv3-uzm8z   Ready    <none>   12m   v1.22.7

在本教程中,您将为所有 kubectl 命令和 清单文件 使用 default 命名空间,这些文件定义了 Kubernetes 中工作的工作负载和操作参数。 Namespaces 就像您的单个物理集群中的虚拟集群。 您可以更改为您想要的任何其他命名空间; 只需确保始终使用 --namespace 标志将其传递给 kubectl,和/或在 Kubernetes 清单元数据字段中指定它。 它们是组织团队部署及其运行环境的好方法; 在 official Kubernetes overview on Namespaces 中阅读有关它们的更多信息。

通过完成此步骤,您现在可以针对您的集群运行 kubectl。 在下一步中,您将创建将用于存放示例应用程序的本地 Git 存储库。

第 2 步 — 创建本地 Git 存储库

您现在将在本地 Git 存储库中构建示例部署。 您还将创建一些 Kubernetes 清单,这些清单对于您将在集群上执行的所有部署都是全局的。

注意: 本教程已经在 Ubuntu 20.04 上进行了测试,并且各个命令的样式都与此操作系统匹配。 但是,这里的大多数命令都可以应用于其他 Linux 发行版,几乎不需要更改,并且像 kubectl 这样的命令与平台无关。


首先,在本地创建一个新的 Git 存储库,稍后您将推送到 GitHub。 在您的主目录中创建一个名为 do-sample-app 的空文件夹,然后将 cd 放入其中:

mkdir ~/do-sample-app
cd ~/do-sample-app

现在使用以下命令在此文件夹中创建一个新的 Git 存储库:

git init .

在此存储库中,创建一个名为 kube 的空文件夹:

mkdir ~/do-sample-app/kube/

这将是您要存储与您将部署到集群的示例应用程序相关的 Kubernetes 资源清单的位置。

现在,创建另一个名为 kube-general 的文件夹,但这一次是在您刚刚创建的 Git 存储库之外。 把它放在你的主目录中:

mkdir ~/kube-general/

此文件夹位于您的 Git 存储库之外,因为它将用于存储不特定于集群上的单个部署但对多个部署通用的清单。 这将允许您将这些通用清单重用于不同的部署。

创建好文件夹并准备好示例应用程序的 Git 存储库后,就可以安排 DOKS 集群的身份验证和授权了。

第 3 步 - 创建服务帐户

通常不建议使用默认的 admin 用户从其他 Services 验证到您的 Kubernetes 集群。 如果您在外部提供商上的密钥被泄露,您的整个集群就会被泄露。

相反,您将使用具有特定角色的单个 服务帐户 ,这是 RBAC Kubernetes 授权模型 的所有部分。

此授权模型基于 Roles 和 Resources。 您首先创建一个 服务帐户 ,它基本上是集群上的用户,然后创建一个角色,在其中指定它可以访问集群上的哪些资源。 最后,创建一个 Role Binding,用于在 Role 和先前创建的 Service Account 之间建立连接,授予 Service Account 访问 Role 有权访问的所有资源的权限。

您要创建的第一个 Kubernetes 资源是 CI/CD 用户的服务帐户,本教程将其命名为 cicd。

在 ~/kube-general 文件夹中创建文件 cicd-service-account.yml,然后使用您喜欢的文本编辑器打开它:

nano ~/kube-general/cicd-service-account.yml

在上面写下以下内容:

~/kube-general/cicd-service-account.yml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: cicd
  namespace: default

这是一个 YAML 文件; 所有 Kubernetes 资源都使用一个来表示。 在这种情况下,您说此资源来自 Kubernetes API 版本 v1(内部 kubectl 通过调用 Kubernetes HTTP API 创建资源),它是 ServiceAccount。

metadata 字段用于添加有关此资源的更多信息。 在这种情况下,您给这个 ServiceAccount 命名为 cicd,并在 default 命名空间上创建它。

您现在可以通过运行 kubectl apply 在集群上创建此服务帐户,如下所示:

kubectl apply -f ~/kube-general/

您将收到类似于以下内容的输出:

Outputserviceaccount/cicd created

要确保您的服务帐户正常工作,请尝试使用它登录到您的集群。 为此,您首先需要获取它们各自的访问令牌并将其存储在环境变量中。 每个服务帐户都有一个访问令牌,Kubernetes 将其存储为 Secret。

您可以使用以下命令检索此密钥:

TOKEN=$(kubectl get secret $(kubectl get secret | grep cicd-token | awk '{print $1}') -o jsonpath='{.data.token}' | base64 --decode)

关于这个命令在做什么的一些解释:

$(kubectl get secret | grep cicd-token | awk '{print $1}')

这用于检索与我们的 cicd 服务帐户相关的机密名称。 kubectl get secret 返回默认命名空间上的机密列表,然后您使用 grep 搜索与您的 cicd 服务帐户相关的行。 然后返回名称,因为它是从 grep 返回的单行中的第一件事。

kubectl get secret preceding-command -o jsonpath='{.data.token}' | base64 --decode

这将仅检索您的服务帐户令牌的密钥。 然后使用 jsonpath 访问令牌字段,并将结果传递给 base64 --decode。 这是必要的,因为令牌存储为 Base64 字符串。 令牌本身是一个 JSON Web 令牌。

您现在可以尝试使用 cicd 服务帐户检索您的 pod。 运行以下命令,将 server-from-kubeconfig-file 替换为可以在 ~/.kube/config 中的 server: 之后找到的服务器 URL(您为集群下载的配置文件)。 此命令将给出一个特定的错误,您将在本教程的后面部分了解该错误:

kubectl --insecure-skip-tls-verify --kubeconfig="/dev/null" --server=server-from-kubeconfig-file --token=$TOKEN get pods

--insecure-skip-tls-verify 跳过验证服务器证书的步骤,因为您只是测试,不需要验证。 --kubeconfig="/dev/null" 是为了确保 kubectl 不会读取您的配置文件和凭据,而是使用提供的令牌。

输出应该与此类似:

OutputError from server (Forbidden): pods is forbidden: User "system:serviceaccount:default:cicd" cannot list resource "pods" in API group "" in the namespace "default"

这是一个错误,但它向我们表明该令牌有效。 您收到的错误是关于您的服务帐户没有列出资源 secrets 的必要授权,但您能够访问服务器本身。 如果您的令牌不起作用,则错误将是以下错误:

Outputerror: You must be logged in to the server (Unauthorized)

现在身份验证成功,下一步是修复服务帐户的授权错误。 为此,您将创建一个具有必要权限的角色并将其绑定到您的服务帐户。

第 4 步 - 创建角色和角色绑定

Kubernetes 有两种定义角色的方法:使用 Role 或 ClusterRole 资源。 前者和后者的区别在于第一个适用于单个命名空间,而另一个适用于整个集群。

当您在本教程中使用单个命名空间时,您将使用 Role。

创建文件 ~/kube-general/cicd-role.yml 并使用您喜欢的文本编辑器打开它:

nano ~/kube-general/cicd-role.yml

基本思想是授予访问权限以执行与 default 命名空间中的大多数 Kubernetes 资源相关的所有操作。 你的 Role 看起来像这样:

~/kube-general/cicd-role.yml

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: cicd
  namespace: default
rules:
  - apiGroups: ["", "apps", "batch", "extensions"]
    resources: ["deployments", "services", "replicasets", "pods", "jobs", "cronjobs"]
    verbs: ["*"]

此 YAML 与您之前创建的 YAML 有一些相似之处,但在这里您说此资源是 Role,它来自 Kubernetes API rbac.authorization.k8s.io/v1。 您将角色命名为 cicd,并在您创建 ServiceAccount、default 的同一个命名空间上创建它。

然后你有 rules 字段,这是该角色有权访问的资源列表。 在 Kubernetes 中,资源是根据它们所属的 API 组、资源种类本身以及您可以执行的操作来定义的,这由动词表示。 这些动词类似于HTTP的。

在这种情况下,您是说您的 Role 可以在以下资源上执行所有操作 *:deployments、services、[ X142X]、pods、jobs 和 cronjobs。 这也适用于属于以下 API 组的资源:""(空字符串)、apps、batch 和 extensions。 空字符串表示根 API 组。 如果您在创建资源时使用 apiVersion: v1,则意味着该资源是该 API 组的一部分。

Role 本身什么也不做; 您还必须创建一个 RoleBinding,它将 Role 绑定到某个东西,在本例中为 ServiceAccount。

创建文件 ~/kube-general/cicd-role-binding.yml 并打开它:

nano ~/kube-general/cicd-role-binding.yml

将以下行添加到文件中:

~/kube-general/cicd-role-binding.yml

kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: cicd
  namespace: default
subjects:
  - kind: ServiceAccount
    name: cicd
    namespace: default
roleRef:
  kind: Role
  name: cicd
  apiGroup: rbac.authorization.k8s.io

您的 RoleBinding 有一些本教程尚未涵盖的特定字段。 roleRef 是你要绑定的 Role ; 在这种情况下,它是您之前创建的 cicd 角色。 subjects 是您将角色绑定到的资源列表; 在这种情况下,它是一个名为 cicd 的 ServiceAccount。

注意: 如果您使用了 ClusterRole,则必须创建 ClusterRoleBinding 而不是 RoleBinding。 该文件将几乎相同。 唯一的区别是它在 metadata 内没有 namespace 字段。


创建这些文件后,您将能够再次使用 kubectl apply。 通过运行以下命令在 Kubernetes 集群上创建这些新资源:

kubectl apply -f ~/kube-general/

您将收到类似于以下内容的输出:

Outputrolebinding.rbac.authorization.k8s.io/cicd created
role.rbac.authorization.k8s.io/cicd created
serviceaccount/cicd unchanged

现在,尝试之前运行的命令:

kubectl --insecure-skip-tls-verify --kubeconfig="/dev/null" --server=server-from-kubeconfig-file --token=$TOKEN get pods

由于您没有 pod,这将产生以下输出:

OutputNo resources found in default namespace.

在此步骤中,您为将在 CircleCI 上使用的服务帐户提供了必要的授权,以在集群上执行有意义的操作,例如列出、创建和更新资源。 现在是时候创建您的示例应用程序了。

第 5 步 - 创建您的示例应用程序

注意:从现在开始创建的所有命令和文件都将从您之前创建的文件夹~/do-sample-app开始。 这是因为您现在正在创建特定于要部署到集群的示例应用程序的文件。


您要创建的 Kubernetes Deployment 将使用 Nginx 图像作为基础,您的应用程序将是一个简单的静态 HTML 页面。 这是一个很好的开始,因为它允许您通过直接从 Nginx 提供 HTML 来测试您的部署是否有效。 正如您稍后将看到的,您可以将所有来自本地 address:port 的流量重定向到集群上的部署,以测试它是否正常工作。

在您之前设置的存储库中,创建一个新的 Dockerfile 文件并使用您选择的文本编辑器打开它:

nano ~/do-sample-app/Dockerfile

在上面写下以下内容:

~/do-sample-app/Dockerfile

FROM nginx:1.21

COPY index.html /usr/share/nginx/html/index.html

这将告诉 Docker 从 nginx 映像构建应用程序容器。

现在创建一个新的 index.html 文件并打开它:

nano ~/do-sample-app/index.html

编写以下 HTML 内容:

~/do-sample-app/index.html

<!DOCTYPE html>
<title>DigitalOcean</title>
<body>
  Kubernetes Sample Application
</body>

此 HTML 将显示一条简单的消息,让您知道您的应用程序是否正在运行。

您可以通过构建然后运行它来测试图像是否正确。

首先,使用以下命令构建镜像,将 dockerhub-username 替换为您自己的 Docker Hub 用户名。 您必须在此处指定您的用户名,以便稍后将其推送到 Docker Hub 时,它将起作用:

docker build ~/do-sample-app/ -t dockerhub-username/do-kubernetes-sample-app

现在运行图像。 使用以下命令,启动你的镜像并将端口 8080 上的任何本地流量转发到镜像内部的端口 80,Nginx 默认监听的端口:

docker run --rm -it -p 8080:80 dockerhub-username/do-kubernetes-sample-app

命令运行时,命令提示符将停止交互。 相反,您将看到 Nginx 访问日志。 如果您在任何浏览器上打开 localhost:8080,它应该会显示一个包含 ~/do-sample-app/index.html 内容的 HTML 页面。 如果您没有可用的浏览器,您可以打开一个新的终端窗口并使用以下 curl 命令从网页中获取 HTML:

curl localhost:8080

您将收到以下输出:

Output<!DOCTYPE html>
<title>DigitalOcean</title>
<body>
  Kubernetes Sample Application
</body>

停止容器(在其运行的终端上为 CTRL + C),然后将此图像提交到您的 Docker Hub 帐户。 为此,首先登录 Docker Hub:

docker login

填写你的 Docker Hub 账户所需的信息,然后使用以下命令推送镜像(不要忘记将 dockerhub-username 替换为你自己的):

docker push dockerhub-username/do-kubernetes-sample-app

您现在已将示例应用程序映像推送到您的 Docker Hub 帐户。 在下一步中,您将使用此映像在 DOKS 集群上创建一个部署。

第 6 步 — 创建 Kubernetes 部署和服务

创建并运行 Docker 映像后,您现在将创建一个清单,告诉 Kubernetes 如何在集群上从中创建 Deployment。

创建 YAML 部署文件 ~/do-sample-app/kube/do-sample-deployment.yml 并使用文本编辑器打开它:

nano ~/do-sample-app/kube/do-sample-deployment.yml

在文件中写入以下内容,确保将 dockerhub-username 替换为您的 Docker Hub 用户名:

~/do-sample-app/kube/do-sample-deployment.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: do-kubernetes-sample-app
  namespace: default
  labels:
    app: do-kubernetes-sample-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: do-kubernetes-sample-app
  template:
    metadata:
      labels:
        app: do-kubernetes-sample-app
    spec:
      containers:
        - name: do-kubernetes-sample-app
          image: dockerhub-username/do-kubernetes-sample-app:latest
          ports:
            - containerPort: 80
              name: http

Kubernetes 部署来自 API 组 apps,因此清单的 apiVersion 设置为 apps/v1。 在 metadata 上,您添加了一个以前没有使用过的新字段,称为 metadata.labels。 这对于组织部署很有用。 spec 字段表示部署的行为规范。 一个部署负责管理一个或多个 Pod; 在这种情况下,spec.replicas 字段将有一个副本。 也就是说,它将创建和管理单个 pod。

要管理 pod,您的部署必须知道它负责哪些 pod。 spec.selector 字段是提供该信息的字段。 在这种情况下,部署将负责所有带有标签 app=do-kubernetes-sample-app 的 pod。 spec.template 字段包含此部署将创建的 Pod 的详细信息。 在模板中,您还有一个 spec.template.metadata 字段。 此字段中的 labels 必须与 spec.selector 上使用的匹配。 spec.template.spec 是 pod 本身的规格。 在这种情况下,它包含一个名为 do-kubernetes-sample-app 的容器。 该容器的镜像是您之前构建并推送到 Docker Hub 的镜像。

这个 YAML 文件还告诉 Kubernetes 这个容器暴露了端口 80,并给这个端口命名为 http。

要访问您的 Deployment 公开的端口,请创建一个服务。 创建一个名为 ~/do-sample-app/kube/do-sample-service.yml 的文件并使用您喜欢的编辑器打开它:

nano ~/do-sample-app/kube/do-sample-service.yml

接下来,将以下行添加到文件中:

~/do-sample-app/kube/do-sample-service.yml

apiVersion: v1
kind: Service
metadata:
  name: do-kubernetes-sample-app
  namespace: default
  labels:
    app: do-kubernetes-sample-app
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: http
      name: http
  selector:
    app: do-kubernetes-sample-app

此文件为您的 Service 提供与部署中使用的相同的标签。 这不是必需的,但它有助于在 Kubernetes 上组织您的应用程序。

服务资源还有一个 spec 字段。 spec.type 字段负责服务的行为。 在这种情况下,它是 ClusterIP,这意味着服务在集群内部 IP 上公开,并且只能从集群内部访问。 这是服务的默认 spec.type。 spec.selector 是选择要由该服务公开的 pod 时应使用的标签选择器标准。 由于您的 pod 具有标签 app: do-kubernetes-sample-app,因此您在此处使用了它。 spec.ports 是 pod 的容器公开的端口,您希望从该服务公开。 您的 pod 有一个容器,它公开端口 80,名为 http,因此您在这里将其用作 targetPort。 该服务也在端口 80 上公开该端口,具有相同的名称,但您可以使用与容器中的端口/名称组合不同的端口/名称组合。

创建 Service 和 Deployment 清单文件后,您现在可以使用 kubectl 在 Kubernetes 集群上创建这些资源:

kubectl apply -f ~/do-sample-app/kube/

您将收到以下输出:

Outputdeployment.apps/do-kubernetes-sample-app created
service/do-kubernetes-sample-app created

通过将机器上的一个端口转发到服务在 Kubernetes 集群内公开的端口来测试这是否有效。 您可以使用 kubectl port-forward 来做到这一点:

kubectl port-forward $(kubectl get pod --selector="app=do-kubernetes-sample-app" --output jsonpath='{.items[0].metadata.name}') 8080:80

subshell 命令 $(kubectl get pod --selector="app=do-kubernetes-sample-app" --output jsonpath='{.items[0].metadata.name}') 检索与您使用的 app 标签匹配的 pod 的名称。 否则,您可以使用 kubectl get pods 从 pod 列表中检索它。

运行 port-forward 后,shell 将停止交互,而是输出重定向到集群的请求:

OutputForwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

在任何浏览器上打开 localhost:8080 应该会呈现您在本地运行容器时看到的相同页面,但它现在来自您的 Kubernetes 集群。 和以前一样,您也可以在新的终端窗口中使用 curl 来检查它是否正常工作:

curl localhost:8080

您将收到以下输出:

Output<!DOCTYPE html>
<title>DigitalOcean</title>
<body>
  Kubernetes Sample Application
</body>

接下来,是时候将您创建的所有文件推送到 GitHub 存储库了。 为此,您必须首先 在 GitHub 上创建一个名为 digital-ocean-kubernetes-deploy 的存储库。

为了保持此存储库的简单性以进行演示,请勿在 GitHub UI 上询问时使用 README、license 或 .gitignore 文件初始化新存储库。 您可以稍后添加这些文件。

创建存储库后,将您的本地存储库指向 GitHub 上的存储库。 为此,请按 CTRL + C 停止 kubectl port-forward 并返回命令行,然后运行以下命令添加一个名为 origin 的新遥控器:

cd ~/do-sample-app/
git remote add origin https://github.com/your-github-account-username/digital-ocean-kubernetes-deploy.git

前面的命令应该没有输出。

接下来,将您到目前为止创建的所有文件提交到 GitHub 存储库。 首先,添加文件:

git add --all

接下来,将文件提交到您的存储库,并带有引号中的提交消息:

git commit -m "initial commit"

这将产生类似于以下内容的输出:

Output[master (root-commit) db321ad] initial commit
 4 files changed, 47 insertions(+)
 create mode 100644 Dockerfile
 create mode 100644 index.html
 create mode 100644 kube/do-sample-deployment.yml
 create mode 100644 kube/do-sample-service.yml

最后,将文件推送到 GitHub:

git push -u origin master

系统将提示您输入用户名和密码。 输入后,您将看到如下输出:

OutputCounting objects: 7, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (7/7), done.
Writing objects: 100% (7/7), 907 bytes | 0 bytes/s, done.
Total 7 (delta 0), reused 0 (delta 0)
To github.com:your-github-account-username/digital-ocean-kubernetes-deploy.git
 * [new branch]      master -> master
Branch master set up to track remote branch master from origin.

如果您转到 GitHub 存储库页面,您现在将看到那里的所有文件。 在 GitHub 上建立您的项目后,您现在可以将 CircleCI 设置为您的 CI/CD 工具。

第 7 步 — 配置 CircleCI

对于本教程,您将使用 CircleCI 在代码更新时自动部署应用程序,因此您需要使用您的 GitHub 帐户登录到 CircleCI 并设置您的存储库。

首先,去他们的主页https://circleci.com,然后按注册。

您正在使用 GitHub,因此请单击绿色的 Sign Up with GitHub 按钮。

CircleCI 将重定向到 GitHub 上的授权页面。 CircleCI 需要您帐户的一些权限才能开始构建您的项目。 这允许 CircleCI 获取您的电子邮件、部署密钥和在您的存储库上创建挂钩的权限,并将 SSH 密钥添加到您的帐户。 如果您需要有关 CircleCI 将如何处理您的数据的更多信息,请查看他们的 关于 GitHub 集成的文档 。

授权 CircleCI 后,您将被重定向到 Projects 页面。 在这里,您可以在 CircleCI 中设置您的 GitHub 存储库。 在 digital-ocean-kubernetes-deploy 存储库的条目中选择 Set Up Project。 然后选择 Faster: Commit a starter CI pipeline to a new branch 选项。 这将为您的项目创建一个新的 circleci-project-setup 分支。

接下来,在 CircleCI 设置中指定一些环境变量。 您可以通过选择页面右上角的项目设置按钮然后选择环境变量来找到项目的设置。 按添加环境变量创建新的环境变量。

首先,添加两个名为 DOCKERHUB_USERNAME 和 DOCKERHUB_PASS 的环境变量,稍后将需要它们来将映像推送到 Docker Hub。 将值分别设置为您的 Docker Hub 用户名和密码。

然后再添加三个:KUBERNETES_TOKEN、KUBERNETES_SERVER 和 KUBERNETES_CLUSTER_CERTIFICATE。

KUBERNETES_TOKEN 的值将是您之前使用服务帐户用户在 Kubernetes 集群上进行身份验证时使用的本地环境变量的值。 如果您已关闭终端,您可以随时运行以下命令再次检索它:

kubectl get secret $(kubectl get secret | grep cicd-token | awk '{print $1}') -o jsonpath='{.data.token}' | base64 --decode

KUBERNETES_SERVER 将是您在使用 cicd 服务帐户登录时作为 --server 标志传递给 kubectl 的字符串。 您可以在 ~/.kube/config 文件中的 server: 之后找到它,或者在您对 Kubernetes 集群进行初始设置时从 DigitalOcean 仪表板下载的文件 kubernetes-deployment-tutorial-kubeconfig.yaml 中找到它。

KUBERNETES_CLUSTER_CERTIFICATE 也应该在您的 ~/.kube/config 文件中可用。 它是与您的集群相关的 clusters 项上的 certificate-authority-data 字段。 它应该是一个长字符串; 确保复制所有内容。

这些环境变量必须在此处定义,因为它们中的大多数都包含敏感信息,并且将它们直接放在 CircleCI YAML 配置文件中是不安全的。

CircleCI 监听你的存储库的变化和配置的环境变量,是时候创建配置文件了。

在示例应用程序存储库中创建一个名为 .circleci 的目录:

mkdir ~/do-sample-app/.circleci/

在此目录中,创建一个名为 config.yml 的文件并使用您喜欢的编辑器打开它:

nano ~/do-sample-app/.circleci/config.yml

将以下内容添加到文件中,确保将 dockerhub-username 替换为您的 Docker Hub 用户名:

~/do-sample-app/.circleci/config.yml

version: 2.1
jobs:
  build:
    docker:
      - image: circleci/buildpack-deps:bullseye
    environment:
      IMAGE_NAME: dockerhub-username/do-kubernetes-sample-app
    working_directory: ~/app
    steps:
      - checkout
      - setup_remote_docker
      - run:
          name: Build Docker image
          command: |
            docker build -t $IMAGE_NAME:latest .
      - run:
          name: Push Docker Image
          command: |
            echo "$DOCKERHUB_PASS" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin
            docker push $IMAGE_NAME:latest
workflows:
  version: 2
  build-deploy-master:
    jobs:
      - build:
          filters:
            branches:
              only: master

这设置了工作流 build-deploy-master,它现在有一个名为 build 的作业。 每次提交到 master 分支时,都会运行此作业。

build 作业使用映像 circleci/buildpack-deps:bullseye 运行其步骤,这是来自 CircleCI 基于官方 buildpack-deps Docker 映像的映像,但安装了一些额外的工具,例如Docker 二进制文件本身。

工作流程有四个步骤:

  • checkout 从 GitHub 检索代码。
  • setup_remote_docker 为每个构建设置一个远程、隔离的环境。 在作业步骤中使用任何 docker 命令之前,这是必需的。 这是必要的,因为这些步骤在 docker 映像中运行,setup_remote_docker 分配另一台机器在那里运行命令。
  • 第一个 run 步骤构建映像,就像您之前在本地环境中所做的那样。 为此,您正在使用在 environment:、IMAGE_NAME 中声明的环境变量。
  • 最后一个 run 步骤将镜像推送到 Dockerhub,使用您在项目设置中配置的环境变量进行身份验证。

将新文件提交到您的存储库并将更改推送到上游:

cd ~/do-sample-app/
git add .circleci/
git commit -m "add CircleCI config"
git push

这将触发 CircleCI 的新构建。 CircleCI 工作流程将正确构建您的图像并将其推送到 Docker Hub。

现在您已经创建并测试了您的 CircleCI 工作流程,您可以设置 DOKS 集群以从 Docker Hub 检索最新图像并在进行更改时自动部署它。

第 8 步 — 更新 Kubernetes 集群上的部署

现在,每次您将更改推送到 GitHub 上的 master 分支时,都会构建您的应用程序映像并将其发送到 Docker Hub,现在是时候更新您在 Kubernetes 集群上的部署,以便它检索新映像并使用它作为部署的基地。

为此,首先解决部署的一个问题:它当前取决于带有 latest 标签的图像。 此标签不会告诉我们您使用的是哪个版本的图像。 您不能轻易地将您的部署锁定到该标记,因为每次您将新映像推送到 Docker Hub 时它都会被覆盖,并且通过这样使用它,您将失去容器化应用程序的可重复性。

你可以在 Vladislav Supalov 的文章中阅读更多关于为什么依赖最新标签是反模式 的文章。

要更正此问题,您首先必须对 ~/do-sample-app/.circleci/config.yml 文件中的 Push Docker Image 构建步骤进行一些更改。 打开文件:

nano ~/do-sample-app/.circleci/config.yml

然后将突出显示的行添加到您的 Push Docker Image 步骤:

~/do-sample-app/.circleci/config.yml

...
      - run:
          name: Push Docker Image
          command: |
            echo "$DOCKERHUB_PASS" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin
            docker tag $IMAGE_NAME:latest $IMAGE_NAME:$CIRCLE_SHA1
            docker push $IMAGE_NAME:latest
            docker push $IMAGE_NAME:$CIRCLE_SHA1
...

CircleCI 默认设置了一些特殊的环境变量。 其中之一是 CIRCLE_SHA1,它包含它正在构建的提交的哈希值。 您对 ~/do-sample-app/.circleci/config.yml 所做的更改将使用此环境变量来使用构建它的提交标记您的图像,始终使用最新标签标记最新构建。 这样,您始终可以使用特定的图像,而不会在将新内容推送到存储库时覆盖它们。

保存并退出文件。

接下来,更改您的部署清单文件以指向该文件。 如果在 ~/do-sample-app/kube/do-sample-deployment.yml 内部您可以将图像设置为 dockerhub-username/do-kubernetes-sample-app:$COMMIT_SHA1,这将是一个很小的变化,但是当您使用 [ 时 kubectl 不会在清单中进行变量替换X173X]。 为了解决这个问题,您可以使用 envsubst。 envsubst 是一个 CLI 工具,它是 GNU gettext 项目的一部分。 它允许您将一些文本传递给它,如果它在文本中找到任何具有匹配环境变量的变量,它将用相应的值替换该变量。 然后将生成的文本作为输出返回。

要使用它,您将创建一个负责部署的 bash 脚本。 在 ~/do-sample-app/ 中创建一个名为 scripts 的新文件夹:

mkdir ~/do-sample-app/scripts/

在该文件夹中创建一个名为 ci-deploy.sh 的新 bash 脚本,并使用您喜欢的文本编辑器打开它:

nano ~/do-sample-app/scripts/ci-deploy.sh

在其中编写以下 bash 脚本:

~/do-sample-app/scripts/ci-deploy.sh

#! /bin/bash
# exit script when any command ran here returns with non-zero exit code
set -e

COMMIT_SHA1=$CIRCLE_SHA1

# Export it so it's available for envsubst
export COMMIT_SHA1=$COMMIT_SHA1

#  Since the only way for envsubst to work on files is using input/output redirection,
#  it's not possible to do in-place substitution, so you will save the output to another file
#  and overwrite the original with that one.
envsubst <./kube/do-sample-deployment.yml >./kube/do-sample-deployment.yml.out
mv ./kube/do-sample-deployment.yml.out ./kube/do-sample-deployment.yml

echo "$KUBERNETES_CLUSTER_CERTIFICATE" | base64 --decode > cert.crt

./kubectl \
  --kubeconfig=/dev/null \
  --server=$KUBERNETES_SERVER \
  --certificate-authority=cert.crt \
  --token=$KUBERNETES_TOKEN \
  apply -f ./kube/

让我们使用文件中的注释浏览这个脚本。 首先,有以下几点:

set -e

此行确保任何失败的命令都会停止执行 bash 脚本。 这样,如果一个命令失败,则不会执行下一个命令。

COMMIT_SHA1=$CIRCLE_SHA1
export COMMIT_SHA1=$COMMIT_SHA1

这些行以新名称导出 CircleCI $CIRCLE_SHA1 环境变量。 如果您刚刚声明了变量而不使用 export 导出它,则 envsubst 命令将看不到它。

envsubst <./kube/do-sample-deployment.yml >./kube/do-sample-deployment.yml.out
mv ./kube/do-sample-deployment.yml.out ./kube/do-sample-deployment.yml

envsubst 不能进行就地替换。 也就是说,它不能读取文件的内容,用它们各自的值替换变量,并将输出写回同一个文件。 因此,您将输出重定向到另一个文件,然后用新文件覆盖原始文件。

echo "$KUBERNETES_CLUSTER_CERTIFICATE" | base64 --decode > cert.crt

您之前在 CircleCI 的项目设置中创建的环境变量 $KUBERNETES_CLUSTER_CERTIFICATE 实际上是一个 Base64 编码的字符串。 要将其与 kubectl 一起使用,您必须对其内容进行解码并将其保存到文件中。 在这种情况下,您将其保存到当前工作目录中名为 cert.crt 的文件中。

./kubectl \
  --kubeconfig=/dev/null \
  --server=$KUBERNETES_SERVER \
  --certificate-authority=cert.crt \
  --token=$KUBERNETES_TOKEN \
  apply -f ./kube/

最后,您正在运行 kubectl。 该命令与您在测试服务帐户时运行的命令具有相似的参数。 您正在调用 apply -f ./kube/,因为在 CircleCI 上,当前工作目录是您项目的根文件夹。 ./kube/ 这里是你的 ~/do-sample-app/kube 文件夹。

保存文件并确保它是可执行的:

chmod +x ~/do-sample-app/scripts/ci-deploy.sh

现在,编辑 ~/do-sample-app/kube/do-sample-deployment.yml:

nano ~/do-sample-app/kube/do-sample-deployment.yml

将容器图像值的标记更改为如下所示:

~/do-sample-app/kube/do-sample-deployment.yml

...
      containers:
        - name: do-kubernetes-sample-app
          image: dockerhub-username/do-kubernetes-sample-app:$COMMIT_SHA1
          ports:
            - containerPort: 80
              name: http

保存并关闭文件。 您现在必须向 CI 配置文件添加一些新步骤,以更新 Kubernetes 上的部署。

在您喜欢的文本编辑器中打开 ~/do-sample-app/.circleci/config.yml:

nano ~/do-sample-app/.circleci/config.yml

在您之前创建的 build 作业的正下方编写以下新作业:

~/do-sample-app/.circleci/config.yml

...
          command: |
            echo "$DOCKERHUB_PASS" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin
            docker tag $IMAGE_NAME:latest $IMAGE_NAME:$CIRCLE_SHA1
            docker push $IMAGE_NAME:latest
            docker push $IMAGE_NAME:$CIRCLE_SHA1
  deploy:
    docker:
      - image: circleci/buildpack-deps:bullseye
    working_directory: ~/app
    steps:
      - checkout
      - run:
          name: Install envsubst
          command: |
            sudo apt-get update && sudo apt-get -y install gettext-base
      - run:
          name: Install kubectl
          command: |
            curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
            chmod u+x ./kubectl
      - run:
          name: Deploy Code
          command: ./scripts/ci-deploy.sh
...

新 deploy 作业的前两个步骤是安装一些依赖项,首先是 envsubst,然后是 kubectl。 Deploy Code 步骤负责运行我们的部署脚本。

现在您将此作业添加到您之前创建的 build-deploy-master 工作流程中。 在 build-deploy-master 工作流配置中,在 build 作业的现有条目之后写入以下新条目:

~/do-sample-app/.circleci/config.yml

...
workflows:
  version: 2
  build-deploy-master:
    jobs:
      - build:
          filters:
            branches:
              only: master
      - deploy:
          requires:
            - build
          filters:
            branches:
              only: master

这会将 deploy 作业添加到 build-deploy-master 工作流程。 deploy 作业只会在提交到 master 时运行,并且只有在 build 作业完成后才会运行。

~/do-sample-app/.circleci/config.yml 的内容现在是这样的:

~/do-sample-app/.circleci/config.yml

version: 2.1
jobs:
  build:
    docker:
      - image: circleci/buildpack-deps:bullseye
    environment:
      IMAGE_NAME: dockerhub-username/do-kubernetes-sample-app
    working_directory: ~/app
    steps:
      - checkout
      - setup_remote_docker
      - run:
          name: Build Docker image
          command: |
            docker build -t $IMAGE_NAME:latest .
      - run:
          name: Push Docker Image
          command: |
            echo "$DOCKERHUB_PASS" | docker login -u "$DOCKERHUB_USERNAME" --password-stdin
            docker tag $IMAGE_NAME:latest $IMAGE_NAME:$CIRCLE_SHA1
            docker push $IMAGE_NAME:latest
            docker push $IMAGE_NAME:$CIRCLE_SHA1
  deploy:
    docker:
      - image: circleci/buildpack-deps:bullseye
    working_directory: ~/app
    steps:
      - checkout
      - run:
          name: Install envsubst
          command: |
            sudo apt-get update && sudo apt-get -y install gettext-base
      - run:
          name: Install kubectl
          command: |
            curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl
            chmod u+x ./kubectl
      - run:
          name: Deploy Code
          command: ./scripts/ci-deploy.sh
workflows:
  version: 2
  build-deploy-master:
    jobs:
      - build:
          filters:
            branches:
              only: master
      - deploy:
          requires:
            - build
          filters:
            branches:
              only: master

您现在可以保存并退出文件。

为确保更改真正反映在您的 Kubernetes 部署中,请编辑您的 index.html。 将 HTML 更改为其他内容,例如:

~/do-sample-app/index.html

<!DOCTYPE html>
<title>DigitalOcean</title>
<body>
  Automatic Deployment is Working!
</body>

保存上述更改后,将所有修改后的文件提交到存储库,并将更改推送到上游:

cd ~/do-sample-app/
git add --all
git commit -m "add deploy script and add new steps to circleci config"
git push

您将看到在 CircleCI 上运行的新构建,并成功地将更改部署到您的 Kubernetes 集群。

等待构建完成,然后运行之前运行的相同命令:

kubectl port-forward $(kubectl get pod --selector="app=do-kubernetes-sample-app" --output jsonpath='{.items[0].metadata.name}') 8080:80

通过在 URL localhost:8080 上打开浏览器或向其发出 curl 请求,确保一切正常。 它应该显示更新的 HTML:

curl localhost:8080

您将收到以下输出:

Output<!DOCTYPE html>
<title>DigitalOcean</title>
<body>
  Automatic Deployment is Working!
</body>

这意味着您已经成功地使用 CircleCI 设置了自动化部署。

结论

这是关于如何使用 CircleCI 对 DigitalOcean Kubernetes 进行部署的基本教程。 从这里,您可以通过多种方式改进您的管道。 您可以做的第一件事是为多个部署创建单个 build 作业,每个部署到不同的 Kubernetes 集群或不同的命名空间。 当您有不同的 Git 分支用于开发/登台/生产环境时,这会很有用,确保部署始终是分开的。

您还可以构建自己的图像以在 CircleCI 上使用,而不是使用 buildpack-deps。 此图像可以基于它,但可能已经安装了 kubectl 和 envsubst 依赖项。

如果您想了解有关 Kubernetes 上的 CI/CD 的更多信息,请查看我们的 CI/CD on Kubernetes 网络研讨会系列 的教程,或者有关 Kubernetes 上的应用程序的更多信息,请参阅 现代化应用程序Kubernetes 。