如何使用CircleCI自动部署到DigitalOcean Kubernetes

拥有自动部署流程是可扩展和弹性应用程序的必要条件,将CI / CD概念应用于Kubernetes尤为重要。在本文中,您将使用CircleCI自动将示例应用程序部署到DigitalOcean Kubernetes(DOKS)集群。

作者选择技术教育基金作为Write for DOnations计划的一部分接受捐赠。

介绍

拥有自动部署流程是可扩展和弹性应用程序的必要条件,而GitOps基于Git的DevOps已迅速成为组织CI / CD的流行方法,其中Git存储库是“单一事实来源”。 CircleCI这样的工具与您的GitHub存储库集成,允许您在每次更改存储库时自动测试和部署代码。 当这种CI / CD与Kubernetes基础架构的灵活性相结合时,您可以构建一个可随着需求变化轻松扩展的应用程序。

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

先决条件

要学习本教程,您需要:

在本教程中,您将使用Kubernetes版本1.13.5kubectl版本1.10.7

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

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

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

转到DigitalOcean帐户的Kubernetes页面

单击“ 创建Kubernetes群集” ,或单击页面右上角的绿色“ 创建”按钮,然后从下拉菜单中选择“ 群集 ”。

[在DigitalOcean上创建Kubernetes集群](assets.digitalocean.com/articles/cart 64920 /创建 DOKS.gif)

下一页是您要指定群集详细信息的位置。 选择Kubernetes版本选择版本1.13.5-do.0 如果没有此选项,请选择更高的一个。

对于选择数据中心区域 ,请选择离您最近的区域。 本教程将使用旧金山 - 2

然后,您可以选择构建节点池 在Kubernetes上,节点是一个工作机器,它包含运行pod所需的服务。 在DigitalOcean上,每个节点都是Droplet。 您的节点池将包含一个标准节点 选择2GB / 1vCPU配置,并在节点数上更改为1节点。

如果需要,您可以添加额外的标签; 如果您计划使用DigitalOcean API或仅更好地组织节点池,这可能很有用。

选择名称时 ,对于本教程,请使用kubernetes-deployment-tutorial 这将使您在阅读下一部分时更容易理解。 最后,单击绿色“ 创建群集”按钮以创建群集。

创建集群后,UI上会有一个按钮,用于下载名为Download Config File的配置文件 这是您将用于验证要对群集运行的kubectl命令的文件。 将其下载到您的kubectl机器。

使用该文件的默认方法是始终在使用--kubeconfig运行的所有命令上传递--kubeconfig标志及其路径。 例如,如果您将配置文件下载到Desktop ,则可以运行kubectl get pods命令,如下所示:

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

这将产生以下输出:

No 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

您将收到以下输出:

No resources found.

现在使用以下内容访问集群:

kubectl get nodes

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

NAME                                    STATUS    ROLES     AGE       VERSION
kubernetes-deployment-tutorial-1-7pto   Ready     <none>    1h        v1.13.5

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

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

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

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

注意:本教程已经在Ubuntu 18.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步 - 创建服务帐户

通常不建议使用默认管理员用户从Kubernetes集群中的其他服务进行身份验证。 如果外部提供商上的密钥遭到破坏,整个群集将会受到损害。

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

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

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

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

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

在上面写下以下内容:

〜/ KUBE-普通/ CICD-服务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/

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

serviceaccount/cicd created

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

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

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服务帐户检索您的cicd 运行以下命令,将server-from-kubeconfig-file替换为server-from-kubeconfig-file in ~kube/config后可以找到的服务器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不会读取您的配置文件和凭据,而是使用提供的令牌。

输出应该类似于:

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

这是一个错误,但它向​​我们显示令牌有效。 您收到的错误是您的服务帐户没有列出资源secrets的必要授权,但您可以访问服务器本身。 如果您的令牌无效,则错误可能是以下错误:

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

既然身份验证成功了,下一步就是修复服务帐户的授权错误。 您将通过创建具有必要权限的角色并将其绑定到您的服务帐户来完成此操作。

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

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

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

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

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

基本思想是授予访问权限以执行与default命名空间中的大多数Kubernetes资源相关的所有操作。 您的Role将如下所示:

〜/ KUBE-一般/ 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与你之前创建的有一些相似之处,但是你在这里说这个资源是一个Role ,它来自Kubernetes API rbac.authorization.k8s.io/v1 您正在命名您的角色cicd ,并在您创建ServiceAccount的同一名称空间(即default名称)上创建它。

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

在我们的例子中,您说允许您的Role在以下资源上执行所有操作*deploymentsservicesreplicasetspodsjobscronjobs 这也适用于属于以下API组的那些资源: "" (空字符串), appsbatchextensions 空字符串表示根API组。 如果在创建资源时使用apiVersion: v1 ,则表示此资源是此API组的一部分。

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

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

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

将以下行添加到文件中:

〜/ KUBE-普通/ CICD-角色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/

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

