参数说明

以下内容使用到的替换参数说明:

${env}:环境变量,说明开发环境,测试环境,k8s上用命名空间区分环境,所以首先需要创建命名空间: kubectl create namespace ${env}, 命名空间名称只能字母数据连接符(-);

${group}:项目组名,或产品线名;

${appName}:应用名;

${version}:应用版本;

${repository}:docker仓库

部署过程说明

步骤1:生成具体Dockerfile

Dockerfile模板如下:

FROM ${repository}/xxxx/base:v4.0
MAINTAINER xxxx@xxxx.cn
COPY ./ROOT.war  /root/tomcat/webapps
CMD ["/root/init.sh"]

说明:

  1. 应用的Dockerfile继承基础镜像,把war包复制到tomcat的webapps目录下,设置启动命令是init.sh脚本;
  2. 基础镜像:使用最小linux版本:alpine,jre依赖glibc:在from alpine的基础上需要再增加bash, glibc, jre, tomcat。tomcat工作目录是/root/tomcat。在catalina.sh 加JAVA_OPTS="-Dfile.encoding=UTF-8" 处理中文乱码;
  3. 应用也可以不用创建镜像,使用基础镜像,使用可共享读写数据卷把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上绑定一个端口,这样就可以通过<NodeIP>: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 xxx@xx.cn

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"><Context path="" docBase="\/pv\/war\/ROOT.war"\/>/' $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 的最佳实践

  1. 优化基础镜像:使用尽量小的基础镜像,只安装必需的东西;

  2. 动静分离:经常变化的内容和基本不会变化的内容要分开,把不怎么变化的内容放在下层,创建出来不同基础镜像供上层使用;

  3. 串接 Dockerfile 命令:ENV RUN换行连着写;

  4. 推荐使用CMD,ENTRYPOINT次之;

  5. 压缩 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
    
  6. ONBUILD;

  7. docker history 查看镜像层;

  8. dockerignore 忽略文件;

  9. 不要在 Dockerfile 中单独修改文件的权限:因为 docker 镜像是分层的,任何修改都会新增一个层,修改文件或者目录权限也是如此。如果有一个命令单独修改大文件或者目录的权限,会把这些文件复制一份,这样很容易导致镜像很大。解决方案也很简单,要么在添加到 Dockerfile 之前就把文件的权限和用户设置好,要么在容器启动脚本(entrypoint)做这些修改,或者拷贝文件和修改权限放在一起做(这样最终也只是增加一层)。

  10. ENV环境变量设置。用户登陆之后是一个用户shell,shell环境下可以执行shell命令或声明变量,也可以执行shell脚本程序。运行shell脚本程序就会创建一个子shell。子shell中定义的变量只能在当前子shell中有效。但是可以通过export命令把变量暴露给子shell,父shell还是不能识别这变量。source命令是在当前shell环境中执行文件中的脚本,而不是创建一个新shell环境执行文件中的脚本。

  11. curl | tar 使用通道减小体积

  12. 多阶段构建,一般用于源码安装

  13. 同一个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

相关命令

  1. 从Dockerfile构建image:
    docker build -t xxxxxl:v1.0.002 -f Dockerfile-xxxxx .

  2. 运行image生成container:
    docker run -d -p 30101:30101 --name xxxxx xxxx:v1.0.002

  3. 和container交互:
    docker exec -it xxxxx /bin/bash

  4. 删除停止掉的容器 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

转载于:https://my.oschina.net/braveCS/blog/1504783

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