项目git地址:https://github.com/callicoder/spring-security-react-ant-design-polls-app
难点在于jenkins的jenkinsfile编写,如何自动的打包编译,构建镜像,最后发布到k8s上,都希望提交代码后能做到自动发布,这就需要将那些变量提前设置好,尽量做到,下次提交代码后还能自动打包编译。
架构图

环境准备
mysql,gitlab,jenkins不运行在容器中,单独运行在机器上,容器仓库用的是官方dockerhub库
| IP | Hostname | 应用 | CPU | Memory |
|---|---|---|---|---|
| 10.140.0.3 | master | k8s,mysql | 2 | 4G |
| 10.140.0.4 | node01 | k8s,jenkins | 2 | 4G |
| 10.140.0.5 | node02 | k8s,gitlab | 2 | 4G |
| 10.140.0.6 | node03 | k8s | 2 | 4G |
准备k8s集群
k8s中建立2个namespce
生产环境常用的test,dev环境
kubectl create ns testkubectl create ns dev
准备mysql
在master上部署数据库mysql5.7
wget -c https://dev.mysql.com/get/mysql80-community-release-el7-1.noarch.rpmrpm -ivh mysql80-community-release-el7-1.noarch.rpmyum -y install yum-utilsyum-config-manager --disable mysql80-communityyum-config-manager --enable mysql57-communityyum install mysql-community-server -y# 启动mysqlsystemctl start mysqld# 开机启动systemctl enable mysqld# 查看root临时密码grep 'temporary password' /var/log/mysqld.log#登陆数据库mysql -uroot -p'xxxxxx'# 使用mysql临时登录,修改root密码ALTER USER 'root'@'localhost' IDENTIFIED BY '123456@Abc';
配置mysql 参数
在/etc/my.cnf添加以下参数
sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTIONskip-name-resolvelower_case_table_names = 1max_connections = 4000character-set-server = utf8mb4collation-server = utf8mb4_unicode_ciinit_connect='SET NAMES utf8mb4'skip-character-set-client-handshake = true[mysql]default-character-set = utf8mb4
重启mysql
systemctl restart mysqld
登陆数据库
mysql -uroot -p'123456@Abc'
授权
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456@Abc';
建库给后端app用
create database polling_app;
准备jenkins
在node01上安装jenkins。
jenkins机器需要git,java,maven,nodejs,docker,kubelet这些在代码编译打包都得用到
基础yum源
yum install -y epel-releaseyum install -y wget bash-com* gityum update -y
docker yum源
wget -P /etc/yum.repos.d/ https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
kubeadm yum源
cat <<EOF >/etc/yum.repos.d/kubernetes.repo[kubernetes]name=Kubernetesbaseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64enabled=1gpgcheck=0EOF
安装java
yum -y install java-1.8.0-openjdk java-1.8.0-openjdk-devel
安装maven
wget -P /home https://mirrors.tuna.tsinghua.edu.cn/apache/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gzcd /hometar xvf apache-maven-3.6.3-bin.tar.gz
cat > /etc/profile.d/maven.sh << EOFexport MAVEN_HOME=/home/apache-maven-3.6.3export PATH=$PATH:${MAVEN_HOME}/binEOF
source /etc/profile
安装nodejs
wget -P /home https://nodejs.org/dist/v14.8.0/node-v14.8.0-linux-x64.tar.xzcd /hometar xvf node-v14.8.0-linux-x64.tar.xz
cat > /etc/profile.d/nodejs.sh << EOFexport NODE_HOME=/home/node-v14.8.0-linux-x64export PATH=$NODE_HOME/bin:$PATHEOF
source /etc/profile
安装docker
yum -y install docker-cesystemctl enable --now docker
安装kubelet
yum -y install kubelet
复制master机器上的.kube目录
scp -r master:/root/.kube /root
下载tomcat和jenkins war包
tomcat包下载地址
https://mirrors.bfsu.edu.cn/apache/tomcat/tomcat-8/
这里用8.5.57,可能会被更新掉
wget -P /opt https://mirrors.bfsu.edu.cn/apache/tomcat/tomcat-8/v8.5.57/bin/apache-tomcat-8.5.57.tar.gzcd /opttar xvf apache-tomcat-8.5.57.tar.gzrm -rf /opt/apache-tomcat-8.5.57/webapps/*wget -P /opt/apache-tomcat-8.5.57/webapps/ https://get.jenkins.io/war-stable/2.222.4/jenkins.warcd /opt/apache-tomcat-8.5.57/webapps/mv jenkins.war ROOT.war
启动jenkins
sh /opt/apache-tomcat-8.5.57/bin/startup.sh
访问jenkins,ip:8080
cat /root/.jenkins/secrets/initialAdminPassword
安装插件




