东八区时区解决的补充(抄来的)

文章目录
一、场景描述
二、问题分析
三、解决方案
一、场景描述
  在采用容器化部署应用程序的过程中,发现写日志的时间是正确的东八区时间,但是容器内部采用 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和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共享卷容器

创建卷

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 镜像名称:版本