Semaphore让你有能力轻松创建CI/CD管道,构建、运行和部署Docker容器。DigitalOcean最近推出了一个管理的Kubernetes服务,简化了云原生应用程序的运行。两者结合在一起,是生产性软件开发的绝佳搭配。在这篇文章中,我们将向你展示如何在一个快速的持续交付管道中把这两种服务连接起来。
我们要构建的东西
我们将使用一个Ruby Sinatra微服务,它暴露了一些HTTP端点并包括一个测试套件。我们将用Docker打包,并将其部署到DigitalOcean Kubernetes。CI/CD流水线将完全自动完成以下任务:
- 安装项目依赖,大部分时间从缓存中重复使用它们。
- 运行单元测试。
- 构建并标记一个Docker镜像。
- 将Docker镜像推送到Docker Hub容器注册处。
- 提供一键部署到DigitalOcean Kubernetes。

第一个CI/CD管道
我们将一步一步地进行,但如果你想直接跳到最终版本的源代码,请查看GitHub上的演示仓库。
semaphoreci-demos / semaphore-demo-ruby-kubernetes
让我们开始吧!
在DigitalOcean上5分钟内启动一个Kubernetes集群
在DigitalOcean上启动一个Kubernetes集群是很简单的。从你的仪表板,使用顶部的创建按钮来创建它。该集群将在4-5分钟内变得可用。
在DigitalOcean上创建一个Kubernetes集群
该集群页面包括一些提示和资源,你可以使用。如果你还没有这样做,现在是时候安装kubectl,Kubernetes命令行工具。
连接到你的DigitalOcean Kubernetes集群
为集群提供一个名称,并选择运行多少个节点和什么类型的节点。然后,滚动到集群页面的底部,下载配置文件,您将使用该文件来验证和连接到集群。
下载你的DigitalOcean Kubernetes配置文件
在你的本地机器上,创建一个目录,包含Kubernetes配置文件:
$ mkdir ~/.kube
将下载的文件移到~/.kube ,并指示kubectl使用它。你可以在你的终端会话中运行以下命令,或将其添加到你的shell配置文件中,如.bashrc 或.zshrc :
$ export KUBECONFIG=$HOME/.kube/kubeconfig.yaml
确保改变kubeconfig.yaml ,以符合你的文件的名称。
通过运行kubectl get nodes ,验证你可以与你的DigitalOcean Kubernetes集群通信。当该命令成功时,它返回类似以下的信息:
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
nostalgic-heisenberg-8vi3 Ready <none> 92s v1.22.8
nostalgic-heisenberg-8vi8 Ready <none> 95s v1.22.8
节点的数量将与你在集群创建过程中选择的数量一致。注意,如果你在集群仍在配置中时运行get nodes ,节点数将为零。
将Semaphore连接到你的DigitalOcean Kubernetes集群上
在这一点上,你有一个Kubernetes集群,你可以从你的本地机器控制。让我们配置一个基本的CI/CD项目,其中Semaphore也可以成功执行kubectl get nodes 。
如果你是Semaphore的新手,请从入门之旅开始,注册一个免费账户。
在Semaphore上创建一个项目
进入Semaphore后,点击项目旁边的+(加号),从列表中选择你的资源库:

将软件库添加到Semaphore
当有提示时,请选择。
- 如果你想从最小的配置开始,在跟随本教程的同时建立它,请选择 "我想从头配置这个项目"。
- 如果你想跳到最后,查看最终的配置,选择 "我将使用现有的配置"选项。
对于这两种情况,Semaphore都会在GitHub上创建一个部署密钥和webhook,这样它就可以在你的代码发生变化时访问它。它还创建了一个管道定义文件.semaphore/semaphore.yml 。
使用Semaphore的秘密与Kubernetes进行认证
让我们编辑管道,指示Semaphore如何与Kubernetes对话。
Semaphore已经提供了预装的kubectl。所以剩下的就是在Semaphore环境中安全地上传Kubernetes配置文件。我们通常通过创建一个秘密来解决这个问题。一个秘密可以是环境变量和文件的集合。一旦创建,它对组织内的所有项目都可用。
在我们的例子中,我们需要一个基于单个文件的秘密。
- 进入你的Semaphore账户,点击设置:

设置菜单
- 按 "创建新秘密 "按钮。
- 为你的Kubernetes配置创建秘密。
- 将秘密的名称设为
do-k8s。 - 在文件下,键入路径
/home/semaphore/.kube/config,其中/home/semaphore/是所有CI/CD工作运行的默认目录。 - 上传DigitalOcean Kubernetes配置文件。
- 将秘密的名称设为
- 单击 "保存更改"。