Git Parameter
安装插件重启即可

安装gitlab
准备yum
cat > /etc/yum.repos.d/gitlab-ce.repo << EOF[gitlab-ce]name=Gitlab CE Repositorybaseurl=https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/gpgcheck=0enabled=1EOF
yum install gitlab-ce git
编辑配置文件
vim /etc/gitlab/gitlab.rb
##配置访问urlexternal_url 'xxxxx'##配置邮箱(可选)gitlab_rails['smtp_enable'] = truegitlab_rails['smtp_address'] = "smtp.exmail.qq.com"gitlab_rails['smtp_port'] = 465gitlab_rails['smtp_user_name'] = "tangwei@tk8s.com"gitlab_rails['smtp_password'] = "邮箱密码"gitlab_rails['smtp_domain'] = "tk8s.com"gitlab_rails['smtp_authentication'] = "login"gitlab_rails['smtp_tls'] = truegitlab_rails['smtp_enable_starttls_auto'] = truegitlab_rails['gitlab_email_from'] = "tangwei@tk8s.com"user['git_user_email'] = "tangwei@tk8s.com"gitlab_rails['gitlab_email_reply_to'] = 'tangwei@tk8s.com'
加载配置文件
gitlab-ctl reconfigure
启动gitlab
gitlab-ctl restart
第一次登陆修改root密码
新建一个后端项目polling-app-server
visibility level这块选择internal 只允许登陆的用户访问
新建一个前端项目polling-app-client
visibility level这块选择internal 只允许登陆的用户访问
准备项目
设置git参数
git config --global user.name "Administrator"git config --global user.email "admin@example.com"
下载源项目
git clone https://github.com/callicoder/spring-security-react-ant-design-polls-app
从gitlab下载空的后端项目,输入gitlab的root账号密码
git clone http://35.221.252.82/root/polling-app-server.git
从gitlab下载空的前端项目,输入gitlab的root账号密码
用http的url拉取
git clone http://35.221.252.82/root/polling-app-client.git

