文章目录
一、场景描述
二、问题分析
三、解决方案
一、场景描述
在采用容器化部署应用程序的过程中,发现写日志的时间是正确的东八区时间,但是容器内部采用 date 获取的时间比实际东八区的时间慢了八个小时(这里一直比较纠结这个八小时用什么动词来修饰,网上很多人用 晚 、 早,但是站在东八区来理解这两个动词,似乎都有点奇怪。纠结很久,还是以东八区的时间为参照物,来定义默认时间比东八区时间慢了八个小时吧)。
宿主机的时间
[root@~ /]# date
2020年 12月 22日 星期二 18:39:24 CST
1
2
写入日志的时间
2020-12-22 18:40:23.233 INFO [,c19fc6564922e71f,54009447514b80af,true] 1 --- [http-nio-9013-exec-10] c.m.report.common.aspect.LogAspect :
容器中的时间
[root@~ /]# docker exec -it app /bin/bash
bash-4.4# date
Tue Dec 22 10:39:54 UTC 2020
此容器的Dockerfile定义
# From 基础镜像
FROM openjdk:8-jre-alpine3.9
# 定义镜像创建者
LABEL maintainer=Rambo@xx.com
# 自定义环境变量
ENV TZ=Asia/Shanghai
# 设置本地系统时间和时区
RUN ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone
# 前端界面路径
# RUN mkdir -p /opt/java/front/sample-web
# 后端程序路径
WORKDIR /opt/java/spring-boot-sample
COPY ./*.jar ./spring-boot-sample.jar
EXPOSE 8888
ENTRYPOINT ["java", "-jar", "./spring-boot-sample.jar"]
CST China Standard Time
UTC Coordinated Universal Time
/etc/localtime 对应 date 指令本地时间
/etc/timezone 对应 java 所获取的时区
二、问题分析
通过以上 Dockerfile 可以了解到,本次实验采用的是 openjdk:8-jre-alpine3.9 最精简的版本,镜像大小 84.9M
Dockerfile 中设置本地时间和时区并写入到时间配置文件中
问题分析和研究结论:
由于 openjdk 采用的 Linux 环境的镜像是 alpine 的,其镜像特别小,集成 openjdk 也能很好的控制在 100M 左右,因此应用非常广泛,在 Docker Hub 中有大量基于 Linux alpine 版本的 JRE 镜像
# 如何验证容器采用的 Linux 内核
# 进入容器内部
docker exec -it 容器ID sh
# 查看 Linux 内核
cat /etc/issue
alpine 的镜像使用的是默认时区,并且也不存在 /usr/share/zoneinfo 路径和相关文件
为什么 Docker 容器中的日志时间却是对的呢?原因是由于以下命令将 Asia/Shanghai 配置指向了容器中的 /etc/timezone 时间,而日志输出和 JAVA 程序获取时间都是通过 /etc/timezone 这个时间配置获取的
ENV TZ=Asia/Shanghai
# 如果是基于 Alpine 的 Linux 镜像, && 前面的部分是不起作用的(因为 Alpine Linux 中都没有这个路径),所以进入容器后采用 date 查看时区还是 UTC 的时区
RUN ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone
通过以上的操作,只是将程序读取的时间设置成了东八区的时间,但是容器中的时区还是 UTC时区,如果涉及到别的程序,比如写 MySQL 的时候,就有可能存在时间不对的问题
所以,建议在构建镜像时设置了时间配置文件的同时,也需要将 Linux 时区也设置为东八区
三、解决方案
欢迎使用此镜像
FROM ramboyang/openjdk-alpine:jre-8u212-timezone-ttf
1
解决方案一
编写基于 openjdk:8-jre-alpine3.9 镜像制作包含时间组件镜像的 Dockerfile
# 基础镜像
FROM openjdk:8-jre-alpine3.9
# 设置东八区时间
ENV TZ=Asia/Shanghai
# 安装时间组件 + 安装 ttf-dejavu 解决生成验证码空指针异常问题
# 1.1 版本
# RUN apk update && apk upgrade && apk add ca-certificates && update-ca-certificates && apk add --update tzdata && cp /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone && apk add --update curl bash ttf-dejavu && rm -rf /var/cache/apk/*
# 1.2 版本
# RUN apk update && apk upgrade && apk add --update tzdata && cp /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone && apk add --update curl bash ttf-dejavu && rm -rf /var/cache/apk/*
# 1.3 版本
RUN apk add --update tzdata && cp /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone && apk add --update ttf-dejavu && rm -rf /var/cache/apk/*
# 1.4 版本
# RUN apk add --update tzdata && cp /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone && rm -rf /var/cache/apk/*
基于以上定制的 Dockerfile 构建基础镜像
# 1.1 版本
docker build -t ramboyang/openjdk-alpine:jre-8u212-cert-timezone-ttf .
# 1.2 版本
docker build -t ramboyang/openjdk-alpine:jre-8u212-timezone-ttf-bash .
# 1.3 版本
docker build -t ramboyang/openjdk-alpine:jre-8u212-timezone-ttf .
# 1.4 版本
docker build -t ramboyang/openjdk-alpine:jre-8u212-timezone .
将本地镜像推送至 Docker Hub 个人公有仓库
docker login
# 1.1 版本
docker push ramboyang/openjdk-alpine:jre-8u212-cert-timezone-ttf
# 1.2 版本
docker push ramboyang/openjdk-alpine:jre-8u212-timezone-ttf-bash
# 1.3 版本
docker push ramboyang/openjdk-alpine:jre-8u212-timezone-ttf
# 1.4 版本
docker push ramboyang/openjdk-alpine:jre-8u212-timezone
采用此镜像为基础镜像构建 JAVA 应用
# From 基础镜像(包含了东八区的设置、ttf-dejavu绘制验证码组件)
FROM ramboyang/openjdk-alpine:jre-8u212-timezone-ttf
# 定义镜像创建者
LABEL maintainer=Rambo@xx.com
# 前端界面路径
# RUN mkdir -p /opt/java/front/sample-web
# 后端程序路径
WORKDIR /opt/java/spring-boot-sample
COPY ./*.jar ./spring-boot-sample.jar
EXPOSE 8888
ENTRYPOINT ["java", "-jar", "./spring-boot-sample.jar"]
解决方案二
点击此处获取所需的非 alpine 内核的 openjdk
非 alpine 内核的 openjdk 一般都存在本地时间文件夹和时区文件,采用以下 Dockerfile 即可完成容器内本地时间和时区的设置
# From 基础镜像
FROM openjdk:8u275-jre-slim
# 定义镜像创建者
LABEL maintainer=Rambo@xx.com
# 自定义环境变量
ENV TZ=Asia/Shanghai
# 设置本地系统时间和时区
RUN ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone
# 前端界面路径
# RUN mkdir -p /opt/java/front/sample-web
# 后端程序路径
WORKDIR /opt/java/spring-boot-sample
COPY ./*.jar ./spring-boot-sample.jar
EXPOSE 8888
ENTRYPOINT ["java", "-jar", "./spring-boot-sample.jar"]
解决方案三
Dockerfile 文件中设置 JAVA 应用读取的时间配置文件
# From 基础镜像
FROM openjdk:8-jre-alpine3.9
# 定义镜像创建者
LABEL maintainer=Rambo@xx.com
# 设置本地系统时间和时区
RUN Asia/Shanghai > /etc/timezone
# 前端界面路径
# RUN mkdir -p /opt/java/front/sample-web
# 后端程序路径
WORKDIR /opt/java/spring-boot-sample
COPY ./*.jar ./spring-boot-sample.jar
EXPOSE 8888
ENTRYPOINT ["java", "-jar", "./spring-boot-sample.jar"]
将镜像启动设置容器共享宿主机的时区
docker run --name spring-boot-sample -v /etc/localtime:/etc/localtime:ro -p 8888:8888 镜像ID
1
解决方案四
从一台有时间文件夹的 Linux 服务器复制出 zoneinfo 文件夹到 Dockerfile 所在构建文件夹
cp -r /usr/share/zoneinfo Dockerfile所在构建文件夹目录
1
Dockerfile 构建镜像时将本地 zoneinfo 文件夹复制到 Alpine Linux 系统中的固定目录
# From 基础镜像
FROM openjdk:8-jre-alpine3.9
# 定义镜像创建者
LABEL maintainer=Rambo@xx.com
# 复制时间文件夹到指定 Alpine 目录
COPY zoneinfo /usr/share/zoneinfo/
# 自定义环境变量
ENV TZ=Asia/Shanghai
# 设置本地系统时间和时区
RUN ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone
# 前端界面路径
# RUN mkdir -p /opt/java/front/sample-web
# 后端程序路径
WORKDIR /opt/java/spring-boot-sample
COPY ./*.jar ./spring-boot-sample.jar
EXPOSE 8888
ENTRYPOINT ["java", "-jar", "./spring-boot-sample.jar"]
解决方案五(不推荐)
在构建镜像过程中安装时区组件(受网络的影响,会导致构建镜像变慢或者失败)
# From 基础镜像
FROM openjdk:8-jre-alpine3.9
# 定义镜像创建者
LABEL maintainer=Rambo@xx.com
#安装时区组件
RUN apk add -U tzdata
# 自定义环境变量
ENV TZ=Asia/Shanghai
# 设置本地系统时间和时区
RUN ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime && echo ${TZ} > /etc/timezone
# 前端界面路径
# RUN mkdir -p /opt/java/front/sample-web
# 后端程序路径
WORKDIR /opt/java/spring-boot-sample
COPY ./*.jar ./spring-boot-sample.jar
EXPOSE 8888
ENTRYPOINT ["java", "-jar", "./spring-boot-sample.jar"]
K8S容器时区问题解决
方法一
在制作容器镜像时候,将时区问题解决
RUN rm -f /etc/localtime && ln -sv /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo "Asia/Shanghai" > /etc/timezone
方法二
采用hostpath方式将主机时区挂载至容器内部
volumeMounts:
- mountPath: /etc/localtime
name: time_name
volumes:
- hostPath:
path: /usr/share/zoneinfo/Asia/Shanghai
type: ''
name: time_name
TOMCAT的docker镜像封装支持中文
Dockerfile如下,fonts是windows下的字体文件
FROM tomcat:8.5.54-jdk8
COPY ./fonts /usr/share/fonts/Fonts
COPY ./sources.list /etc/apt/sources.list
RUN apt update && apt install locales-all fontconfig ttf-dejavu ttf-mscorefonts-installer -y && rm -rf /var/cache/apk/* && chmod 777 -R /usr/share/fonts/Fonts && cd /usr/share/fonts && mkfontscale && mkfontdir && fc-cache -fv
ENV LANG zh_CN.utf8
#ENV LANG C.UTF-8
COPY --from=hengyunabc/arthas:latest /opt/arthas /opt/arthas
sources.list如下
deb http://mirrors.aliyun.com/debian/ buster main non-free contrib
deb http://mirrors.aliyun.com/debian-security buster/updates main
deb http://mirrors.aliyun.com/debian/ buster-updates main non-free contrib
deb http://mirrors.aliyun.com/debian/ buster-backports main non-free contrib
deb-src http://mirrors.aliyun.com/debian-security buster/updates main
deb-src http://mirrors.aliyun.com/debian/ buster main non-free contrib
deb-src http://mirrors.aliyun.com/debian/ buster-updates main non-free contrib
deb-src http://mirrors.aliyun.com/debian/ buster-backports main non-free contrib
server.xml修改
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" URIEncoding="UTF-8"/> ## 增加UTF-8部分
catalina.sh修改
JAVA_OPTS="$JAVA_OPTS -Xms16384m -Xmx16384m -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dfile.encoding=UTF8 -Djava.awt.headless=true"
## headless=true,解决验证码不显示
## -Dfile.encoding=UTF8,解决字符集问题
## -Duser.timezone=GMT+08,解决时区问题
docker的daemon.json的一些配置
{
"data-root": "/data/docker",
"log-driver":"json-file",
"log-opts": {"max-size":"500m", "max-file":"3"},
"registry-mirrors": ["https://0xj5rnq5.mirror.aliyuncs.com"]
}
docker挂载文件和挂载目录的不同
挂载文件的时候,当宿主机文件修改的时候,容器内文件内容不变
挂载目录的时候,当宿主机文件修改的时候,容器内文件会同时更改,如果删除目录,容器内目录不会丢失
原因,linux的文件inode造成的。
简单的部署docker和docker-compose的脚本
基于UBUNTU
#!/bin/env bash
apt update
apt upgrade -y
apt install apt-transport-https ca-certificates curl software-properties-common -y
curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | sudo apt-key add -
add-apt-repository "deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable"
apt update
apt install docker-ce docker-ce-cli containerd.io -y
#!/bin/env bash
COMPOSEVERSION=$(curl -s https://github.com/docker/compose/releases/latest/download 2>&1 | grep -Po [0-9]+\.[0-9]+\.[0-9]+)
curl -L "https://get.daocloud.io/docker/compose/releases/download/$COMPOSEVERSION/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
目前docker-compose已经是2.x版本了,把COMPOSEVERSION赋值1.29.2
/etc/docker/daemon.json
{
"registry-mirrors": ["https://0xj5rnq5.mirror.aliyuncs.com"],
"log-driver":"json-file",
"log-opts": {"max-size":"500m", "max-file":"3"}
}
容器运行jar包输出日志到宿主机
建立start.sh
#!/bin/env bash
java -jar -Duser.timezone=GMT+08 -Xms512m -Xmx512m xxx.jar >> /var/log/xxx/xxx.log 2>&1
建立Dockerfile
FROM java:8
RUN mkdir /opt/xxx
COPY ./xxx.jar /opt/xxx/xxx.jar
COPY ./start.sh /opt/xxx/start.sh
WORKDIR /opt/xxx
ENTRYPOINT ["sh","/opt/xxx/start.sh"]
EXPOSE 8080
编辑docker-compose.yml
version: '2'
services:
xxx:
image: xxx:openjdk-8
container_name: xxx
ports:
- "8080:8080"
mem_limit: 1024m
restart: always
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/log/xxx:/var/log/xxx
docker-compose限制资源
v2
mem_limit: 1024m
启动:
docker-compose up -d
v3
deploy:
resources:
limits:
memory: 500M
启动:
docker-compose --compatibility up -d
docker共享卷容器
创建卷
docker volume create data1
docker volume create data2
挂载卷
docker-compose.yml重点配置
注意顶级标签一定要写成这样:
volumes:
data1:
external: true #如果没有这句,就会自动创建以docker-compose.yml所在目录为名字的volumes,每个已有卷都要增加这句。
data2:
external: true
一个共享示例:
第一个docker-compose.yml
version: '3'
services:
container1:
image: busybox:latest
volumes:
- data1:/opt/data1
- data2:/opt/data2
volumes:
data1:
external: true
data2:
external: true
第二个docker-compose.yml
version: '3'
services:
container2:
image: busybox:latest
volumes:
- data1:/opt/data1
- data2:/opt/data2
volumes:
data1:
external: true
data2:
external: true
这样就实现了两个容器的数据共享
docker镜像的基本迁移
基本步骤:
查看
docker images
打包
docker save imageid > 路径/名称.tar
迁移
scp 名称.tar root@远端主机:/存放路径
导入
docker load < 路径/名称.tar
重命名
docker tag imageid 镜像名称:版本
