0x01:前提概要
公司业务基于AWS云服务,由于测试环境是在公司内网,所以本次实验是在内网环境完成,只是借助了AWS的镜像仓库服务(AWS-ECR)
0x01:关键名词解释
docker: 容器操作工具
kubectl: k8s集群客户端
jenkins: CI/CD工具
rancher: k8s集群管理工具
harbor: docker镜像仓库(本次实验没有选用)
aws-ecr: 亚马逊云镜像仓库
0x02:服务器资源介绍
本次实验使用了2台4c8g的虚拟服务器,操作系统为centos7.3,服务器资源描述如下:
172.100.10.13 【关闭selinux、安装docker、docker-compose、kubectl】
172.100.10.23 【关闭selinux、安装docker】
我们选用172.100.10.13这台服务器作为master节点,它上面会部署jenkins和rancher,整个服务部署情况如下图所示:
我们预先在172.100.10.13上创建好如下的目录结构:
opt/
├── docker
│ ├── jenkins
│ │ └── home
│ └── rancher_home
│ ├── auditlog
│ └── rancher
注意把/opt/docker/jenkins/home目录用户设置给jenkins用户,否则容器内无法写入该目录,~/.docker 目录也是如此,因为jenkins容器内部需要登录镜像仓库,登录操作是需要修改~/.docker/config.json文件的(参考资料)
chown -R 1000 home
chown -R 1000 /root/.docker
0x03:打通AWS服务,安装配置aws-cli
由于我们需要依赖aws的镜像仓库服务,所以需要有能操作aws的客户端工具,AWS官方提供了两种使用方式,一种是在宿主机安装,另外一种直接通过docker来完成,因为宿主机要配置aws,所以我先采用的第一种方式,参考连接:
aws-cli安装
aws-cli配置
通过以上,我们已经可以对aws进行基本的操作,通过以下命令来检测,如果能正常回显说明配置完成。
aws configure get region
配置完成以后我们回到用户目录下,看一下生成了.aws目录,里面包含了两个文件,这里先放一遍,后面我们会用到。
.aws/
├── config
└── credentials
另外一种方式可以通过docker来启动aws-cli,这里我们也可以尝试一下,后面在容器内部使用aws-cli就是用的这种方式:
docker run --rm -it -v /root/.aws:/root/.aws amazon/aws-cli configure get region
可以看到跟上面执行的结果是一样的。
0x04:宿主机登录ECR仓库,其他类型的镜像仓库原理也是一样的
完成aws-cli基础部署之后,我们需要对镜像仓库进行登录授权,这里的登录主要是为了拿到授权文件,为后面jenkins执行pipline提供配置文件,命令如下:
aws ecr get-login-password --region ********** | docker login --username ********** --password-stdin ***************.dkr.ecr.************.amazonaws.com
执行完成会看到 Login Succeed提示,这表示我们已经拿到了aws-ecr仓库的授权,可以进行镜像的推送和下载(请注意,这个登录态是有时间限制的,不同的仓库可能不一样: aws-ecr 登录说明)。同时我们本机的 ~/.docker 目录下也生成了docker的配置文件,打开看一下:
cat ~/.docker/config.json
{
"auths": {
"*********.dkr.ecr.********.amazonaws.com": {
"auth": "**************"
}
},
"HttpHeaders": {
"User-Agent": "Docker-Client/19.03.11 (linux)"
}
}
接下来我们就可以在ECR上面创建我们需要的仓库了,可以登录到aws控制台上创建,也可以采用aws-cli,命令如下:
aws ecr create-repository \
--repository-name ******* \
--image-scanning-configuration scanOnPush=false \
--region ******
以上完成后,我们开始安装rancher
0x05: 部署Rancher,创建K8S集群
我们采用docker的方式部署,我这里选择的rancher版本是2.4.3
sudo docker run -d --restart=unless-stopped -v /opt/docker/rancher_home/rancher:/var/lib/rancher/ -v /opt/docker/rancher_home/auditlog:/var/log/auditlog/ --name rancher -p 80:80 -p 443:443 rancher/rancher:v2.4.3
启动成功以后就可以通过浏览器访问了,如果访问不成功的可以耐心等待一会,启动过程需要一点时间,如果长时间没有成功,注意看一下 docker log,有防火墙限制的记得把80和443端口开放出来。
我这里通过访问 https://172.100.10.13:443/ 进入rancher ,选择语言,设置密码,然后就OK了。
这时我们还没有K8S集群,需要来创建一个,看到rancher主菜单有一个“集群”,点进去,找到添加集群按钮
由于我们是自己的服务器搭建集群,所以这里选择自定义,进去以后输入集群名称,进入下一步,
这一步把Etcd和Control都选上,然后复制下面的命令,到172.100.10.13服务器上面执行,没有报错的话就静静等待集群启动起来。
启动完成以后我们查看新创建的集群,看到状态已经是绿色的Active了,这个时候可以给集群添加主机,点击集群升级按钮,打开页面后滚动到最下方,找到添加主机命令,复制。
登录到172.100.10.23服务器,执行上面的命令,完成以后节点会自动加入集群。
集群创建好了,我们还需要一个命令行客户端来管理它,这里用到了kubectl,我们来配置一下,首先拿到kubeconfig文件,在这个位置:
复制好内容,保存到172.100.10.13服务器的 /root/.kube/config文件中,然后测试一下看看是不是生效
kubectl get pods --all-namespaces
或者利用docker操作kubectl
docker run --rm --name kubectl -v /root/.kube/config:/.kube/config bitnami/kubectl:latest get pods --all-namespaces
0x06: 部署Jenkins
docker启动jenkins脚本:
docker run -d -p 8081:8080 -p 50000:50000 \
-v /opt/docker/jenkins/home:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /usr/bin/docker:/usr/bin/docker \
-v /usr/lib64/libltdl.so.7:/usr/lib/x86_64-linux-gnu/libltdl.so.7 \
-v /root/.aws:/root/.aws \
-v /root/.docker:/root/.docker \
-v /usr/bin/kubectl:/usr/bin/kubectl \
-v /root/.kube/config:/root/.kube/config \
-u root --name jenkins --restart=always --privileged=true jenkins/jenkins
看到我们挂载了好多宿主机上的文件,这里简单介绍一下:
/opt/docker/jenkins/home # jenkins 工作的主要目录,用于保存jenkins状态数据;
/var/run/docker.sock && /usr/bin/docker && /usr/lib64/libltdl.so.7 # 用于在容器内部调用宿主机docker命令;
/root/.aws # aws-cli配置文件
/root/.docker # docker仓库授权配置文件存放于此目录下
/usr/bin/kubectl # 用于在容器内部调用宿主机kubectl命令
/root/.kube/config # k8s集群配置文件
启动完成以后,访问 http://172.100.10.13:8081/ ,按照引导进入jenkins。
接下来开始安装jenkins插件,这里我们主要用到了这些插件:
Localization: Chinese 、Git、GitHub、ssh、Blue Ocean、docker、docker-build-step、Pipeline
0x07: 通过Jenkins部署golang应用
我们的代码都托管在github上,首先在github上面创建仓库,然后把编写好的代码提交上去,注意在项目根目录下写好你的Dockerfile和Jenkinsfile,用来制作docker镜像以及jenkins发布的pipeline步骤
Jenkinsfile文件内容:
def createVersion() {
// 定义一个版本号作为当次构建的版本,输出结果 20200615175842
return new Date().format('yyyyMMddHHmmss')
}
def currentVersion = createVersion()
pipeline {
agent any
parameters {
string(
name: 'appVersion',
defaultValue: currentVersion,
description: '应用版本号'
)
}
stages {
stage('prepare') {
steps {
echo "workspace: ${WORKSPACE}"
echo "GIT_COMMIT: ${GIT_COMMIT}"
echo "APP_VERSION: ${params.appVersion} ."
}
}
stage('repository authorization') {
steps {
echo "repository authorization stage ..."
sh "docker run --rm -v /root/.aws:/root/.aws amazon/aws-cli ecr get-login-password >> ecr-login.txt"
sh "cat ecr-login.txt | docker login --username AWS --password-stdin *****.dkr.ecr.*****.amazonaws.com"
}
}
stage('build') {
steps {
echo "build stage ..."
sh "docker build -t *****:${params.appVersion} ."
}
}
stage('tag') {
steps {
echo "tag stage ..."
sh "docker tag *****:${params.appVersion} *****.dkr.ecr.*****.amazonaws.com/******:${params.appVersion}"
}
}
stage('push') {
steps {
echo "push stage ..."
sh "docker push *****.dkr.ecr.*****.amazonaws.com/******:${params.appVersion}"
}
}
stage('clean') {
steps {
echo "clean stage ..."
sh "docker rmi ******.dkr.ecr.*****.amazonaws.com/*****:${params.appVersion}"
sh "docker rmi *****:${params.appVersion}"
}
}
stage('k8s secret') {
steps {
echo "k8s secret stage ..."
// 不完美,不能做到无缝升级,查阅了官方文档,没有 exist for update 机制
sh "docker run --rm --name kubectl -w /.kube -v /root/.kube/config:/.kube/config bitnami/kubectl:latest delete --ignore-not-found=true secrets aws-ecr-secret"
sh "docker run --rm -u root --name kubectl -w /.kube -v /root/.kube/config:/.kube/config -v /root/.docker/config.json:/root/.docker/config.json bitnami/kubectl:latest create secret generic aws-ecr-secret --from-file=.dockerconfigjson=/root/.docker/config.json --type=kubernetes.io/dockerconfigjson"
}
}
stage('deploy') {
steps {
echo "deploy stage ..."
sh '''
cat > *****.yaml << EOF
# 声明一个Deployment资源对象
apiVersion: apps/v1
kind: Deployment
metadata:
name: deployment-*****
spec:
# 通过replicas声明pod个数是2
replicas: 2
# 通过标签选择被控制的pod
selector:
matchLabels:
app: *****
# 在template中定义pod
template:
metadata:
labels:
# 给pod打上标签app=*****
app: *****
spec:
imagePullSecrets:
- name: aws-ecr-secret
containers:
# 声明容器名称,注意不是pod名称,pod名称应该定义在metadata中
- name: *****
image: *****.dkr.ecr.*****.amazonaws.com/*****:${appVersion}
args: ["test"]
ports:
- name: http
containerPort: 8898
resources:
limits:
cpu: 1000m
memory: 2Gi
requests:
cpu: 500m
memory: 1Gi
# 在一个yaml文件中通过---分割多个资源对象
---
apiVersion: v1
# 声明一个Service资源对象
kind: Service
metadata:
name: service-*****
spec:
ports:
- name: http
# Service监听端口
port: 8898
# 转发到后端Pod的端口号
targetPort: 8898
# 外部访问端口
#nodePort: 30076
# service-*****将选择标签包含app=*****的pod
selector:
app: *****
type: NodePort
EOF
'''
// 不完美,还是需要依赖宿主机的 kubectl
//sh "docker run --rm --name kubectl -w /.kube -v /root/.kube/config:/.kube/config -v ${WORKSPACE}/*****.yaml:/.kube/*****.yaml bitnami/kubectl:latest apply -f ./*****.yaml"
sh "kubectl apply -f *****.yaml"
}
}
}
}
下面我们来部署应用,打开Blue Ocean,进去之后点击“创建流水线”,代码仓库选择GitHub,首次会提示你输入access_token,输入进去即可,下一步选择组织,接着选择项目仓库,最后点击创建流水线,完成以后就会按照项目中配置的Jenkinsfile开始执行发布流程,我们上面分拆了这几个步骤:预备、编译打包镜像、生成tag、推送镜像、部署镜像、清理镜像,当然这些不是固定的,可以按照你的需求自由增减。
0x08: 总结归纳
目前整体流程下来基本上没什么大问题,就是还有几个做法不是太完美:
1.部署jenkins依赖宿主机的docker,最好能在jenkins容器内直接操作docker;
2.kubectl也是同样的问题,我试了kubtctl镜像,在宿主环境下可以执行kubectl apply,但是到container环境中会报错,已经反馈issue给到作者,看看后面能不能解决;https://github.com/bitnami/bitnami-docker-kubectl/issues/16
3.Jenkinsfile 因为用到了参数化构建,第一次会构建错误,镜像拿不到版本号,但是第二次以后就都可以了,不知道是何原因
有疑问加站长微信联系(非本文作者)