rolebinding.rbac.authorization.k8s.io/cicd created
role.rbac.authorization.k8s.io/cicd created
serviceaccount/cicd created

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

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

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

No resources found.

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

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

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

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

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

nano ~/do-sample-app/Dockerfile

在上面写下:

〜/做样品应用内/ Dockerfile
FROM nginx:1.14

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

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

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

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

编写以下HTML内容:

〜/做样品应用内/ 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

您将收到以下输出:

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

停止容器(运行它的终端上的CTRL + C ),并将此映像提交到Docker Hub帐户。 要执行此操作,请首先登录Docker Hub:

docker login

填写有关Docker Hub帐户的必要信息,然后使用以下命令推送映像(不要忘记将dockerhub-username替换为您自己dockerhub-username ):

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

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

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

创建并运行Docker镜像后,您现在将创建一个清单,告知Kubernetes如何在群集上创建部署

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

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

在文件上写下以下内容,确保将dockerhub-username替换dockerhub-username Docker Hub用户名:

〜/做样品应用内/ KUBE / DO-采样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。

要管理容器,您的部署必须知道它负责哪些容器。 spec.selector字段是spec.selector提供该信息的字段。 在这种情况下,部署将负责所有标签app=do-kubernetes-sample-app spec.template字段包含此部署将创建的Pod的详细信息。 在模板内部,您还有一个spec.template.metadata字段。 此字段内的labels必须与spec.selector使用的labels匹配。 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

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

〜/做样品应用内/ KUBE / DO-采样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有一个容器,它暴露了名为http端口80 ,因此您在此处使用它作为targetPort 该服务也使用相同的名称在端口80上公开该端口,但您可以使用与容器中的端口/名称组合不同的端口/名称组合。

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

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

您将收到以下输出:

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

通过将计算机上的一个端口转发到服务在Kubernetes集群中公开的端口来测试它是否正常工作。 你可以使用kubectl port-forward做到这kubectl port-forward

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

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

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

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

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

curl localhost:8080

您将收到以下输出:

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

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

为了使此存储库简单以用于演示目的,请不要在GitHub UI上询问时使用READMElicense.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"

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

[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

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

Counting 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 ,然后按“ 注册”

circleci家庭页

您正在使用GitHub,因此请单击绿色注册GitHub按钮。

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

circleci-GitHub的授权

授权CircleCI后,您将被重定向到他们的仪表板。

circleci项目的仪表板

接下来,在CircleCI中设置GitHub存储库。 单击CircleCI仪表板中的设置新项目 ,或作为快捷方式,打开以下链接,使用您自己的GitHub用户名更改突出显示的文本: https://circleci.com/setup-project/gh/ your-github-username /digital-ocean-kubernetes-deployhttps://circleci.com/setup-project/gh/ your-github-username /digital-ocean-kubernetes-deploy

之后按Start Building 暂时不要在存储库中创建配置文件,如果第一次构建失败,请不要担心。

circleci启动建设

接下来,在CircleCI设置中指定一些环境变量。 您可以通过单击页面右上角的cog图标的小按钮然后选择Environment Variables来查找项目的设置,或者您可以使用以下URL直接转到环境变量页面(记得填写)在您的用户名中): https://circleci.com/gh/ your-github-username /digital-ocean-kubernetes-deploy/edit#env-varshttps://circleci.com/gh/ your-github-username /digital-ocean-kubernetes-deploy/edit#env-vars 添加变量以创建新的环境变量。

首先,添加两个名为DOCKERHUB_USERNAMEDOCKERHUB_PASS环境变量, DOCKERHUB_PASS将需要将其推送到Docker Hub。 分别将值设置为Docker Hub用户名和密码。

然后再添加三个: KUBERNETES_TOKENKUBERNETES_SERVERKUBERNETES_CLUSTER_CERTIFICATE

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

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

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

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

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

随着CircleCI监听存储库的更改以及配置的环境变量,是时候创建配置文件了。

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

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

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

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

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

〜/做样品应用内/ .circleci / config.yml
version: 2.1
jobs:
  build:
    docker:
      - image: circleci/buildpack-deps:stretch
    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-master:
    jobs:
      - build:
          filters:
            branches:
              only: master

