2019独角兽企业重金招聘Python工程师标准>>>
参数说明
以下内容使用到的替换参数说明:
${env}:环境变量,说明开发环境,测试环境,k8s上用命名空间区分环境,所以首先需要创建命名空间: kubectl create namespace ${env}, 命名空间名称只能字母数据连接符(-);
${group}:项目组名,或产品线名;
${appName}:应用名;
${version}:应用版本;
${repository}:docker仓库
部署过程说明
步骤1:生成具体Dockerfile
Dockerfile模板如下:
FROM ${repository}/xxxx/base:v4.0
MAINTAINER [email protected]
COPY ./ROOT.war /root/tomcat/webapps
CMD ["/root/init.sh"]
说明:
- 应用的Dockerfile继承基础镜像,把war包复制到tomcat的webapps目录下,设置启动命令是init.sh脚本;
- 基础镜像:使用最小linux版本:alpine,jre依赖glibc:在from alpine的基础上需要再增加bash, glibc, jre, tomcat。tomcat工作目录是/root/tomcat。在catalina.sh 加JAVA_OPTS="-Dfile.encoding=UTF-8" 处理中文乱码;
- 应用也可以不用创建镜像,使用基础镜像,使用可共享读写数据卷把ROOT挂载到基础镜像容器上。不推荐这种做法。
步骤2:创建镜像
docker rmi -f ${repository}/${group}/${appName}:${version}
docker build -t ${repository}/${group}/${appName}:${version} --no-cache -f Dockerfile-${appName}-${env} .
先删除已存在的镜像,再创建新的镜像。
创建的镜像名称:${repository}/${group}/${appName}:${version}
步骤3:提交镜像
docker login ${repository} -u admin -p password
docker push ${repository}/${group}/${appName}:${version}
登陆仓库,再提交镜像。
步骤4:分配应用端口
应用的端口使用configMap统一管理,需要使用端口时,再导出configMap获取应用端口。configMap文件模板如下:
apiVersion: v1
kind: ConfigMap
metadata:
name: ${env}-env
namespace: ${env}
data:
${appName1}.applicationName: xxxxxx
${appName1}.port: "30001"
${appName2}.applicationName: yyyyyy
${appName2}.port: "30101"
说明:
environmentName: 对应原来setenv.sh里的environmentName;
zookeeperUrl:对应原来setenv.zookeeperUrl; ${appName1}:应用标识符,不能使用下划线;
${appName1}.applicationName:对应原来应用${appName1} setenv.applicationName;
${appName1}.port:分配给应用${appName1}的tomcat端口,这里的端口必须加双引号;
创建configMap:kubectl create --save-config -f configMap-${env}.yaml
如果configMap已存在:有2种方法升级 1)recreate:删掉configMap,再创建;
2)滚动升级:kubectl apply -f configMap-${env}.yaml
步骤5:定义deployment,service
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: ${appName}
namespace: ${env}
labels:
app: ${appName}
spec:
replicas: 1
selector:
matchLabels:
app: ${appName}
template:
metadata:
labels:
app: ${appName}
spec:
containers:
- name: ${appName}
imagePullPolicy: Always
image: ${repository}/${group}/${appName}:${version}
env:
- name: port
valueFrom:
configMapKeyRef:
name: ${env}-env
key: ${appName}.port
ports:
- containerPort: %port%
volumeMounts:
- mountPath: "/mnt/mfs"
name: hp-${appName}-${env}
subPath: ${appName}-${env}
volumes:
- name: hp-${appName}-${env}
persistentVolumeClaim:
claimName: pv-nfs
tolerations:
- key: "CriticalAddonsOnly"
operator: "Exists"
说明:
- tomcat的端口,pod的端口,service的端口,node的端口需要一致才可以访问,具体为什么不清楚。service端口限制30000~80000,node的端口限制0~32767,所以可配置的端口范围30000~32767。
- .spec.selector用来指定 label selector ,圈定Deployment管理的pod范围。 如果被指定, .spec.selector 必须匹配 .spec.template.metadata.labels,否则它将被API拒绝。
- namespace: 使用${env}来划分
- 设置pod容器的环境变量有:port。都是从${env}-env的configMap里获取
- imagePullPolicy设置成Always,始终从仓库拉取镜像
- 持久卷:pod使用的数据卷设置为 读写共享nfs持久卷。详细细节定义见附件一节。
apiVersion: v1
kind: Service
metadata:
name: ${appName}
namespace: ${env}
labels:
app: ${appName}
spec:
type: NodePort
selector:
app: ${appName}
ports:
- port: %port%
targetPort: %port%
nodePort: %port%
说明:
- type:使用NodePort:给服务分配一个集群内部可以访问的虚拟IP,并在node上绑定一个端口,这样就可以通过
:NodePort来访问该服务。 - selector: 使用selector选择pod生成endpoint。
- port:服务监听的端口
- targetPort:需要转发到后端pod的端口
- nodePort:物理机node的端口号
步骤6:创建deployment,service
port=`kubectl get configmap ${env}-env -n ${env} -o yaml|grep ${appName}.port|awk -F '"' '{print $2}'`
sed -i "s/%port%/$port/g" ${appName}-${env}.yaml
kubectl create --save-config -f ${appName}-${env}.yaml
从configmap获取端口,替换yaml文件里的占位符,创建yaml文件里定义的资源。
升级过程说明
2种方式: 1)recreate:删除应用的pod,deployment自动重新拉取镜像创建新的podkubectl delete pod pod名称 -n ${env}
;
2)滚动升级:kubectl apply -f ${appName}-${env}.yaml
基础镜像Dockerfile
FROM alpine:latest
MAINTAINER [email protected]
ENV tomcat_home=/root/tomcat \
JAVA_HOME=/root/jre1.7.0_80 \
PATH=/root/jre1.7.0_80/bin:$PATH \
LANG=zh_CN.UTF-8 \
TZ="Asia/Shanghai" \
GLIBC_PKG_VERSION=2.23-r1
ADD ./jre-7u80-linux-x64.gz /root
COPY ./tomcat $tomcat_home
COPY ./init.sh /root
WORKDIR /tmp
RUN chmod 775 /root/init.sh && \
echo "https://mirrors.aliyun.com/alpine/v3.6/main">/etc/apk/repositories && \
apk add --no-cache --update-cache wget ca-certificates bash bash-doc bash-completion && \
wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://raw.githubusercontent.com/sgerrand/alpine-pkg-glibc/master/sgerrand.rsa.pub && \
wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_PKG_VERSION}/glibc-${GLIBC_PKG_VERSION}.apk && \
wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_PKG_VERSION}/glibc-bin-${GLIBC_PKG_VERSION}.apk && \
wget https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_PKG_VERSION}/glibc-i18n-${GLIBC_PKG_VERSION}.apk && \
apk add glibc-${GLIBC_PKG_VERSION}.apk glibc-bin-${GLIBC_PKG_VERSION}.apk glibc-i18n-${GLIBC_PKG_VERSION}.apk && \
apk del curl ca-certificates && \
rm -rf /tmp/* /var/cache/apk/*
init.sh启动脚本
#!/bin/bash
#sed -i 's/autoDeploy="true">/autoDeploy="true"> /' $tomcat_home/conf/server.xml
sed -i "s/8080/$port/" $tomcat_home/conf/server.xml
sh $tomcat_home/bin/startup.sh
tail -F $tomcat_home/logs/catalina.out
持久卷定义
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs
namespace: ${env}
spec:
capacity:
storage: 50Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
nfs:
path: /data/k8s_volume
server: 192.168.0.1
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pv-nfs
namespace: ${env}
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 50Gi
最佳实践
参考 编写 Dockerfile 的最佳实践
-
优化基础镜像:使用尽量小的基础镜像,只安装必需的东西;
-
动静分离:经常变化的内容和基本不会变化的内容要分开,把不怎么变化的内容放在下层,创建出来不同基础镜像供上层使用;
-
串接 Dockerfile 命令:ENV RUN换行连着写;
-
推荐使用CMD,ENTRYPOINT次之;
-
压缩 Docker images(压缩层): Export
docker run --name demo eop-base:v3.0 echo 123 docker export --output eop-base4.0.tar demo docker rm demo docker import eop-base4.0.tar eop-base:v4.0
-
ONBUILD;
-
docker history 查看镜像层;
-
dockerignore 忽略文件;
-
不要在 Dockerfile 中单独修改文件的权限:因为 docker 镜像是分层的,任何修改都会新增一个层,修改文件或者目录权限也是如此。如果有一个命令单独修改大文件或者目录的权限,会把这些文件复制一份,这样很容易导致镜像很大。解决方案也很简单,要么在添加到 Dockerfile 之前就把文件的权限和用户设置好,要么在容器启动脚本(entrypoint)做这些修改,或者拷贝文件和修改权限放在一起做(这样最终也只是增加一层)。
-
ENV环境变量设置。用户登陆之后是一个用户shell,shell环境下可以执行shell命令或声明变量,也可以执行shell脚本程序。运行shell脚本程序就会创建一个子shell。子shell中定义的变量只能在当前子shell中有效。但是可以通过export命令把变量暴露给子shell,父shell还是不能识别这变量。source命令是在当前shell环境中执行文件中的脚本,而不是创建一个新shell环境执行文件中的脚本。
-
curl | tar 使用通道减小体积
-
多阶段构建,一般用于源码安装
-
同一个run里安装卸载软件不会增加镜像大小,一个RUN一个commit层
坑&注意项
- jenkins的参数化构建里的参数和一般参数的命名规范差不多,不能用-,使用${}引用,会把shell脚本里的$也一起替换,可以再创建一个同名的参数
- docker创建的容器默认用户 root
- 创建镜像ADD命令报错:Forbidden path outside the build context: ..
- docker重启,kubelet也需要重启
- COPY ./tomcat /root/tomcat Dockerfile的COPY,
- data source rejected establishment of connection message from server too manay connections
- executable file not found”和“no such file: windows下编写的shell回车问题 http://www.linuxidc.com/Linux/2011-09/42618.htm
相关命令
-
从Dockerfile构建image:
docker build -t xxxxxl:v1.0.002 -f Dockerfile-xxxxx . -
运行image生成container:
docker run -d -p 30101:30101 --name xxxxx xxxx:v1.0.002 -
和container交互:
docker exec -it xxxxx /bin/bash -
删除停止掉的容器 docker ps -a|grep Exited|awk '{cmd="docker rm "$1;system(cmd)}'
5)删除无用的镜像 docker images|grep none|awk '{cmd="docker rmi "$3;system(cmd)}'
6)删除日志 docker inspect -f {{.LogPath}} $(docker ps -a -q)|grep log |awk '{cmd="echo 0> "$1;system(cmd)}'
7)根据线程id 获取容器 docker inspect -f '{{.State.Pid}} {{.Id}} {{.Name}} ' $(docker ps -a -q)|grep PID