上传Kubernetes配置文件到Semaphore
要修改管道配置,点击右上角的编辑工作流按钮。

点击 " 编辑工作流"来修改管道。
让我们尝试连接到Kubernetes。修改初始块,使其看起来像这样。

连接到Kubernetes
如果这是你第一次看到Semaphore管道,快速浏览一下概念将有助于你理解它。以下是它们在这个例子中的应用要点:
- 工作有一个名称和一个要执行的命令列表。
- 工作被分组在块中。
- 我们为作业初始化环境变量。在这个例子中,我们要设置
KUBECONFIG = /home/semaphore/.kube/dok8s.yml - 我们用刚刚创建的
do-k8s秘密挂载Kubernetes配置文件。 - 我们的管道有一个区块和一个作业,在这个区块中,我们从GitHub下载我们的代码并运行
kubectl get nodes
右上角的 "Run Workflow"按钮,将修改内容推送到GitHub:

启动流水线
这将立即启动流水线:

Semaphore与Kubernetes集群对话
点击作业会出现它的日志:

好了,我们开始工作了!让我们通过设置一个实际的项目来进行。
为Sinatra微服务设置持续集成
我们的Sinatra应用是一个微服务,具有最小的配置和一个RSpec测试套件:
.
├── Gemfile
├── Gemfile.lock
├── README.md
├── app.rb
├── config.ru
└── spec
├── app_spec.rb
└── spec_helper.rb
我们再来设置持续集成管道。
编辑工作流程,删除我们上面创建的块。我们将设置一个管道,看起来像这样:

持续集成管线
在Semaphore上,区块是按顺序运行的,而区块内的作业是并行运行的。CI管道包含两个块,一个用于安装依赖关系,另一个用于运行测试。这是一个例子,如果有意义的话,你当然可以把这些块合并成一个。
点击第一个块来编辑工作。我们将在Semaphore缓存中存储我们的Ruby宝石,以避免每次提交时从头开始运行bundle install :
- 区块名称:安装依赖项
- 作业名称:捆绑安装
- 作业命令。
checkout
cache restore
bundle config set deployment 'true'
bundle install --path vendor/bundle
cache store

创建 "安装依赖性 "块
有必要在第二个块中运行bundle install ,尽管cache restore 在这时会一直恢复 gem 捆绑。这是Bundler方面的一个限制,但好在该命令会很快退出。第二个区块的命令是:
checkout
cache restore
bundle install --path vendor/bundle
bundle exec rspec

创建 "测试 "块
当你点击运行工作流>开始时,你会看到一个真正的CI流水线在Semaphore上成型了。

我们的微服务的持续集成管道
Dockerize Sinatra应用程序
下一步是在Docker容器中打包我们的Sinatra应用。下面的Dockerfile 就可以了。
# Dockerfile
FROM ruby:3.1
RUN apt-get update -qq && apt-get install -y build-essential
ENV APP_HOME /app
RUN mkdir $APP_HOME
WORKDIR $APP_HOME
ADD Gemfile* $APP_HOME/
RUN bundle config set deployment 'true'
RUN bundle install --path vendor/bundle
ADD . $APP_HOME
EXPOSE 4567
CMD ["bundle", "exec", "rackup", "--host", "0.0.0.0", "-p", "4567"]
为了验证,你可以在本地构建并运行该容器:
$ docker build -t demo .
$ docker run -p 80:4567 demo
$ curl localhost
> hello world
将Docker文件添加到GitHub:
$ git add Dockerfile
$ git commit -m "add Dockerfile"
$ git push origin master
将Docker镜像推送到Docker Hub容器注册处
部署到Kubernetes需要我们将Docker镜像推送到容器注册中心。在这个例子中,我们将使用Docker Hub。这个过程与其他注册表提供者非常相似。
推送到容器注册中心,无论是公共的还是私人的,都需要认证。例如,当你在Mac上使用Docker Desktop时,你会自动进行身份验证,与Docker Hub的通信就会正常。在CI/CD环境中,我们需要在做docker push ,使凭证可用并进行认证。
使用Semaphore秘密与Docker Hub进行认证
按照Semaphore文档中的Docker Hub说明,我们需要创建一个秘密。
在左边的菜单中,进入配置 > 秘密,点击创建新的秘密:
- 秘密的名称:"dockerhub"
- 变量:
DOCKER_USERNAME: 输入你的DockerHub用户名DOCKER_PASSWORD: 输入你的DockerHub密码。
- 单击 "保存更改"。

创建dockerhub秘密
我们现在有了从Semaphore作业推送到Docker Hub的条件。
配置一个推送来运行docker build
有了Docker构建和推送操作,我们就进入了项目的交付阶段。我们将用一个推广来扩展我们的Semaphore管道,并使用它来触发下一个阶段。
要创建一个新的推广:
- 编辑工作流程
- 向右滚动到管道中的最后一个块,并按下添加第一个推广按钮。