拷贝源目录中的前端和后端项目到gitlab项目中
用http的url拉取
mv spring-security-react-ant-design-polls-app/polling-app-server/* polling-app-server/mv spring-security-react-ant-design-polls-app/polling-app-client/* polling-app-client/
修改后端数据库信息
vim /root/polling-app-server/src/main/resources/application.properties
上传后端到gitlab
cd /root/polling-app-server/git add .git commit -m "upload server"git push -u origin master

上传前端到gitlab
cd /root/polling-app-client/git add .git commit -m "upload client"git push -u origin master

总结:前期准备完成,和实际生产环境一样,gitlab中项目先准备好,jenkins准备好,k8s环境准备好。
部署后端到k8s
jenkins添加gitlab和dockerhub的登陆账号密码
添加gitlab root账号密码,id为gitlab,后面需要用到
添加dockerhub的账号密码,id为jenkins-harbor-creds,后面需要用到
建立流水线
设置参数
需要设置4个string parameters,1个git parameters 1个choice parametes,这些变量需要用到

设置git
完整pipeline配置
进入gitlab中web ide。添加3个文件
编写Dockerfile
FROM tanmgweiwow/jdkenv:v1.0ARG JAR_FILEARG WORK_PATH="/opt/"# 环境变量ENV JAVA_OPTS="-server -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -Xdebug -Xnoagent -Djava.compiler=NONE -XX:MaxNewSize=256m -Dsun.net.inetaddr.ttl=3 -Dsun.net.inetaddr.negative.ttl=1 -Dsun.misc.URLClassPath.disableJarChecking=true -XX:+UseParNewGC -XX:ParallelGCThreads=4 -XX:MaxTenuringThreshold=9 -XX:+UseConcMarkSweepGC -XX:+DisableExplicitGC -XX:+UseCMSInitiatingOccupancyOnly -XX:+ScavengeBeforeFullGC -XX:+UseCMSCompactAtFullCollection -XX:+CMSParallelRemarkEnabled -XX:CMSFullGCsBeforeCompaction=9 -XX:CMSInitiatingOccupancyFraction=60 -XX:+CMSClassUnloadingEnabled -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+CMSPermGenSweepingEnabled -XX:CMSInitiatingPermOccupancyFraction=70 -XX:+ExplicitGCInvokesConcurrent -XX:+HeapDumpOnOutOfMemoryError -XX:-OmitStackTraceInFastThrow -Dclient.encoding.override=UTF-8 -Dfile.encoding=UTF-8" \JAR_FILE=$JAR_FILE \LANG=en_US.UTF-8 \LANGUAGE=en_US:en \LC_ALL=en_US.UTF-8 \TZ=Asia/ShanghaiCOPY $JAR_FILE $WORK_PATH/WORKDIR $WORK_PATHENTRYPOINT exec java $JAVA_OPTS -jar $JAR_FILE
编写Jenkinsfile
这里流程大致为
pipeline {agent anyenvironment {HARBOR_CREDS = credentials('jenkins-harbor-creds')GIT_TAG = sh(returnStdout: true,script: 'git describe --tags --always').trim()}stages {stage('Maven Build') {when { expression { env.GIT_TAG != null } }steps {sh 'mvn clean'sh 'mvn clean compile -U package -Dmaven.test.skip=true'}}stage('Docker Build') {when {allOf {expression { env.GIT_TAG != null }}}steps {sh "docker login -u ${HARBOR_CREDS_USR} -p ${HARBOR_CREDS_PSW}"sh "cp -f /root/.jenkins/workspace/${ENV_NAME}/target/${JAR} /root/.jenkins/workspace/${ENV_NAME}/"sh "cd /root/.jenkins/workspace/${ENV_NAME}/"sh "docker build --build-arg JAR_FILE=${JAR} -t ${DOCKER_IMAGE}:${APP_NAME}_${GIT_TAG} . "}}stage('Docker Push') {when {allOf {expression { env.GIT_TAG != null }}}steps {sh "docker push ${DOCKER_IMAGE}:${APP_NAME}_${GIT_TAG}"sh "docker rmi ${DOCKER_IMAGE}:${APP_NAME}_${GIT_TAG}"}}stage('Deploy On K8S') {when {allOf {expression { env.GIT_TAG != null }}}steps {sh "cd /root/.jenkins/workspace/${ENV_NAME}/"sh """if [ -f "k8s-templete.yml" ];thenkubectl delete -f k8s-templete.yml --namespace=${K8S_NAMESPACE} || sleep 1fi"""sh "sed -e 's#{IMAGE_URL}#${DOCKER_IMAGE}#g;s#{IMAGE_TAG}#${APP_NAME}_${GIT_TAG}#g;s#{APP_NAME}#${APP_NAME}-${GIT_TAG}#g;s#{APP}#${APP_NAME}#g' k8s-templete.tpl > k8s-templete.yml"sh "kubectl apply -f k8s-templete.yml --namespace=${K8S_NAMESPACE}"sh "rm -f ${JAR}"}}}}
编写 k8s-templete.tpl
apiVersion: apps/v1kind: Deploymentmetadata:name: {APP_NAME}-deploymentlabels:app: {APP_NAME}spec:replicas: 2selector:matchLabels:app: {APP}app-name: {APP_NAME}template:metadata:labels:app: {APP}app-name: {APP_NAME}spec:containers:- name: {APP_NAME}image: {IMAGE_URL}:{IMAGE_TAG}ports:- name: httpcontainerPort: 8080resources:limits:memory: 2Gi------apiVersion: v1kind: Servicemetadata:name: {APP}-svclabels:app: {APP}spec:selector:app: {APP}type: NodePortports:- port: 8080targetPort: 8080protocol: TCPname: httpnodePort: 32682
commit提交,会自动建立一个新的分支

jenkins构建后端


后端构建成功


记住后端的svc,这个前端需要调用 http://nodeip:32682/api
部署前端到k8s中
建立流水线


设置参数
需要设置4个string parameters,1个git parameters 1个choice parametes,这些变量需要用到



设置git

完整pipeline配置
进入gitlab中web ide。添加4个文件
编写Dockerfile
FROM node:12.4.0-alpine as buildWORKDIR /appCOPY package.json package-lock.json ./RUN npm install# Copy the main applicationCOPY . ./# ArgumentsARG REACT_APP_API_BASE_URLENV REACT_APP_API_BASE_URL=$REACT_APP_API_BASE_URL# Build the applicationRUN npm run build#### Stage 2: Serve the React application from NginxFROM nginx:latestCOPY --from=build /app/build /var/www# Copy our custom nginx configCOPY nginx.conf /etc/nginx/nginx.conf# Expose port 3000 to the Docker host, so we can access it# from the outside.EXPOSE 80ENTRYPOINT ["nginx","-g","daemon off;"]
编写Jenkinsfile
pipeline {agent anyenvironment {HARBOR_CREDS = credentials('jenkins-harbor-creds')GIT_TAG = sh(returnStdout: true,script: 'git describe --tags --always').trim()}stages {stage('NPM build and Docker Build') {when {allOf {expression { env.GIT_TAG != null }}}steps {sh "docker login -u ${HARBOR_CREDS_USR} -p ${HARBOR_CREDS_PSW}"sh "docker build --build-arg REACT_APP_API_BASE_URL=${REACT_APP_API_BASE_URL} -t ${DOCKER_IMAGE}:${APP_NAME}_${GIT_TAG} . "}}stage('Docker Push') {when {allOf {expression { env.GIT_TAG != null }}}steps {sh "docker push ${DOCKER_IMAGE}:${APP_NAME}_${GIT_TAG}"sh "docker rmi ${DOCKER_IMAGE}:${APP_NAME}_${GIT_TAG}"}}stage('Deploy On K8S') {when {allOf {expression { env.GIT_TAG != null }}}steps {sh "cd /root/.jenkins/workspace/${ENV_NAME}/"sh """if [ -f "k8s-deployment.yml" ];thensudo kubectl delete -f k8s-deployment.yml --namespace=${K8S_NAMESPACE} || sleep 1fi"""sh "sed -e 's#{IMAGE_URL}#${DOCKER_IMAGE}#g;s#{IMAGE_TAG}#${APP_NAME}_${GIT_TAG}#g;s#{APP_NAME}#${APP_NAME}-${GIT_TAG}#g;s#{APP}#${APP_NAME}#g' k8s-deployment.tpl > k8s-deployment.yml"sh "sudo kubectl apply -f k8s-deployment.yml --namespace=${K8S_NAMESPACE}"}}}}
编写k8s-deployment.tpl
apiVersion: apps/v1kind: Deploymentmetadata:name: {APP_NAME}-deploymentlabels:app: {APP_NAME}spec:replicas: 2selector:matchLabels:app: {APP}app-name: {APP_NAME}template:metadata:labels:app: {APP}app-name: {APP_NAME}spec:containers:- name: {APP_NAME}image: {IMAGE_URL}:{IMAGE_TAG}ports:- name: httpcontainerPort: 80resources:limits:memory: 1Gi---apiVersion: v1kind: Servicemetadata:name: {APP}-svclabels:app: {APP}spec:selector:app: {APP}type: NodePortports:- port: 80targetPort: 80protocol: TCPname: httpnodePort: 32680
编写nginx.conf
error_log stderr notice;worker_processes 2;worker_rlimit_nofile 130048;worker_shutdown_timeout 10s;events {multi_accept on;use epoll;worker_connections 16384;}http {default_type application/octet-stream;include /etc/nginx/mime.types;aio threads;aio_write on;tcp_nopush on;tcp_nodelay on;keepalive_timeout 5m;keepalive_requests 100;reset_timedout_connection on;server_tokens off;autoindex off;server {listen 8081;location /healthz {access_log off;return 200;}location /stub_status {stub_status on;access_log off;}}server {listen 80;root /var/www;index index.html index.htm;location / {# First attempt to serve request as file, then# as directory, then fall back to redirecting to index.htmltry_files $uri $uri/ /index.html;}# Media: images, icons, video, audio, HTClocation ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {expires 1M;access_log off;add_header Cache-Control "public";}# Javascript and CSS fileslocation ~* \.(?:css|js)$ {try_files $uri =404;expires 1y;access_log off;add_header Cache-Control "public";}# Any route containing a file extension (e.g. /devicesfile.js)location ~ ^.+\..+$ {try_files $uri =404;}}}
commit提交,会自动建立一个新的分支
jenkins构建前端
前端构建成功

访问
通过nodeport访问前端 nodeip:32080
注册一个账号测试
登陆

创建投票


如果有ingress的话,可以编写ingress访问前端
cat > ingress.yaml <<EOFapiVersion: extensions/v1beta1kind: Ingressmetadata:name: poll-ingressnamespace: testannotations:kubernetes.io/ingress.class: "nginx"spec:rules:- host: poll.tk8s.comhttp:paths:- path: /backend:serviceName: polling-app-client-svcservicePort: 80EOF

然后域名绑定公网ip即可









