Prerequisites

  • 具有一个 Kubernetes 集群 以部署 Spinnaker
  • 可运行 Docker 的环境 (1 vCPU, 3.75 GB) 或者是 Ubuntu,用以安装 Halyard (用于 spinnaker 的服务)
  • 对象存储 (MinIO),用于持久化 Spinnaker 的数据
  • 对象存储的 Bucket 的访问账号

安装执行步骤

安装 Halyard

可以直接使用 Docker 方式安装,这个没什么必要性,就是管理工具而已,参考附录1 [1]

首先创建映射目录

bash
1
2
mkdir ~/.hal -pv
mkdir ~/.kubeconfig -pv

然后执行 docker run 运行容器

bash
1
2
3
4
5
6
7
docker run -d -p 8084:8084 -p 9000:9000 \
    --name halyard --rm \
    -v ~/.hal:/home/spinnaker/.hal \
    -v ~/.kubeconfig:/home/spinnaker/.kube \
    -v /usr/local/bin:/usr/local/sbin \
    -v /etc/kubernetes/auth/admin.conf:/home/spinnaker/.kube/config \
    us-docker.pkg.dev/spinnaker-community/docker/halyard:stable

因为 docker 环境不能重启服务,需要修改配置文件,这里可以在外部创建一个配置文件来映射进去,这里后面会说到 GCS 的问题

bash
1
$ docker run -it --rm us-docker.pkg.dev/spinnaker-community/docker/halyard:stable cat /opt/halyard/config/halyard.yml > /tmp/halyard.yml

修改 spinnaker 部分的配置,将 enabled: true ,改为 enabled: false

yaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
spinnaker:
  artifacts:
    debian: https://us-apt.pkg.dev/projects/spinnaker-community
    docker: us-docker.pkg.dev/spinnaker-community/docker
  config:
    input:
      gcs:
        enabled: false
      writerEnabled: false
      bucket: halconfig

然后将输出的配置文件保存到宿主机,而后映射到容器内即可

bash
1
2
3
4
5
6
7
8
docker run -d -p 8084:8084 -p 9000:9000 \
    --name halyard --rm \
    -v ~/.hal:/home/spinnaker/.hal \
    -v ~/.kubeconfig:/home/spinnaker/.kube \
    -v /usr/local/bin:/usr/local/sbin \
    -v /etc/kubernetes/auth/admin.conf:/home/spinnaker/.kube/config \
    -v /tmp/halyard.yml:/opt/halyard/config/halyard.yml \
    us-docker.pkg.dev/spinnaker-community/docker/halyard:stable

安装kubectl

这部分在上一章中注明了挂载 kubectl 的路径

使用 Docker 运行的 Halyard 可以直接挂在 kubectl 到 容器内就可以了,halyard 默认的路径在 /usr/local/bin 只要避免和这个路径冲突就可以了。

halyard 中附带的 kubectl 不一定与你的集群版本一致

最后进入容器就可以管理 spinnaker 了

bash
1
docker exec -it halyard bash

生成一个 Halyard config [3]

离线安装时,需要生成一个 Halyard config 文件,默认在 ~/.hal/config

bash
1
hal config version edit --version local:1.19.2

Notes:

  • 如果是选择离线安装或者 Local 方式安装,那么 local 关键字必须加
  • 如果主机没有网,此时需要指定参数 --no-validate 来控制关闭验证,验证通常要联网
  • 对于执行大部分的 hal 命令都会在 ~/.hal/config 生成配置文件

选择云供应商

这里可以指选择一个 Kubernetes 集群将其添加到 Halyard config 中

注意,这里需要时 Kubectl 可以正常请求集群,即需要 kubectl 与 kubeconfig

但在选择 Kubernetes 作为 Halyard 的 provider 之前,可以在 Kubernetes 中创建一个新的 service 以在 Halyard 中使用。

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Run in Halyard container
CONTEXT=$(kubectl config current-context)
kubectl apply --context $CONTEXT \
    -f https://spinnaker.io/downloads/kubernetes/service-account.yml

TOKEN=$(kubectl get secret --context $CONTEXT \
   $(kubectl get serviceaccount spinnaker-service-account \
       --context $CONTEXT \
       -n spinnaker \
       -o jsonpath='{.secrets[0].name}') \
   -n spinnaker \
   -o jsonpath='{.data.token}' | base64 --decode)