添加一个新的推广
-
配置新的推广:
- 勾选 "启用自动推广"选项
- 呼叫推广:"Dockerize"
-
设置新的管道来构建一个Docker镜像:
- 点击新的流水线。将其名称设为 "Docker build"。在这里我们可以改变运行该管道的机器类型,没有必要改变默认的e1-standard-2。
- 编辑新管道上的第一个块。让我们把它命名为 "Build"。
- 打开秘密部分,检查dockerhub条目。
-
在作业命令框中输入以下命令:
echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin
checkout
docker pull "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:latest || true
docker build --cache-from "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:latest -t "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:$SEMAPHORE_WORKFLOW_ID .
docker images
docker push "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:$SEMAPHORE_WORKFLOW_ID

设置构建作业
在作业的第一条命令中,我们使用dockerhub 秘密中定义的环境变量与Docker Hub进行认证。
我们使用Docker层缓存来加速容器构建过程。首先, docker pull 命令从注册表中检索一个先前构建的镜像。如果这是我们第一次运行这个操作,这个步骤将被跳过,不会失败。
通过使用--cache-from 标志和docker build ,我们重新使用了拉来的镜像中的层,从而更快地构建新的镜像。
对于docker build 和docker push 命令,我们使用SEMAPHORE_WORKFLOW_ID 环境变量,在每次构建后产生一个独特的工件。这是每个Semaphore作业中可用的环境变量之一;完整的列表见文档。
请注意,我们不是在创建一个新版本的latest 标签。我们只有在成功部署后才会这样做。
一旦你完成了作业的设置,点击运行工作流>开始。你应该有一个类似这样状态的管道。

Docker Build pipeline
根据你启动管道的分支,构建管道可能不会自动启动。默认情况下,自动推广只在主分支上触发。你可能需要点击推广来启动它。
工作日志显示,容器镜像已经被创建并推送。
Docker构建工作日志
而你的Docker注册表应该包含最新的镜像:

