• 精华帖DevOps
  • 【好玩的 KubeSphere】使用 DevOps 对外提供 S2I/B2I 的 API 服务

1. KubeSphere DevOps

工具软件的价值在于,让一件很复杂的事情变得简单。从时间和人力上,显著降低成本,提升效率。

近年来,DevOps 正在以编排引擎为核心快速整合各种工具链,尝试打造更加完整一致的开发体验。KubeSphere Devops 结合 Kubernetes 为此而生。工具原子组合成流水线,更进一步,流水线也可以对外进行输出,提供 API 调用。

这些流水线可以用于替代一些 iPaaS 组件、微服务。只要能用脚本和工具原子实现的都可以考虑。下面以 S2I/B2I 为例进行实践,但边界远不止于此。

2. S2I/B2I 是什么

文档 上是这样介绍的:

     Source-to-image (S2I) 是一个直接将源代码构建成镜像的自动化构建工具,它是通过将源代码放入一个负责编译源代码的 构建器镜像(Builder image) 中,自动将编译后的代码打包成 Docker 镜像。

文档漏了一个关键点。S2I 是可以独立 KubeSphere 运行的,它是基于 CRD 实现的,相关仓库可以查看 s2ioperator,在使用上 S2I 与 B2I 一样。

实际上,s2ioperator 只是管理 S2I 的运行数据,真正干活的是 s2irun 。s2irun 是本文需要使用到的功能,也是一个独立工具,通过设置配置文件路径,执行 builder 命令即可完成 S2I/B2I 的构建。

需要说明的是,这里的 kubesphere/s2irun 镜像配置了 CMD 命令,起来之后立马就退出了,没法在 Jenkins 中使用。于是,我仅注释了 Dockerfile 的最后一行 CMD 命令,再次编译推送了镜像 shaowenchen/s2irun:v2.1.1-keep 。你也可以自行编译推送。

3. 创建 Jenkins 流水线

使用 KubeSphere 创建流水线,并将以下 Jenkinsfile 内容贴入其中。

pipeline {
  agent {
    kubernetes {
      label 's2irun'
      yaml '''apiVersion: v1
kind: Pod
spec:
  containers:
  - name: s2irun
    image: shaowenchen/s2irun:v2.1.1-keep
    tty: true
    volumeMounts:
    - name: dockersock
      mountPath: /var/run/docker.sock
    - name: dockerbin
      mountPath: /usr/bin/docker
  volumes:
  - name: dockersock
    hostPath:
      path: /var/run/docker.sock
  - name: dockerbin
    hostPath:
      path: /usr/bin/docker
      '''
      defaultContainer 's2irun'
    }
  }

  parameters {
    string(name: 'username', defaultValue: '', description: '')
    string(name: 'password', defaultValue: ' ', description: '')
    string(name: 'builderImage', defaultValue: '', description: '')
    string(name: 'tag', defaultValue: '', description: '')
    string(name: 'sourceUrl', defaultValue: '', description: '')
    string(name: 'revisionId', defaultValue: '', description: '')
  }

  stages {
    stage('s2i') {
      steps {
        catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
          sh '''
mkdir -p /root/data
touch /root/data/config.json
cat > /root/data/config.json <<-EOF
{
    "displayName":"For S2I",
    "sourceUrl":"$sourceUrl",
    "builderImage":"$builderImage",
    "tag":"$tag",
    "export":true,
    "pushAuthentication":{
        "username":"$username",
        "password":"$password"
    },
    "revisionId":"$revisionId"
}
EOF
/root/builder -v=4 -logtostderr=true
'''
        }
      }
    }

  }
}

其中:

username ,推送镜像账户名
password, 推送镜像账户密码
builderImage,构建的基础镜像,下面是可选列表:

    - kubesphere/tomcat85-java11-centos7:v2.1.0
    - kubesphere/tomcat85-java8-centos7:v2.1.0
    - kubesphere/java-11-centos7:v2.1.0
    - kubesphere/java-8-centos7:v2.1.0
    - kubesphere/nodejs-8-centos7:v2.1.0
    - kubesphere/nodejs-6-centos7:v2.1.0
    - kubesphere/nodejs-4-centos7:v2.1.0
    - kubesphere/python-36-centos7:v2.1.0
    - kubesphere/python-35-centos7:v2.1.0
    - kubesphere/python-34-centos7:v2.1.0
    - kubesphere/python-27-centos7:v2.1.0

tag,构建的产出镜像,例如 shaowenchen/hello-python:latest
sourceUrl,仓库地址,密码可以拼在 url 中,类似 http://username:password@gitlab.com
revisionId,仓库分支