kubectl config set-credentials ${CONTEXT}-token-user --token $TOKEN
kubectl config set-context $CONTEXT --user ${CONTEXT}-token-user

启用 kubernetes,并配置使用的 kubernetes 用户

bash
1
2
3
4
5
6
7
# Run in Halyard container

hal config provider kubernetes enable

ACCOUNT="my-k8s-account"
hal config provider kubernetes account add ${ACCOUNT} \
    --context ${CONTEXT}

Halyard 有几种部署 Spinnaker 服务的选项,例如Local, git 和 Distributed,这里使用 Distributed 模式,将 Spinnaker 以分布式方式部署到 Kubernetes内。

bash
1
hal config deploy edit --type distributed --account-name $ACCOUNT

配置S3存储

Spinnaker 需要外部存储,例如 S3 对象存储置,为此,我使用 Minio 作为外部存储服务。这里使用 docker 进行部署,作为学习,也可以使用 minIO 官方提供的 minIO-dev [4],可以快速在 Kubernetes 集群上部署一个单实例的 minIO

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# System
MINIO_ROOT_USER=$(< /dev/urandom tr -dc a-z | head -c${1:-4})
MINIO_ROOT_PASSWORD=$(< /dev/urandom tr -dc _A-Z-a-z-0-9 | head -c${1:-8})
MINIO_PORT="9010"

# Start the container
docker run -it -d --rm -v ~/.minio-data/:/data --name minio-4-spinnaker -p ${MINIO_PORT}:${MINIO_PORT} \
-e MINIO_ROOT_USER=${MINIO_ROOT_USER} -e  MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD} \
 minio/minio  server /data --address :${MINIO_PORT}

# This information is used in next {.1}

echo "
MINIO_ROOT_USER=${MINIO_ROOT_USER}
MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
ENDPOINT=http://$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' minio-4-spinnaker):${MINIO_PORT}
"

如果需要开启,那么需要在目录 ~/.hal/default/profiles/front50-local.yml 创建文件

bash
1
2
3
spinnaker:
  s3:
    versioning: false

然后使用以下命令进行配置到 halyard config 文件

bash
1
2
3
4
5
6
7
8
9
ENDPOINT=http://10.0.2.4:9000
MINIO_ACCESS_KEY=minio
MINIO_SECRET_KEY=minio123

echo $MINIO_SECRET_KEY | hal config storage s3 edit --endpoint $ENDPOINT \
    --access-key-id $MINIO_ACCESS_KEY \
    --secret-access-key

hal config storage edit --type s3

生成一个BOM文件

找一台有外网的主机,执行下列命令

bash
1
hal version bom 1.19.2 -q -o yaml

Note: 如果开启了 gcs.enabled: true 需要重新启动一个容器,因为这个步骤需要联网查询

bash
1
2
3
4
5
$ DOCKERID=`docker run -d --rm \
    -v ~/.hal:/home/spinnaker/.hal \
    us-docker.pkg.dev/spinnaker-community/docker/halyard:stable`

$ docker exec -it ${DOCKERID} hal version bom 1.19.2 -q -o yaml && docker stop ${DOCKERID}

将输出的文件保存在 halyard 容器内 ~/.hal/.boms/bom/{version}.yml ,将 {version} 替换为你安装的版本,例如这里为 1.19.2

其次,要在本地执行此操作的容器内,需要在 halconfig 目录下有对应的 BOM 清单,清单格式如下:

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ tree ~/.hal/.boms/
/root/.hal/.boms/
├── bom
│   └── 1.19.2.yml
├── clouddriver
│   └── clouddriver.yml
├── deck
│   └── settings.js
├── echo
│   └── echo.yml
├── fiat
│   └── fiat.yml
├── front50
│   └── front50.yml
├── gate
│   └── gate.yml
├── igor
│   └── igor.yml
├── kayenta
│   └── kayenta.yml
├── orca
│   └── orca.yml
└── rosco
    └── rosco.yml