在Docker Hub上推送的容器镜像
部署到DigitalOcean Kubernetes上
如果你的Docker镜像是私有的,你需要使Kubernetes集群能够与Docker注册表进行认证。这样做的方法是,再次创建一个秘密,只是这次是在Kubernetes集群的一端。出于示范目的,我将向你展示如何做到这一点,尽管我们在本教程中使用的镜像是公开的。
创建一个Kubernetes秘密,从注册表中提取一个私有Docker镜像
在你的本地机器上运行以下命令,在你的Kubernetes集群上创建一个docker-registry-type secret:
$ kubectl create secret docker-registry dockerhub-user \
--docker-server=https://index.docker.io/v1/ \
--docker-username=YOUR_DOCKER_HUB_USERNAME \
--docker-password=YOUR_DOCKER_HUB_PASSWORD \
--docker-email=YOUR_EMAIL
你可以通过运行以下命令来验证该秘密:
$ kubectl get secret dockerhub-user --output=yaml
Kubernetes的秘密是base64编码的,输出结果看起来类似于:
apiVersion: v1
data:
.dockerconfigjson: eyJhdXRocyI6eyJodHR...
kind: Secret
metadata:
creationTimestamp: 2019-02-08T10:18:52Z
name: dockerhub-user
namespace: default
resourceVersion: "7431"
selfLink: /api/v1/namespaces/default/secrets/dockerhub-user
uid: eec7c39e-2b8a-11e9-a804-1a46bc991881
type: kubernetes.io/dockerconfigjson
编写一个部署清单
在你的存储库中创建一个新文件,例如,称为deployment.yml 。这个文件描述了Kubernetes所谓的部署,也就是运行你的容器化应用程序的一组pod。在这个例子中,我们将只运行应用程序的一个实例,但我们可以根据需要通过改变replicas 值来扩大它:
# deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: semaphore-demo-ruby-kubernetes
spec:
replicas: 1
selector:
matchLabels:
app: semaphore-demo-ruby-kubernetes
template:
metadata:
labels:
app: semaphore-demo-ruby-kubernetes
spec:
containers:
- name: semaphore-demo-ruby-kubernetes
image: semaphoredemos/semaphore-demo-ruby-kubernetes:$SEMAPHORE_WORKFLOW_ID
imagePullSecrets: # if using a private image
- name: dockerhub-user
现在出现的部署配置文件并不是有效的YML。我们的计划是使用一个名为envsubst的 Linux 工具(在 Mac 上也可通过 Homebrew 使用),在 Semaphore CI/CD 工作中用其值替换$SEMAPHORE_WORKFLOW_ID 。
然而,如果没有Kubernetes负载均衡器,我们的部署清单还不完整,它将在一个公共IP地址上暴露所部署的服务。将以下内容追加到同一文件中:
# deployment.yml
# ...
---
apiVersion: v1
kind: Service
metadata:
name: semaphore-demo-lb
spec:
selector:
app: semaphore-demo-ruby-kubernetes
type: LoadBalancer
ports:
- port: 80
targetPort: 4567
然后,完整的部署文件就是:
apiVersion: apps/v1
kind: Deployment
metadata:
name: semaphore-demo-ruby-kubernetes
spec:
replicas: 1
selector:
matchLabels:
app: semaphore-demo-ruby-kubernetes
template:
metadata:
labels:
app: semaphore-demo-ruby-kubernetes
spec:
imagePullSecrets:
- name: dockerhub
containers:
- name: semaphore-demo-ruby-kubernetes
image: $DOCKER_USERNAME/semaphore-demo-ruby-kubernetes:$SEMAPHORE_WORKFLOW_ID
---
apiVersion: v1
kind: Service
metadata:
name: semaphore-demo-ruby-kubernetes-lb
spec:
selector:
app: semaphore-demo-ruby-kubernetes
type: LoadBalancer
ports:
- port: 80
targetPort: 4567
你可以在你的本地机器上验证Kubernetes配置是否按预期运行,方法是将$SEMAPHORE_WORKFLOW_ID ,并运行latest :
$ kubectl apply -f deployment.yml
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
semaphore-demo-ruby-kubernetes LoadBalancer 10.245.117.152 68.183.249.106 80:30569/TCP 10s
...
将部署清单添加到GitHub:
$ git add deployment.yml
$ git commit -m "add Kubernetes manifest"
$ git push origin master
定义一个Semaphore部署管道
我们正在进入CI/CD配置的最后阶段。在这一点上,我们有一个 CI 管线和一个Docker build 管线。我们将定义第三条管道,从Docker build 手动触发,将其部署到 Kubernetes:
- 编辑工作流程
- 向右滚动到工作流的最后一个块,点击添加推广按钮。
- 将推广称为 "部署到Kubernetes"
- 点击新的流水线。将其名称设为 "部署到Kubernetes"。
- 点击新管道上的第一个块。让我们把它称为 "部署"。
- 在秘密部分:检查dockerhub和do-k8s条目。
- 将作业名称设为 "Deploy",并输入以下命令。
checkout
kubectl get nodes
kubectl get pods
envsubst < deployment.yml | tee deployment.yml
kubectl apply -f deployment.yml

设置Deploy to Kubernetes块。
添加第二个块,将最新的Docker镜像推送到Docker Hub:
- 单击 "添加块"。将其名称设为 "Tag latest release"。
- 将工作名称设为 "docker tag latest"。
- 在Secrets下,检查dockerhub和do-k8s条目。
- 在作业框中输入以下命令。
echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin
docker pull "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:$SEMAPHORE_WORKFLOW_ID
docker tag "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:$SEMAPHORE_WORKFLOW_ID "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:latest
docker push "${DOCKER_USERNAME}"/semaphore-demo-ruby-kubernetes:latest

设置标签最新发布块
部署管道有两个块:应用新的Kubernetes配置和创建新版本的latest 容器镜像,我们把它当作Git中的主分支(你的做法可能有所不同)。
一旦你点击运行工作流程>开始,Semaphore就会运行该管道。一旦Docker构建管道成功完成,点击 "推广 "按钮触发部署。

这应该会触发部署管道。

启动部署到Kubernetes
现在你可以运行kubectl get services ,查看负载均衡器的服务细节。或者,你可以打开DigitalOcean上的负载平衡器列表>网络标签部分,找到你的微服务的公共IP地址。

负载平衡器的公共IP地址
并进行测试:
$ curl 137.184.242.191
hello world :))
恭喜你!你现在有了一个完全自动化的持续交付系统。你现在有一个完全自动化的持续交付管道到Kubernetes。
自己部署一个演示应用程序
你可以自由地分叉semaphore-demo-ruby-kubernetes仓库,并创建一个Semaphore项目,将其部署在你的Kubernetes实例上。
semaphoreci-demos / semaphore-demo-ruby-kubernetes
这里有一些你可以做的潜在变化的想法:
- 引入一个暂存环境
- 首先构建一个Docker镜像,并在其中运行测试(需要Dockerfile的开发版本,因为在为生产制作镜像时最好避免安装开发和测试依赖)。
- 用更多的微服务来扩展这个项目。
- 尝试修改部署管道以进行金丝雀部署或蓝绿部署。
构建愉快 🚀