这将使用单个作业(称为build设置工作流,该作业针对master分支的每次提交运行。 这个工作是使用图像circleci/buildpack-deps:stretch来运行它的步骤,这是来自CircleCI的图像,基于官方buildpack-deps Docker镜像,但安装了一些额外的工具,比如Docker二进制文件本身。

工作流程有四个步骤:

  • checkout从GitHub检索代码。
  • setup_remote_docker为每个构建设置一个远程隔离环境。 在作业步骤中使用任何docker命令之前,这是必需的。 这是必要的,因为当步骤在setup_remote_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构建页面,包含成功构建信息

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

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

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

To do that, first fix one issue with your deployment: it's currently depending on an image with the latest tag. This tag does not tell us which version of the image you are using. You cannot easily lock your deployment to that tag because it's overwritten everytime you push a new image to Docker Hub, and by using it like that you lose one of the best things about having containerized applications: Reproducibility.

You can read more about that on this article about why depending on Docker latest tag is a anti-pattern .

To correct this, you first must make some changes to your Push Docker Image build step in the ~/do-sample-app/.circleci/config.yml file. Open up the file:

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

Then add the highlighted lines to your Push Docker Image step:

~/do-sample-app/.circleci/config.yml:16-22
...
      - 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 has some special environment variables set by default. One of them is CIRCLE_SHA1 , which contains the hash of the commit it's building. The changes you made to ~/do-sample-app/.circleci/config.yml will use this environment variable to tag your image with the commit it was built from, always tagging the most recent build with the latest tag. That way, you always have specific images available, without overwriting them when you push something new to your repository.

Next, change your deployment manifest file to point to that file. This would be simple if inside ~/do-sample-app/kube/do-sample-deployment.yml you could set your image as dockerhub-username /do-kubernetes-sample-app:$COMMIT_SHA1 , but kubectl doesn't do variable substitution inside the manifests when you use kubectl apply . To account for this, you can use envsubst . envsubst is a cli tool, part of the GNU gettext project. It allows you to pass some text to it, and if it finds any variable inside the text that has a matching environment variable, it's replaced by the respective value. The resulting text is then returned as their output.

To use this, you will create a simple bash script which will be responsible for your deployment. Make a new folder called scripts inside ~/do-sample-app/ :

mkdir ~/do-sample-app/scripts/

Inside that folder create a new bash script called ci-deploy.sh and open it with your favorite text editor:

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

Inside it write the following bash script:

~/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

# We must 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 we need to 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/

Let's go through this script, using the comments in the file. First, there is the following:

set -e

This line makes sure any failed command stops the execution of the bash script. That way if one command fails, the next ones are not executed.

COMMIT_SHA1=$CIRCLE_SHA1
export COMMIT_SHA1=$COMMIT_SHA1

These lines export the CircleCI $CIRCLE_SHA1 environment variable with a new name. If you had just declared the variable without exporting it using export , it would not be visible for the envsubst command.

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 cannot do in-place substitution. That is, it cannot read the content of a file, replace the variables with their respective values, and write the output back to the same file. Therefore, you will redirect the output to another file and then overwrite the original file with the new one.

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

The environment variable $KUBERNETES_CLUSTER_CERTIFICATE you created earlier on CircleCI's project settings is in reality a Base64 encoded string. To use it with kubectl you must decode its contents and save it to a file. In this case you are saving it to a file named cert.crt inside the current working directory.

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

Finally, you are running kubectl . The command has similar arguments to the one you ran when you were testing your Service Account. You are calling apply -f ./kube/ , since on CircleCI the current working directory is the root folder of your project. ./kube/ here is your ~/do-sample-app/kube folder.

Save the file and make sure it's executable:

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

Now, edit ~/do-sample-app/kube/do-sample-deployment.yml :

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

Change the tag of the container image value to look like the following one:

~/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

保存并关闭文件。 You must now add some new steps to your CI configuration file to update the deployment on Kubernetes.

Open ~/do-sample-app/.circleci/config.yml on your favorite text editor:

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

Write the following new steps, right below the Push Docker Image one you had before:

~/do-sample-app/.circleci/config.yml
...
      - 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

The first two steps are installing some dependencies, first envsubst , and then kubectl . The Deploy Code step is responsible for running our deploy script.

To make sure the changes are really going to be reflected on your Kubernetes deployment, edit your index.html . Change the HTML to something else, like:

~/do-sample-app/index.html
<!DOCTYPE html>
<title>DigitalOcean</title>
<body>
  Automatic Deployment is Working!
</body>

Once you have saved the above change, commit all the modified files to the repository, and push the changes upstream:

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

You will see the new build running on CircleCI, and successfully deploying the changes to your Kubernetes cluster.

Wait for the build to finish, then run the same command you ran previously:

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

Make sure everything is working by opening your browser on the URL localhost:8080 or by making a curl request to it. It should show the updated HTML:

curl localhost:8080

您将收到以下输出:

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

Congratulations, you have set up automated deployment with CircleCI!

结论

This was a basic tutorial on how to do deployments to DigitalOcean Kubernetes using CircleCI. From here, you can improve your pipeline in many ways. The first thing you can do is create a single build job for multiple deployments, each one deploying to different Kubernetes clusters or different namespaces. This can be extremely useful when you have different Git branches for development/staging/production environments, ensuring that the deployments are always separated.

You could also build your own image to be used on CircleCI, instead of using buildpack-deps . This image could be based on it, but could already have kubectl and envsubst dependencies installed.

If you would like to learn more about CI/CD on Kubernetes, check out the tutorials for our CI/CD on Kubernetes Webinar Series , or for more information about apps on Kubernetes, see Modernizing Applications for Kubernetes .