这些文件夹需要自行创建,并且里面的配置文件也需要自行创建,如果不知道格式如何,可以参考 Spinnaker github 仓库上,每一个上面的文件夹都是一个项目仓库,而这些仓库的根目录都存在一个 halconfig 目录,此时需要你将对应的文件保存到对应目录下,例如,clouddriver 文件夹需要选择 github.com/spinnaker/clouddriver 项目,而配置文件需要选择 {service_name}.yml 为命名的,例如 clouddriver 就需要选择 clouddriver.yml,这个需要自行下载。

可以使用下列脚本进行生成这些配置文件(需要上网)

bash
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#!/bin/bash
####################################################################################
#                         Install Spinnaker scripts for CentOS                     #
####################################################################################
set -e
START_TIME=`date +%s`
export PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
export ROOT=$(cd $(dirname $0); pwd)
export BASE_URL="https://raw.githubusercontent.com/spinnaker"
export DECK_FILE_NAME="halconfig/settings.js"
export FILE_PREFIX="halconfig"


usage(){
	cat <<EOF
Usage: $CMD <bom_file> <output_dir>
    $CMD ~/.hal/.boms/bom/1.20.0.yml ~/.hal/.boms
EOF
}


function install_json_tools(){
    which jq &> /dev/null || sudo yum install jq -y
    which yq || ( wget https://github.com/mikefarah/yq/releases/download/v4.16.2/yq_linux_amd64 \
    && chmod +x yq_linux_amd64 \
    && mv yq_linux_amd64 /usr/local/bin/yq )
}

function remove_json_tools(){
   rm -f a/usr/local/bin/yq
   rpm -e jq --force
}

function pull_packer(){
    ##check paramter##
    if [[ ${#} -ne 1 ]]; then
        echo -e "\033[32m Paramter amount error. \033[0m" && exit ${MALFORMEDPARAMTER}
    fi
    
    export SPIN_TMP_DIR=${ROOT}/spin_installer
    [ -d ${SPIN_TMP_DIR} ] || mkdir -pv ${SPIN_TMP_DIR}

    cd ${SPIN_TMP_DIR}
    git init
    # 配置远程仓库地址
    git remote add origin https://github.com/spinnaker/rosco

    # 启用 sparse checkout
    git config core.sparsecheckout true

    # 指定要克隆的目录
    echo "halconfig/packer" >> .git/info/sparse-checkout

    # 拉取远程仓库的内容
    git pull origin ${1}
    tar zcf packer.tar.gz -C ./halconfig packer
    mv packer.tar.gz ${BOM_PATH_I}/
    # clean work dir
    cd ${ROOT} && rm -fr ${SPIN_TMP_DIR}
}


function gererate_bom(){
    yq eval -o json ${BOM_FILE_NAME} |
    jq '.services' |
    jq 'del(.defaultArtifact ,.["monitoring-third-party"], .["monitoring-daemon"])' |
    jq -r 'to_entries[] | "\(.key)=\(.value)"' |
    while IFS="=" read -r key value; do
        VERSION="version-`echo $value | jq '.version'|awk -F '=' '{print $1}' | awk -F '-' '{print $1}'| tr -d '"'`"
        
        export BOM_PATH_I=${BOM_PATH}/${key}

        [ -d ${BOM_PATH_I} ] || mkdir -pv ${BOM_PATH_I}; chmod 777 ${BOM_PATH_I}

        case ${key} in
            "deck")
                curl "${BASE_URL}/${key}/${VERSION}/${FILE_PREFIX}/settings.js" -o ${BOM_PATH_I}/settings.js
                ;;
            "rosco")
                curl "${BASE_URL}/${key}/${VERSION}/${FILE_PREFIX}/${key}.yml" -o ${BOM_PATH_I}/${key}.yml
                curl "${BASE_URL}/${key}/${VERSION}/${FILE_PREFIX}/images.yml" -o ${BOM_PATH_I}/images.yml
                pull_packer ${VERSION}
                ;;
            *)
                curl "${BASE_URL}/${key}/${VERSION}/${FILE_PREFIX}/${key}.yml"  -o ${BOM_PATH_I}/${key}.yml

        esac
    done
    chmod 777 ${BOM_PATH} -R
}