可以点击执行看看效果:

4. 开启 KubeSphere ApiGateway 访问

相关文档可以参考,如何调用 API ,非常详细,这里就不粘贴复制了。

假设这里的访问方式是 NodePort: 30881 ,访问 Token 是 eyJhbGciOiJxxx 。

5. API 调用

Pipeline 相关的 API 文档:Pipeline

这里使用 Postman 进行测试,运行 Pipeline。获取运行日志的接口是 GetRunLog ,获取运行状态的接口是 SearchPipelineRuns ,这里就不一一演示。

调用 API 之前需要进入 Jenkins 的管理页面,关闭 CSRF 校验:

去掉上图所示的勾选即可。

  • 设置 Token

  • 设置参数,按照文档,输入 Jenkins 的参数

  • 调用 API

  • 页面查看运行状态

  • 检查是否推送成功

附1:适用于 2.1

关闭 Jenkins 的 CSRF 会带来安全风险,下面是一段 Python 脚本,可以用于脚本触发流水线

# -*- coding: utf-8 -*-
import json
import requests

user = "admin"
pwd = "P@88w0rd"
# 需要提前放开 APIGW 的端口
host = "http://{APIGW_HOST}:{APIGW_PORT}"
devops_name = "project-W4zNlwJLzgRB"
pipeline_name = "1"
# 参数选填,如果不填将使用默认值
parameters = json.dumps({"parameters": [{
    "name": "Value1",
    "value": "ccc"}, {
    "name": "Value2",
    "value": "dddd"}]})

pipeline_uri = "/kapis/devops.kubesphere.io/v1alpha2/devops/" \
               + devops_name + "/pipelines/" + pipeline_name + "/runs"
crumbissuer_uri = "/kapis/devops.kubesphere.io/v1alpha2/crumbissuer"
login_uri = "/kapis/iam.kubesphere.io/v1alpha2/login"

try:
    login_data = requests.post(host + login_uri, headers={
        "content-type": "application/json"}, json={
        "username": user,
        "password": pwd})
    token = login_data.json()["access_token"]
    crumb_data = requests.get(host + crumbissuer_uri, headers={
        "Authorization": "Bearer " + token})
    # 如果不带参数可以去掉 data 部分
    data = requests.post(host + pipeline_uri, headers={
        "content-type": "application/json",
        "Authorization": "Bearer " + token,
        'Jenkins-Crumb': crumb_data.json()['crumb']},
        data=parameters)
    if str(data.status_code) == "200":
        print("Jenkins job is triggered")
    else:
        print("Failed to trigger the Jenkins job")
except Exception as e:
    print("Exception: " + str(e))

附件2,适用于 3.0

# -*- coding: utf-8 -*-
import json
import requests

user = "admin"
pwd = "P@88w0rd"
# 需要提前放开 ks-apiserver 的端口
host = "http://{{API_HOST}}:30980"
devops_name = "demoz2ts7"
pipeline_name = "hello"
# 参数选填,如果不填将使用默认值
parameters = json.dumps({"parameters": [{
    "name": "Value1",
    "value": "ccc"}, {
    "name": "Value2",
    "value": "dddd"}]})

pipeline_uri = "/kapis/devops.kubesphere.io/v1alpha2/devops/" \
               + devops_name + "/pipelines/" + pipeline_name + "/runs"
crumbissuer_uri = "/kapis/devops.kubesphere.io/v1alpha2/crumbissuer"
login_uri = "/oauth/token"


def run(i):
    try:
        login_data = requests.post(host + login_uri, headers={
            "Content-Type": "application/x-www-form-urlencoded"}, data={
                "grant_type": "password",
                "username": user,
                "password": pwd
            })
        token = login_data.json()["access_token"]
        crumb_data = requests.get(host + crumbissuer_uri, headers={
            "Authorization": "Bearer " + token})
        # 如果不带参数可以去掉 data 部分
        print(crumb_data)
        data = requests.post(host + pipeline_uri, headers={
            "content-type": "application/json",
            "Authorization": "Bearer " + token,
            'Jenkins-Crumb': crumb_data.json()['crumb']},
            data=parameters)
        if str(data.status_code) == "200":
            print(str(i) + "----Jenkins job is triggered")
        else:
            print("Failed to trigger the Jenkins job")
    except Exception as e:
        print("Exception: " + str(e))


if __name__ == "__main__":
    for i in range(2):
        run(i)

refer to https://www.chenshaowen.com/