k8s + jenkins + kubeSphere部署应用
这里的k8s有两层意义,第一层就是把jenkins的agent部署到k8s里面,以实现各种一次性的独立的构建环境,第二层意义是把项目代码部署到k8s里面
说明:以下的操作是在win11专业版上进行的,安装wsl2,在win上面用linux,jenkins直接安装在linux里面
必备环境
- 安装wsl2,选ubuntu, 后续操作都是在ubuntu中进行
- 安装jenkins
- 安装docker engine和docker-compose
- 安装minikube
- 安装kubectl
- 安装harbor
wsl2安装ubuntu
shell
# 会自动安装ubuntu
wsl --install
安装jenkins
Jenkins Debian Packages 大概步骤就是先下载个啥.asc文件,然后转换成jenkins.list,再然后更新仓库列表,然后就能找到jenkins官方下载地址下载安装
安装docker engine
安装好后用docker info,测试是否ok,第一次engine可能启动比较慢会导致docker info提示连不上,可以稍等会儿,后续每次docker engine会跟随系统自动启动
如果按上面的链接操作安装,则直接安装了docker compose plugin,就可以通过docker compose执行原先的docker-compose命令,或者单独安装一个docker-compose
ps: sudo docker run hello-world可能镜像会下载不了,需要魔法
json
// 这一步是为了后续能以http的形式访问本地harbor
// 修改下 /etc/docker/daemon.json
// 添加这么一段,文件如果不存在就新建一个
{
"insecure-registries":["你的ubuntu ip"]
}
安装minikube
shell
# 安装好后执行,这一步依赖docker,会下载相关镜像生成一个minikube container
minikube start --drvier=docker --insecure-registry=linux的ip地址
安装kubectl
安装harbor
这个安装没有参考官方,感觉官方的安装写的贼复杂,harbor安装依赖docker和docker-compose
shell
# 从github下载最新的安装包,包里面有相关docker镜像,几百M,下载估计比较慢
# 1. liunx下载命令,或者用win下载,然后拷贝到linux, 所有版本列表https://github.com/goharbor/harbor/releases
wget https://github.com/goharbor/harbor/releases/download/v2.7.4/harbor-offline-installer-v2.7.4.tgz
# 2. 解压,并修改配置文件
tar zxf harbor-offline-installer-v2.7.4.tgz
# 默认有个harbor.yml.tmpl,直接从这个文件复制下,然后修改harbor.yml
cp harbor.yml.tmpl harbor.yml
harbor.yml的部分
shell
# The IP address or hostname to access admin UI and registry service.
# DO NOT use localhost or 127.0.0.1, because Harbor needs to be accessed by external clients.
hostname: 172.22.109.126 # 地址就按上面提示的用ubuntu的ip,可以通过ifconfig,找到eth0开头的那一段,就是ubuntu的ip
# http related config
http:
# port for http, default is 80. If https enabled, this port will redirect to https port
port: 80
# https相关的都注释掉,这个搞起来麻烦,当然这一块简单了,后面docker和minikube都需要特别处理下这个问题
# https related config
# https:
# # https port for harbor, default is 443
# port: 443
# # The path of cert and key files for nginx
# certificate: /root/certs/reg.westos.org.crt
# private_key: /your/private/key/path
shell
# 3. 进入解压后的harbor目录
./prepare # 做一些准备工作
# 取出里面的镜像,创建container,这一步可能遇到permission denied,就是没权限,那可以加上 sudo ./install.sh
# 我用的就是 sudo ./install.sh,但又碰上找不到/root/.docker/docker-desktop/docker.sock的问题,可能是因为我先装过win版的docker desktop导致的
# 后面用了软连接处理的 ln s /var/run/docker.sock /root/.docker/docker-desktop/docker.sock
./install.sh
# 浏览器访问
# 直接打开localhost就行,默认是80端口
具体步骤
- 进入wsl2
- 启动minikube
- 启动harbor
- 启动jenkins,访问jenkins的web控制台,安装k8s的各种插件
- 构建集成kubectl和使用root用户的jenkins agent镜像
- 编写k8s的deployment.yml文件,编写Jenkinsfile,编写Dockerfile
- 创建pipline项目,填写项目信息,流水线选择pipline script from scm,填入信息,保存,执行构建,构建成功后用minikube service serviceName暴露服务,用浏览器查看
进入wsl2
可以搜索wsl,或者ubuntu就可以进入
启动minikube
shell
# 安装好后执行,这一步依赖docker,会下载相关镜像生成一个minikube container
# insecure的意思是允许以http的形式访问本地harbor,前面harbor不是图简单直接把https注释了吗
minikube start --driver=docker --insecure-registry=linux的ip地址
# 启动k8s 控制台,便捷操作deployment, pod的查看删除操作
nohup minikube dashboard &
tail nohup.out # 打开日志,ctrl + 点击对应链接可在浏览器打开对应的dashboard
启动harbor
再执行一遍前面的harbor安装步骤涉及的install.sh文件即可,就能启动了,目前没整明白为啥harbor没有在系统重启后随docker自动启动
访问localhost,输入默认用户名 "admin" 默认密码 "Harbor12345",创建仓库
启动jenkins并访问jenkins的web控制台
shell
# 启动
nohup jenkins &
# 从log中找到jenkins初始化需要的字符串
tail nohup.out
访问localhost:8080, 进入后安装k8s相关插件
- Kubernetes plugin:用于实现jenkins agent的k8s部署 参考插件官网
- Kubernetes :: Pipeline :: DevOps Steps 作用没搞懂
- Kubernetes CLI Plugin:用于实现kubectl连接远程k8s cluster,提供用户名密码和远程k8s cluster给它,就可以安装kubectl,然后操作远程k8s cluster了
添加安全凭证,后续的Jenkinsfile编写需要用到这些凭证,系统管理 -> 凭据管理 -> 添加凭据
- gitee: 用于git拉代码,选username with password,id填入gitee,可以任意起名字,后续编写Jenkinsfile指定这个名字即可
- harbor:用于登录harbor,选username with password,id填入harbor
- sa-secret: 用于访问k8s cluster,选择secret text,填入token,id设置为sa-secret,token按下面的步骤获取
- 创建用于访问k8s cluster的凭据
shell
# 创建serviceAccount,这一步没怎么研究,就跟着网上的走
# 创建一个名为jenkins-robot的sa
kubectl create serviceaccount jenkins-sa
# 绑定角色啥的,没太懂
kubectl create rolebinding jenkins-sa-binding --clusterrole=cluster-admin --serviceaccount=default:jenkins-sa
- 创建secret绑定到对应的serviceAccount
yaml
apiVersion: v1
kind: Secret
metadata:
name: sa-secret # 指定secret的名字
annotations:
kubernetes.io/service-account.name: "jenkins-sa" # 绑定到jenkins-sa这个serviceAccount上
type: kubernetes.io/service-account-token
- 查看找到secret对应的token并复制token
shell
kubectl describe secret sa-secret
# 得到一大堆字符串,类似下面这种,复制其中token字段的值
#Name: sa-secret
#Namespace: default
#
#Type: kubernetes.io/service-account-token
#
#Data
#====
#ca.crt: 1111 bytes
#extra: 4 bytes
#namespace: 7 bytes
#token: eyJhbGciOiJSUzI1NiIsImtpZCI6InNFNTdsTTBMSnUyQWNLNU4tLXl2S1Q0aDdzYm1USnFZWGtlbTRTdWhrZ00ifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6InNhLXNlY3JldCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiZTUyMTAyMDAtMGFhMS00Mjc2LWE4N2QtMjVhNTdkNjdlOTRkIiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6ZGVmYXVsdCJ9.TrL5U-3GeYW5DORcG6Rk7CC2pGa0AO_pZ8O1Ia7vIKxiZq1STvQyewtJiBSUcpKPUmsTe60uWLR6_1MY3-7EESGx3G-XUIRtF9Bt5HGEnhfZn6-BGP0brXYQXhfMcssQdePYroO9N9N3LQnyMroCHyCFhamU_-gIKNgotLiE8xO-kA5v6FFgK3rzohex-qQyOEJRDoaIESvo-FlxDu2Ai8KXsTvd0wKTG1s-C5XtgUPczJuHUW8bHepuwimUsvtB4vI_XoygaeicOT_uajd9Hh5tVeX4pzl_vwSolrH-dVwEGjUCoEVp6-wDjs5ox0GEz4tFvQzIcL8pFbjPHuOibw
创建cloud,即指定连接哪个k8s集群
创建即可,后续的kubernetes plugin的podTemplate如果不指定cloud会自动使用这个(猜的)
系统管理 -> clouds -> new cloud,填入名称k8s,选择kubernetes,如果没有这个选项也许你还没安装kubernetes plugin
yaml
# 1. Kubernetes 地址: 用下面的命令获取,取第一个
kubectl cluster-info
# 2. 勾选 "禁用 HTTPS 证书检查"
# 3. 凭据选择前面创建的sa-secret然后连接测试,通过了表示可以连接到k8s cluster,即后续的jenkins agent可以部署到k8s上
# 4.Jenkins 地址: 不填就会用jenkins系统配置里面的地址,填的话不要用localhost或者127,要用本地真实ip
# Jenkins 通道不用填
# 保存即可
构建集成kubectl和使用root用户的jenkins agent镜像
构建自己的agent镜像,因为用默认镜像的话是使用不了kubectl命令的,而尝试挂载host中的kubectl又失败了,所以自己打包一个安装了kubectl的版本
Dockerfile
# 这个版本应该是随意的,我写这个是前面使用过程中发现我安装的kubernets plugins自动用的这个版本,也可以用最新版吧
FROM jenkins/inbound-agent:3192.v713e3b_039fb_e-1
# 修改为root用户启动,这一步很重要,默认jenkins agent用的是jenkins用户启动,会导致访问不了主机的docker.sock
# 不过也可以配置 kubernets plugins的runAsUser参数, runAsUser: '0', 也表示用root用户启动
USER root
# 这一步kubectl的版本号,需要保持和host一致,可以通过 kubectl version查看版本
ARG KUBECTL_VERSION=1.28.4
# Install kubectl (same version of aws esk)
RUN curl -sLO https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl && \
mv kubectl /usr/bin/kubectl && \
chmod +x /usr/bin/kubectl
ENTRYPOINT ["/usr/local/bin/jenkins-agent"]
shell
# 构建镜像并推送
docker build -t localhost/my-rep/agent1:latest
docker push localhost/my-rep/agent1:latest
编写Dockerfile,Jenkinsfile,k8s配置文件
先在gitee上创建个项目,然后用vue-cli进行下初始化,在新建个deploy目录,在里面创建deployment.yml, Dockerfile和Jenkinsfile放在根目录
Dockerfile
Dockerfile
# 从docker hub下载nginx镜像,然后复制dist目录下的文件到/usr/share/nginx/html/,因为nginx启动后,主页默认在这个目录
FROM nginx:alpine
COPY dist/ /usr/share/nginx/html/
创建一个secret
shell
kubectl create secret docker-registry my-docker-secret \
--docker-username=admin \
--docker-password=Harbor12345 \
--docker-server=你的ubuntu ip
k8s deployment.yml
shell
# 创建deployment,名称为nginx,使用镜像为IMAGE_PATH,这是个占位字符,后续在pipline中会用正则把它替换成真正的镜像地址
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: IMAGE_PATH
ports:
- containerPort: 80
imagePullSecrets:
- name: my-docker-secret # 指定一个前面创建的docker-registry类型的secret,用于拉取harbor镜像
---
# 创建一个service,用于暴露pod的80端口
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
nodePort: 80
type: NodePort
Jenkinsfile
shell
def HARBOR_URL = '你的ubuntu IP'
// podTemplate用法是kubernetes plugins提供的,也可以用声明式语法,都差不多,kubernetes plugins官网能看到各种用法
//
podTemplate(volumes: [
// 挂载主机的docker,然后可以使用docker命令,类似于win上的快捷方式,不过这一步要用前面自己构建的容器,否则默认的启动用户没有访问docker.sock的权限
hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock'),
hostPathVolume(mountPath: '/usr/bin/docker', hostPath: '/usr/bin/docker'),
hostPathVolume(mountPath: '/home/jenkins/agent/.pnpm-store', hostPath: '/root/.local/share/pnpm/'), // 挂载pnpm的全局存储库,减小pnpm安装包的时间,这一步如果有问题也可以去除
],
// 或者配置 runAsUser: '0'也可以启用root用户
// 配置容器信息,自己构建容器,加入了kubectl和使用root用户
containers: [
containerTemplate(
name: "jnlp",
image: "${HARBOR_URL}/my-rep/agent1:latest"
),
containerTemplate(name: "nodejs", image: "node:18-alpine", command: 'sleep', args: '99d') // 要加上sleep命令,否则container启动后自动关闭,没搞懂为啥
]) {
node(POD_LABEL) {
// 拉取代码并打包
stage("clone") {
git credentialsId: 'gitee', url: 'https://gitee.com/xxx/test-jenkins.git'
container("nodejs") {
sh '''npm install -g pnpm@7
pnpm i
pnpm build
'''
}
}
// 构建镜像
stage("build") {
echo "开始构建docker镜像"
sh "docker build -t ${HARBOR_URL}/my-rep/nginx:${env.BUILD_NUMBER} ."
echo "构建结束"
}
// 上传镜像到私有镜像库
stage("publish") {
// 获取harbor的用户命和密码
withCredentials([usernamePassword(credentialsId: 'harbor', passwordVariable: 'HARBOR_SECRET_PSW', usernameVariable: 'HARBOR_SECRET_USR')]) {
echo "开始推送镜像"
sh "docker login -u ${HARBOR_SECRET_USR} -p ${HARBOR_SECRET_PSW} http://${HARBOR_URL}"
sh "docker push ${HARBOR_URL}/my-rep/nginx:${env.BUILD_NUMBER}"
echo "推送结束"
}
}
// 进行k8s发布
stage("deploy") {
// withKubeConfig由kubernetes cli提供,用于访问k8s cluster
// 这一步配置内网ip很重要
// 这个serverUrl不是通过kubectl cluster-info得到的那个,那个是外部访问的ip,而这里因为agent是部署在k8s里面的,和k8s cluster在同一个内部网络,需要用内网地址
// kubectl get services 得到下面的参数,取kubernetes这个ip加端口
// kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 23h
// nginx-service NodePort 10.108.227.72 <none> 80:30080/TCP 23h
withKubeConfig(credentialsId: 'sa-secret', serverUrl: 'https://10.96.0.1:443') {
// 进入deploy目录,因为我项目的配置文件都放在了deploy目录下
dir('deploy') {
def image_name = "${HARBOR_URL}\/my-rep\/nginx:${env.BUILD_NUMBER}"
echo "替换image路径"
sh "sed -i 's/IMAGE_PATH/${image_name}/g' deployment.yml"
echo "部署app"
sh "kubectl apply -f deployment.yml"
echo "部署结束,可以通过执行minikube service nginx-service得到一个可访问的地址,driver是docker且是wsl或win环境,NodePort类型的service的IP并不能直接被访问到,所以minikube提供了service命令"
}
}
}
}
}
一些重要概念
- jenkins master: 类型于一台主机,可以直接在上面运行任务,官方建议把master设置为禁止执行任务,只做控制用
- jenkins agent:类似于一个只干活的主机,上面会运行jenkins的一个agent.jar,然后与master连接,master给它分配任务。然后这个agent可以直接其他主机上创建,也可以由k8s帮忙创建,而如果是k8s创建,就需要kubernetes plugins,按最基本的配置的话,它会生成一个名称为jnlp的container,用于执行任务以及和master连接通信,所以如果自己完全写k8s的配置的话也要写一个name为jnlp的container,同时里面要包含agent.jar的功能,那么直接用agent官方镜像或者集成官方镜像即可
过程中遇到的坑
网络问题
有镜像下载不下来的情况;有连不上docker deamon的情况;minikube start下载镜像贼慢还有可能失败;有连不上k8s cluster的情况;有连不上jenkins master的情况
解决办法:魔法是必须的,不仅win要魔法,还得开个tun mode给wsl也来个魔法,其次最好有个队友一起搞,这样能知道一些网络问题是哪配置出错了还是就是网慢
linux权限问题
多次出现各种权限问题,最典型的报错 permission denies,这个推荐没有linux基础的花1h看看linux用户、用户组、文件权限相关的内容, 推荐鸟哥相关linux文章
KubeSphere介绍
kubeSphere就是比k8s dashboard功能更加丰富,同时它还有很多其他功能,比如集成了jenkins的pipeline,下文主要介绍怎么使用kubesphere的devops功能。kubesphere安装了devops后 会生成jenkins master Pod,当使用pipeline功能时,会自动创建jenkins agent Pod,当然也可以自定义agent
kubesphere安装及devops安装和使用
参照官网安装,装好后怎么都登录不了,最后发现是k8s版本太高导致,其实官网有说明只支持到k8s指定版本,没仔细看,反而花了巨多时间搜索登录页面的报错
然后坑又来了,devops插件怎么都装不上,最后尝试安装2.1的kubesphere版本,devops总算ok了
降kubesphere版本过程中,又涉及helm降版本安装,镜像拉不下来,参考解决k8s helm安装tiller出现ImagePullBackOff,ErrImagePull错误解决了
然后就是创建devops项目,创建流水线。可以直接复制Jenkinsfile。甚至可以直接用浏览器访问内部的jenkins,默认的service是ks-jenkins,找到其暴露的端口号即可,登录后能看到,kubesphere只是把流水线配置直接放在了pipline里面。 那么用kubesphere作为devops的优势在哪呢,也许是它简化了jenkins的各种插件的复杂配置?
反正我最后是没有用kubesphere完成过构建,可能是版本太低了,前面手动写的Jenkinsfile放进去执行各种报错,最终放弃。
最后
以上都是一些最简单的入门,很多总结是自己根据实践猜测的,并没有深入原理,还有很多概念不清不楚,因此可能会有很多错误,还请自行甄别。写这个的目的只是为了让自己了解下CI的工具集,并不打算深入。