function MAIN(){
    ##check user##
    if [[ $UID != 0 ]];then
        echo -e "\033[41;05m Sorry, this script must be run as root! \033[0m"
        exit ${ILLEGALUSER}
    fi

    ##check paramter##
    if [[ ${#} -lt 2 ]]; then
        usage && exit ${MALFORMEDPARAMTER}
    fi

    export BOM_FILE_NAME=$1; shift
    export BOM_PATH=$1; shift
    
    ##cheking command line##

    install_json_tools
    
    ##processing bom##
    gererate_bom

	END_TIME=`date +%s`
	EXECUTING_TIME=`expr $END_TIME - $START_TIME`
	echo -e "\033[42;30m Time had spent $EXECUTING_TIME seconds. \033[0m"
	echo -e "\033[40;34m ######################################################### \033[0m"
	echo -e '\n'
}

MAIN $1 $2

在执行脚本时需要在容器运行的宿主机进行,如果这台主机没有网络,那么可以在其他机器执行

额外下载一个 packer.tar.gz

这里 进入文件夹 rosco/master rosco 有一个文件夹叫packer,这将其移至文件夹并解压缩为 packer.tar.gz

bash
1
2
3
mv ~/.hal/.boms/rosco/master/packer.tar.gz ~/.hal/.boms/rosco
cd ~/.hal/.boms/rosco
tar xvf packer.tar.gz

为BOM服务配置local关键字

对于离线安装,我们需要为 BOM 中的每个服务使用的镜像名称都增加一个 local: 前缀,这是官方的固定格式 [5]

yaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
dependencies:
  consul:
    version: 0.7.5
  redis:
    version: 2:2.8.4-2
  vault:
    version: 0.7.0
services:
  clouddriver:
    commit: 024b9220a1322f80ed732de9f58aec2768e93d1b
    version: local:6.4.3-20191210131345
...

配置镜像获取源

这里可以选择 直接 docker 导入镜像到每个 Kubernetes worker 节点上,也可以选择配置私有镜像仓库。

如果需要使用私有镜像,那么需要修改 VERSION.yml 中的dockerRegistry 选项,将其修改为你自己的镜像仓库

yaml
1
2
3
4
5
6
artifactSources:
  debianRepository: https://dl.bintray.com/spinnaker-releases/debians
  #dockerRegistry: gcr.io/spinnaker-marketplace
  dockerRegistry: private-docker-registry/repository-name
  gitPrefix: https://github.com/spinnaker
  googleImageProject: marketplace-spinnaker-release

或者使用 docker save 通过命令导出镜像为 tar.gz 文件,然后导入到所有的 Kubernetes 的工作节点上

部署

可以直接执行 hal deploy 命令可以进行部署,更新,删除等操作

bash
1
2
hal deploy apply # 部署
hal deploy clean # 一键清理已经部署的服务

在默认部署情况下,igor 与 fiat 是不开启的,如果你配置了授权与CI的配置,那么会部署上这两个服务sss

image-20230723163140283

这里会生成 Kubernetes 的资源,而手动创建的资源会存在 S3 对象存储中

Troubleshooting

Could not load “versions.yml” from config bucket: xx [2]

这是因为默认情况下从GCS读取配置文件,可以通过修改配置文件 /opt/spinanker/config/halyard-local.yml 关闭 gcs 功能(或 /opt/halyard/config/halyard.yml

yaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
server:
  port: 8064

...

spinnaker:
  artifacts:
    debian: https://us-apt.pkg.dev/projects/spinnaker-community
    docker: us-docker.pkg.dev/spinnaker-community/docker
  config:
    input:
      gcs:
        enabled: true
      writerEnabled: false
      bucket: halconfig

Notes: 修改完成后需要重启进程,并且修改时需要使用root用户进入容器内

text
1
2
docker exec -it 4f3c037d2e3c bash
hal shutdown

Unable to retrieve profile “clouddriver.yml”

bash
1
2
Validation in Global:
! ERROR Unable to retrieve profile "clouddriver.yml": connect timed out

解决:BOM需要按照固定格式,创建对应每个配置文件的清单

Unable to retrieve profile “versions.yml”

bash
1
2
Validation in Global:
! ERROR Unable to retrieve profile "versions.yml": connect timed out

解决:关闭 GCS 即可

No persistent storage type was configured

bash
1
2
Validation in Global:
! ERROR No persistent storage type was configured.

解决:hal config storage s3 edit

Error retirveing contentes of archive packer.tar.gz

bash
1
2
Validation in Global:
! ERROR Error retirveing contentes of archive packer.tar.gz

解决:拷贝对应服务的 github 仓库中的 packer 文件夹

No profile reader exists to read

bash
1
2
3
! ERROR No profile reader exists to read '6.7.1-20200319123809'.
  Consider setting 'spinnaker.config.input.gcs.enabled: true' in
  /opt/spinnaker/config/halyard.yml

解决:因为 bom 文件中镜像没有设置 local

Access to XMLHttpRequest at ‘xxx’ has been blocked by CORS policy

如下图所示:

image-20230719230037546

官方给出的检查方法是“排查 gate 服务的可用性” [8],但检查 gate 日志没有问题,service ip 请求也是通的

bash
1
2
3
4
5
2023-07-19 15:01:05.150  INFO 1 --- [applications-10] c.n.s.g.s.internal.ClouddriverService    : ---> HTTP GET http://spin-clouddriver.spinnaker:7002/applications?restricted=false&expand=true
2023-07-19 15:01:05.186  INFO 1 --- [applications-10] c.n.s.g.s.internal.ClouddriverService    : <--- HTTP 200 http://spin-clouddriver.spinnaker:7002/applications?restricted=false&expand=true (31ms)
2023-07-19 15:01:05.227  INFO 1 --- [-applications-9] c.n.s.g.s.internal.Front50Service        : <--- HTTP 200 http://spin-front50.spinnaker:8080/v2/applications?restricted=false (83ms)
2023-07-19 15:01:08.343  INFO 1 --- [TaskScheduler-6] c.n.s.gate.plugins.deck.DeckPluginCache  : Refreshing plugin cache
2023-07-19 15:01:08.343  INFO 1 --- [TaskScheduler-6] c.n.s.gate.plugins.deck.DeckPluginCache  : Cached 0 deck plugins

请求 service ip + port

bash
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ curl 10.111.192.125:8084 -vv
* About to connect() to 10.111.192.125 port 8084 (#0)
*   Trying 10.111.192.125...
* Connected to 10.111.192.125 (10.111.192.125) port 8084 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 10.111.192.125:8084
> Accept: */*
> 
< HTTP/1.1 302 
< Access-Control-Allow-Credentials: true
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT, PATCH
< Access-Control-Max-Age: 3600
< Access-Control-Allow-Headers: x-requested-with, content-type, authorization, X-RateLimit-App, X-Spinnaker-Priority
< Access-Control-Expose-Headers: X-AUTH-REDIRECT-URL
< X-SPINNAKER-REQUEST-ID: 6b96a924-9bcb-496c-9389-12cc6834aff7
< X-Content-Type-Options: nosniff
< X-XSS-Protection: 1; mode=block
< Cache-Control: no-cache, no-store, max-age=0, must-revalidate
< Pragma: no-cache
< Expires: 0
< X-Frame-Options: DENY
< Location: http://spin-deck.spinnaker:9000
< Content-Length: 0
< Date: Wed, 19 Jul 2023 15:01:28 GMT
< 
* Connection #0 to host 10.111.192.125 left intact

浏览器访问 gate url 也是正常的

image-20230719230307938

访问首页提示如下错误,但是单独访问 gate 页面没有问题

报错如下:

text
1
Access to XMLHttpRequest at 'http://gate.spinnaker.fuck:30080/credentials?expand=true' from origin 'http://deck.spinnaker.fuck:30080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.Access to XMLHttpRequest at 'http://gate.spinnaker.fuck:30080/credentials?expand=true' from origin 'http://deck.spinnaker.fuck:30080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

image-20230723162227426

图:spinnaker首页报错 - 跨源资源共享问题

解决:如果使用 Halyard 部署 Spinnaker,则可以使用以下设置创建文件 ~/.hal/default/profiles/gate-local.yml

yaml
1
2
cors:
  allowedOriginsPattern: {you gate url}

Reference

[1] Install Halyard on Docker

[2] ERROR Could not load “versions.yml” from config bucket: 403 #3920

[3] Spinnaker: How to bring custom boms into spinnaker pod to be able to deploy it with hal?

[4] MinIO Object Storage for Kubernetes

[5] BOMs and Configuration on your Filesystem

[6] ! ERROR No persistent storage type was configured. #5875

[7] Install in Air Gaped Environment

[8] I can’t load the Applications screen

[9] use k8s cluster private, how to access? not use localhost! #4689