Docker

06 容器内存占用过高 阅读更多

宿主机情况 一般系统内存过高的情况下,可以通过 free -m 查看当前系统的内存使用情况: sugoi@sugoi:~$ free -m 总计 已用 空闲 共享 缓冲/缓存 可用 内存: 11415 3576 764 593 7074 7001 交换: 2047 8 2039 在发现是系统内存占用高后,可以手动清理 Cache,因为 Cache 高的话,可以通过 drop_caches 的方式来清理: 清理 page cache:echo 1 > /proc/sys/vm/drop_caches 清理 dentries 和 inodes:echo 2 > /proc/sys/vm/drop_caches 清理 page cache、dentries 和 inodes:echo 3 > /proc/sys/vm/drop_caches 因为进程是运行在容器中,在 Kubernetes 集群中,若执行 drop_caches 相关命令,将会对节点上的所有其他应用程序产生影响,尤其是那些占用大量 I/O 并由于缓冲区高速缓存而获得更好性能的应用程序,可能会产生 “负面” 后果。 所以上述不是一个在容器环境下使用的好方法。 容器情况 因为容器设置了 Memory Limits,在容器在运行达到 Limits 上限,就会因 OOM 被杀掉,产生原因可能是: 频繁申请重复对象 不知名内存泄露 madvise(give advice about use of memory)策略变更 监控/判别条件有问题 容器环境的机制

05-存储驱动与磁盘文件系统 阅读更多

查看宿主局Docker的配置信息。 docker info Server: ... Storage Driver: overlay2 Backing Filesystem: xfs Supports d_type: true ... 这三个关键参数如上所示,那么就是目前的最佳搭配。 问题 在新的服务器上安装docker并运行的时候,遇到如下报错: sugoi@sugoi:~/Documents/Golang-Guide$ systemctl status docker ● docker.service - Docker Application Container Engine Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled) Active: inactive (dead) since Mon 2020-03-16 10:37:14 CST; 23h ago Docs: https://docs.docker.com 3月 16 19:36:42 sugoi dockerd[21992]: time="2020-03-16T19:36:42.508676716+08:00" level=error msg="docker error creating overlay mount to xxx invalid argument" ... 使用journalctl -u docker看不出什么问题 使用sudo dockerd --debug发现问题 ... DEBU[2020-03-16T19:36:42.508676716+08:00] falied to start daemon : error initializing graphdriver: /var/lib/docker ... storage driver (-s <DRIVER>) ... 定位到问题,也就是docker的存储驱动和文件系统不匹配,那就只要把磁盘分区重新格式化一下就好了。 解决 定位到问题了,那么问题来了,磁盘分区需要格式化为哪一种文件系统,各种文件系统有啥优劣,后面简单分析一下。 首先,配置一下Docker,执行如下命令,也就是在/etc/docker/daemon.json中配置storage-driver为overlay2。 if [[ ! -f /etc/docker/daemon.json ]]; then mkdir -p /etc/docker cat > /etc/docker/daemon.json <<EOF { "exec-opts": ["native.cgroupdriver=systemd"], "log-driver": "json-file", "log-opts": { "max-size": "100m" }, "graph":"/media/kevin/storage2/docker/images_data", "storage-driver": "overlay2", "storage-opts": [ "overlay2.override_kernel_check=true" ] } EOF fi 在daemon.json配置文件中graph在非默认目录(/var/lib/docker)时需要配置。 Docker官网上说,overlay和overlay2只支持xfs后端文件系统,并且d_type=true。 overlay2是overlay和devicemapper的升级版,能有更高的效率且更加节约磁盘inode。 docker info Server: ... Storage Driver: overlay2 Backing Filesystem: <unknow> Supports d_type: true ... 在查看问题节点发现,后端文件系统是unknown。 上一步已经修改了Docker的配置,第二步就是重新格式化Docker运行的磁盘分区为xfs(退一步ext4也行),且d_type=true。 # 创建docker默认目录 mkdir -p /var/lib/docker # 格式化磁盘分区 mkfs.xfs -f -n ftype=1 /dev/<p_name> # 挂载分区到目录上 mount /dev/<p_name> /var/lib/docker # 查看磁盘文件系统信息确认一下 xfs_info /dev/<p_name> | grep ftype=1 # 配置/etc/fstab实现开机自动挂载磁盘分区 blkid /dev/<p_name> UUID= <UUID> /var/lib/docker xfs defaults,uquota,pquota 0 0 最后,重启Docker守护进程,在检查info信息,都正常了。 sudo systemctl daemon-reload sudo systemctl restart docker

04-编码方式的坑 阅读更多

字符集只是一个规则集合的名字,对于一个字符集来说要正确编码转码一个字符需要三个关键元素: 字库表(character repertoire):相当于所有可读或者可显示字符的数据库,字库表决定了整个字符集能够展现表示的所有字符的范围 编码字符集(coded character set):用一个编码值code point来表示一个字符在字库中的位置 字符编码(character encoding form):编码字符集和实际存储数值之间的转换关系 统一字库表的目的是为了能够涵盖世界上所有的字符,但实际使用过程中会发现真正用的上的字符相对整个字库表来说比例非常低。 例如,中文地区的程序几乎不会需要日语字符,而一些英语国家甚至简单的ASCII字库表就能满足基本需求。而如果把每个字符都用字库表中的序号来存储的话,每个字符就需要3个字节(这里以Unicode字库为例),这样对于原本用仅占一个字符的ASCII编码的英语地区国家显然是一个额外成本(存储体积是原来的三倍)。 Unicode就是编码字符集,而UTF-8就是字符编码,即Unicode规则字库的一种实现形式。 随着互联网的发展,对同一字库集的要求越来越迫切,Unicode标准也就自然而然的出现。它几乎涵盖了各个国家语言可能出现的符号和文字,并将为他们编号。Unicode的编号从0000开始一直到10FFFF共分为17个Plane,每个Plane中有65536个字符。而UTF-8则只实现了第一个Plane,可见UTF-8虽然是一个当今接受度最广的字符集编码,但是它并没有涵盖整个Unicode的字库,这也造成了它在某些场景下对于特殊字符的处理困难。 UTF-8 UTF-8编码为变长编码。最小编码单位(code unit)为一个字节。一个字节的前1-3个bit为描述性部分,后面为实际序号部分。 如果一个字节的第一位为0,那么代表当前字符为单字节字符,占用一个字节的空间。0之后的所有部分(7个bit)代表在Unicode中的序号 如果一个字节以110开头,那么代表当前字符为双字节字符,占用2个字节的空间。110之后的所有部分(5个bit)加上后一个字节的除10外的部分(6个bit)代表在Unicode中的序号。且第二个字节以10开头 如果一个字节以1110开头,那么代表当前字符为三字节字符,占用3个字节的空间。110之后的所有部分(5个bit)加上后两个字节的除10外的部分(12个bit)代表在Unicode中的序号。且第二、第三个字节以10开头 如果一个字节以10开头,那么代表当前字节为多字节字符的第二个字节。10之后的所有部分(6个bit)和之前的部分一同组成在Unicode中的序号 具体每个字节的特征可见下表,其中x代表序号部分,把各个字节中的所有x部分拼接在一起就组成了在Unicode字库中的序号。 Byte 1 Byte 2 Byte3 0xxx xxxx 110x xxxx 10xx xxxx 1110 xxxx 10xx xxxx 10xx xxxx 例子 实际字符 在Unicode字库序号的十六进制 在Unicode字库序号的二进制 UTF-8编码后的二进制 UTF-8编码后的十六进制 $ 0024 010 0100 0010 0100 24 ¢ 00A2 000 1010 0010 1100 0010 1010 0010 C2 A2 € 20AC 0010 0000 1010 1100 1110 0010 1000 0010 1010 1100 E2 82 AC 3个字节的UTF-8十六进制编码一定是以E开头的 2个字节的UTF-8十六进制编码一定是以C或D开头的 1个字节的UTF-8十六进制编码一定是以比8小的数字开头的 乱码 乱码的出现是因为:编码和解码时用了不同或者不兼容的字符集。一个用UTF-8编码后的字符,用GBK去解码。由于两个字符集的字库表不一样,同一个汉字在两个字符表的位置也不同,最终就会出现乱码。 要从乱码字符中反解出原来的正确文字需要对各个字符集编码规则有较为深刻的掌握。但是原理很简单,这里用最常见的UTF-8被错误用GBK展示时的乱码为例,来说明具体反解和识别过程。 借助MySQL来操作。 编码 mysql> select hex(convert('你好' using utf8)); +-----------------------------------+ | hex(convert('你好' using utf8)) | +-----------------------------------+ | E4BDA0E5A5BD | +-----------------------------------+ 1 row in set (0.00 sec) mysql> select hex(convert('你' using utf8)); +--------------------------------+ | hex(convert('你' using utf8)) | +--------------------------------+ | E4BDA0 | +--------------------------------+ 1 row in set (0.00 sec) mysql> select hex(convert('好' using utf8)); +--------------------------------+ | hex(convert('好' using utf8)) | +--------------------------------+ | E5A5BD | +--------------------------------+ 1 row in set (0.00 sec) 中文,“你”的UTF-8编码为E4BDA0,“好”的UTF-8编码为E5A5BD。 mysql> select hex(convert('你好' using gbk)); +----------------------------------+ | hex(convert('你好' using gbk)) | +----------------------------------+ | C4E3BAC3 | +----------------------------------+ 1 row in set (0.00 sec) mysql> select hex(convert('你' using gbk)); +-------------------------------+ | hex(convert('你' using gbk)) | +-------------------------------+ | C4E3 | +-------------------------------+ 1 row in set (0.00 sec) mysql> select hex(convert('好' using gbk)); +-------------------------------+ | hex(convert('好' using gbk)) | +-------------------------------+ | BAC3 | +-------------------------------+ 1 row in set (0.00 sec) 中文,“你”的GBK编码为C4E3,“好”的GBK编码为BAC3。 整理在表格里里看: 中文 UTF-8 GBK 你 E4BDA0 C4E3 好 E5A5BD BAC3 UTF-8对中文使用了三个字节来编码,而GBK只使用了2个字节来编码。 解码 这些都是16进制的表示,所以需要前缀0x。 # gbk编码的你,用utf-8解码,结果是null mysql> select convert(0xC4E3 using utf8); +----------------------------+ | convert(0xC4E3 using utf8) | +----------------------------+ | NULL | +----------------------------+ 1 row in set, 1 warning (0.00 sec) mysql> select convert(0xC4E3 using GBK); +---------------------------+ | convert(0xC4E3 using GBK) | +---------------------------+ | 你 | +---------------------------+ 1 row in set (0.00 sec) # utf-8编码的你,用gbk解码,结果是null mysql> select convert(0xE4BDA0 using gbk); +-----------------------------+ | convert(0xE4BDA0 using gbk) | +-----------------------------+ | NULL | +-----------------------------+ 1 row in set, 1 warning (0.00 sec) mysql> select convert(0xE4BDA0 using utf8); +------------------------------+ | convert(0xE4BDA0 using utf8) | +------------------------------+ | 你 | +------------------------------+ 1 row in set (0.00 sec) 因为一个中文字太短,错误编码的时候就显示了,使用两个中文字,就能看到乱码的效果了。 # utf-8编码的你好,用gbk解码为三个字 mysql> select convert(0xE4BDA0E5A5BD using gbk); +-----------------------------------+ | convert(0xE4BDA0E5A5BD using gbk) | +-----------------------------------+ | 浣犲ソ | +-----------------------------------+ 1 row in set (0.01 sec) # gbk编码的你好,用utf-8解码为null mysql> select convert(0xC4E3BAC3 using utf8); +--------------------------------+ | convert(0xC4E3BAC3 using utf8) | +--------------------------------+ | NULL | +--------------------------------+ 1 row in set, 1 warning (0.00 sec) emoji问题 Emoji在Unicode位于\u1F601-\u1F64F区段的字符。这超过了目前常用的UTF-8字符集的编码范围\u0000-\uFFFF。 如何将emoji存入MySQL数据库。 一般MySQL数据库的默认字符集都会配置成UTF-8(三字节),而utf8mb4在5.5以后才被支持,很少会将系统默认字符集改成utf8mb4。当把一个需要4字节UTF-8编码才能表示的字符存入数据库的时候就会报错: ERROR 1366: Incorrect string value: '\xF0\x9D\x8C\x86' for column # 试图将一串Bytes插入到一列中,而这串Bytes的第一个字节是`\xF0`意味着这是一个四字节的UTF-8编码。 当MySQL表和列字符集配置为UTF-8的时候是无法存储这样的字符。 升级MySQL到5.6或更高版本,并且将表字符集切换至utf8mb4。 把内容存入到数据库之前做一次过滤,将Emoji字符替换成一段特殊的文字编码,然后再存入数据库中。之后从数据库获取或者前端展示时再将这段特殊文字编码转换成Emoji显示。假设用-*-1F601-*-来替代4字节的Emoji。

03-Chromium 阅读更多

制作镜像的时候,在里面安装了一个chromium浏览器,但是容易出现崩溃的情况。 安装 以Ubuntu基础镜像为例。 FROMubuntu:18.04RUN apt-get update \ && apt-get install -y --allow-unauthenticated chromium-browser chromium-browser-l10n chromium-codecs-ffmpeg \ && rm -rf /var/lib/apt/lists/* \ && ln -s /usr/bin/chromium-browser /usr/bin/google-chrome-stable \ && mkdir $HOME \ && echo "CHROMIUM_FLAGS='--no-first-run --no-sandbox --start-maximized --user-data-dir --disable-software-rasterizer --disable-dev-shm-usage'" > $HOME/.chromium-browser.init 报错 通过Terminal运行发现报错信息如下。 [2576:2605:0310/115342.554370:ERROR:zygote_host_impl_linux.cc(259)] Failed to adjust OOM score of renderer with pid 2644: Permission denied (13) [2765:2771:0310/115428.402995:FATAL:memory.cc(22)] Out of memory. size=262144 ... 需要增加CHROMIUM_FLAGS='--no-first-run --no-sandbox --start-maximized --user-data-dir --disable-software-rasterizer --disable-dev-shm-usage'。 参数详解 全部的参数在这里。 --no-first-run:跳过“首次运行”任务,无论它实际上是否是“首次运行”。 会被kForceFirstRun参数覆盖。这不会删除“首次运行”步骤,因此也不能防止在没有此标志的情况下下次启动chrome时发生首次运行。 --no-sandbox:对通常为沙盒的所有进程类型禁用沙盒。 --start-maximized:无论以前的任何设置如何,都以最大化(全屏)的方式启动浏览器。 --user-data-dir:浏览器存储用户配置文件的目录。 --disable-software-rasterizer:禁止使用3D软件光栅化器。 --disable-dev-shm-usage:在某些VM环境中,/dev/shm分区太小,导致Chrome发生故障或崩溃(请参阅)。 使用此标志解决此问题(临时目录将始终用于创建匿名共享内存文件)。 /dev/shm /dev/shm是Linux下一个非常有用的目录,这个目录不在硬盘上,而是在内存里。因此在Linux下,就不需要大费周折去建 ramdisk,直接使用/dev/shm/就可达到很好的优化效果。 /dev/shm/需要注意的一个是容量问题,在Linux下,它默认最大为内存的一半大小,使用df -h命令可以看到。 但它并不会真正的占用这块内存: 如果/dev/shm/下没有任何文件,它占用的内存实际上就是0字节; 如果它最大为1G,里头放有100M文件,那剩余的900M仍然可为其它应用程序所使用,但它所占用的100M内存,是绝不会被系统回收重新划分的。 默认系统就会加载/dev/shm ,它就是所谓的tmpfs,这与ramdisk(虚拟磁盘)不一样。tmpfs 可以使用 RAM,也可以使用交换分区来存储。 传统的虚拟磁盘是个块设备,并需要一个 mkfs 之类的命令才能真正地使用它,tmpfs 是一个文件系统,而不是块设备,只是安装它,就可以使用。 默认的最大一半内存大小在某些场合可能不够用,并且默认的inode数量很低一般都要调高些,这时可以用mount命令来修改/dev/shm。 mount -o size=1500M -o nr_inodes=1000000 -o noatime,nodiratime -o remount /dev/shm # 在/etc/fstab中增加配置 tmpfs /dev/shm tmpfs defaults,size=1.5G 0 0

02-字符集 阅读更多

locales 被 glibc 和其它需要本地化的应用程序和库用来解析文本(或正确的显示当前区域的某些文字样式,如货币,时间,日期,特殊字符和其他的区域格式)。 系统支持的语言保存在这里/usr/share/i18n/locales。 locale locale是一组环境变量,用于定义应用程序和Linux系统上的shell会话的语言,国家和字符编码设置(或任何其他特殊的变体首选项)。 这些环境变量由系统上的系统库和支持区域设置的应用程序使用。 locale会影响时间/日期的格式,一周的第一天,数字,货币以及根据在Linux系统上设置的语言或地区/国家/格式化的许多其他值。 设置 locale 前,需要先准备需要的 locale。要列出所有启用的locale,使用locale -a。 查看locale 要查看当前正在使用的locale和相关环境变量的信息,请使用locale或localectl实用程序。 locale:获取特定于语言环境的信息。 localectl:控制系统区域(locale)设置和键盘布局设置。 locale命名规则: <语言>_<地区>.<字符集编码><@修正值> zh_CN.UTF-8:zh表示中文,CN表示大陆地区,UTF-8表示字符集编码 locale LANG=zh_CN.UTF-8 # 优先级最低,是所有LC_变量的默认值 LANGUAGE=zh_CN:zh:en_US:en LC_CTYPE="zh_CN.UTF-8" # 用于字符分类和字符串处理,控制所有字符的处理方式 LC_NUMERIC=zh_CN.UTF-8 # 用于格式化非货币的数字显示 LC_TIME=zh_CN.UTF-8 # 用于格式化时间和日期 LC_COLLATE="zh_CN.UTF-8" # 用于比较和排序 LC_MONETARY=zh_CN.UTF-8 # 用于格式化货币单位 LC_MESSAGES="zh_CN.UTF-8" # 用于控制程序输出时所使用的语言 LC_PAPER=zh_CN.UTF-8 # 默认纸张尺寸大小 LC_NAME=zh_CN.UTF-8 # 姓名书写方式 LC_ADDRESS=zh_CN.UTF-8 # 地址书写方式 LC_TELEPHONE=zh_CN.UTF-8 # 电话号码书写方式 LC_MEASUREMENT=zh_CN.UTF-8 # 度量衡表达方式 LC_IDENTIFICATION=zh_CN.UTF-8 # locale对自身包含信息的概述 LC_ALL= # 优先级最高,它是一个宏,可通过该变量的设置覆盖所有的LC_*变量 # -a 参数显示所有可用于被设置的语言环境 localectl status System Locale: LANG=zh_CN.UTF-8 LANGUAGE=zh_CN:zh:en_US:en VC Keymap: n/a X11 Layout: cn 设置locale 如果要更改或设置本地系统,请使用update-locale程序。 修改LANG变量以设置整个系统的语言环境。 update-locale:修改全局的locale设置。 以下命令将LANG设置为en_IN.UTF-8,并删除LANGUAGE的定义。 sudo update-locale LANG=en_IN.UTF-8 LANGUAGE # OR sudo localectl set-locale LANG=en_IN.UTF-8 要配置特定的语言环境参数,需要编辑对应的变量。 sudo update-locale LC_TIME=en_IN.UTF-8 # OR sudo localectl set-locale LC_TIME=en_IN.UTF-8 可以在以下文件中找到全局语言环境设置: /etc/default/locale – on Ubuntu/Debian /etc/locale.conf – on CentOS/RHEL /etc/locale.gen - on Alpine 如果直接编辑上面的文件,则在编辑完成后需要执行locale-gen来生效。 每次更新glibc的时候,都会自动执行一下locale-gen,建议开启UTF-8。 要为单个用户设置全局语言环境,只需打开~/.bash_profile文件并添加以下行。 LANG="en_IN.utf8" export LANG Alpine中设置 Alpine使用的是musl,这是一个叫精简的,没有locale,所以需要手动添加musl-locale。 musl-locale 存放在/usr/bin/locale,可以在musl libc中运行。要安装,请使用cmake . && make && sudo make install安装在具有Musl功能的发行版上。包括英语和俄语,以及.pot文件。 需要如下的依赖: musl (with developer tools) gettext (with libintl and developer tools) С compiler (gcc or clang recommended) CMake CMake backend provider (make or ninja) # 设置环境变量ENV MUSL_LOCALE_DEPS cmake make musl-dev gcc gettext-dev libintl \ # 设置MUSL_LOCPATH来获取其他可设置的locale MUSL_LOCPATH /usr/share/i18n/locales/musl \ TZ=Asia/Shanghai \ LANG=zh_CN.UTF-8 \ LANGUAGE=zh_CN:zh:en_US:en \ LC_ALL=zh_CN.UTF-8# 下载依赖RUN apk add --update cmake make musl-dev gcc gettext-dev libintl# 下载musl-locale并编译RUN apk add --no-cache \ $MUSL_LOCALE_DEPS \ && wget https://gitlab.com/rilian-la-te/musl-locales/-/archive/master/musl-locales-master.zip \ && unzip musl-locales-master.zip \ && cd musl-locales-master \ && cmake -DLOCALE_PROFILE=OFF -D CMAKE_INSTALL_PREFIX:PATH=/usr . && make && make install \ && cd .. && rm -r musl-locales-master

01-时区 阅读更多

0.1. 系统时区 0.1.1. timedatectl 0.1.1.1. 显示当前设置 0.1.1.2. 设置时区 0.1.1.3. 设置时间 0.1.2. Debian及其发行版 0.1.3. REHL/CentOS7/Fedora 0.2. Golang代码中设置时区 方法一 方法二 0.3. docker容器中设置时区 0.3.1. 在Dockerfile中设置环境变量 0.3.2. scratch基础镜像 0.3.3. 挂载宿主机时区文件 如果应用程序里不显式地设置时区,那么golang的time包里的函数统一用的是默认的UTC时区。 0.1. 系统时区 在Linux中有许多时间管理工具,如date、timedatectl来获取当前系统的时区并且和远端NTP服务器进行同步来启动自动的更精确的系统时间处理。 0.1.1. timedatectl timedatectl命令是基于RHEL/CentOS7和Fedora 21+发行版的新实用程序,它是systemd系统和服务管理器的一部分,是在基于sysvinit守护程序的Linux发行版中使用的旧传统date命令的替代。 timedatectl --help timedatectl [OPTIONS...] COMMAND ... Query or change system time and date settings. -h --help Show this help message --version Show package version --no-pager Do not pipe output into a pager --no-ask-password Do not prompt for password -H --host=[USER@]HOST Operate on remote host -M --machine=CONTAINER Operate on local container --adjust-system-clock Adjust system clock when changing local RTC mode Commands: status 显示当前时间设置 set-time TIME 设置系统时间 set-timezone ZONE 设置系统沙丘 list-timezones 显示已知的时区 set-local-rtc BOOL 控制RTC是否在当地时间(local time,1=true/0=false) set-ntp BOOL 启用或禁用网络时间同步(true/false) 要在系统上显示当前时间和日期,请从命令行使用timedatectl命令,如下所示: 0.1.1.1. 显示当前设置 timedatectl status Local time: 一 2020-03-09 15:03:56 CST Universal time: 一 2020-03-09 07:03:56 UTC RTC time: 一 2020-03-09 07:03:56 Time zone: Asia/Shanghai (CST, +0800) System clock synchronized: yes systemd-timesyncd.service active: yes RTC in local TZ: no # RTC time 表示的是硬件时钟时间 始终通过系统上设置的时区来管理Linux系统上的时间,上面的命令输出中,Time zone就是当前系统的时区。 0.1.1.2. 设置时区 设置时区使用如下命令: timedatectl set-timezone "Asia/Shanghai" # 始终建议使用并设置coordinated universal time,即UTC。 0.1.1.3. 设置时间 # 仅设置时间,可以按照HH:MM:SS(时分秒)的时间格式 timedatectl set-time 20:21:22 # 仅设置日期,可以按照YY:MM:DD(年月日)中的日期格式 timedatectl set-time 20200229 # 同时设置时间和日期 timedatectl set-time '2020-03-04 13:14:15' 0.1.2. Debian及其发行版 cat /etc/timezone Asia/Shanghai 直接修改这个文件,即可生效。 0.1.3. REHL/CentOS7/Fedora 这几个操作系统的 /etc/localtime是目录/usr/share/zoneinfo下文件的符号链接。 sudo ln -sf /usr/share/zoneinfo/zoneinfo /etc/localtime # 参数说明 -s, --symbolic 创建符号链接而不是硬链接 -f, --force 强行删除任何已存在的目标文件 # 例如要将时区修改为东八区 sudo ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime 0.2. Golang代码中设置时区 方法一 可以使用os.Setenv("TZ","Asia/Shanghai")从应用程序内部实现,重要的是必须先调用此函数,然后其他任何程序包才能使用time程序包中的任何内容。要保证生效,可以创建一个除了设置时区外什么都不做的包。 package tzinit import ( "os" ) func init() { os.Setenv("TZ", "Asia/Shanghai") } 首先将这个tzinit包导入main包中,如下所示: package main import _ "path/to/tzinit" // Your other, "regular" imports: import ( "fmt" "os" "time" ... ) 这样的话,设置TZ环境变量的操作将在任何其他程序包可以访问时间程序包之前进行。 请注意,只是为tzinit使用了单独的导入声明,其原因是因为许多IDE会按字母顺序重新排列导入,单独导入声明将确保导入tzinit包是第一个导入的。 “The Spec: Package initialization”说明了程序包初始化的要求和规则,并且未指定导入的处理顺序(唯一可以保证的是,所有引用的程序包将在使用前递归初始化)。这意味着,尽管当前的编译器按列出的顺序处理它们,但不能100%依靠它。 为了安全起见,最好是在启动Go应用之前设置TZ环境变量。 方法二 2020-8-11,golang 1.15版本发布,其中增加了"time/tzdata"包。 tzdata包提供了时区数据库的嵌入式副本。将此包导入到程序中的任意位置后,如果time包在系统上找不到tzdata文件,它将使用此嵌入式信息。 导入这个包将会使程序增加约800KB。通常应该在main包而不是其他库包(通常不应该决定是否在程序中包括时区数据库)中导入。 也可以在编译时使用-tags timetzdata来自动导入这个包。 0.3. docker容器中设置时区 在Linux系统下Go运行时会从多个来源读取时区信息,在$GOROOT/src/time/zoneinfo.unix文件里能够找到Go运行时是从哪些地方读取时区信息的。 // Many systems use /usr/share/zoneinfo, Solaris 2 has // /usr/share/lib/zoneinfo, IRIX 6 has /usr/lib/locale/TZ. var zoneSources = []string{ "/usr/share/zoneinfo/", "/usr/share/lib/zoneinfo/", "/usr/lib/locale/TZ/", runtime.GOROOT() + "/lib/time/zoneinfo.zip", } 0.3.1. 在Dockerfile中设置环境变量 ENV TZ=Asia/ShanghaiRUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone# 不生效的时候,再使用这个命令,在非交互的方式下重新配置一个已经按照的包RUN dpkg-reconfigure -f noninteractive tzdata 不同Linux发行版的时区设置略有不同,上面的设置可以在大部分发行版最为基础镜像的容器中正常运行。 当使用Alpine作为基础镜像时,需要先安装tzdate工具,同时删除不要的文件来精简镜像: tzdata软件包,全称time zone and daylight-saving time(DST) data,供各个Linux系统安装以读取Time Zone Database中数据。 # 墙内可以使用镜像地址加快下载安装的进度RUN sed -i "s/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g" /etc/apk/repositoriesRUN apk add --no-cache tzdata && rm -rf /var/cache/apk/*ENV TZ=Asia/ShanghaiRUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 0.3.2. scratch基础镜像 scratch镜像的容器里中,上面列的几个目录都没有,scratch镜像里并不包含这些时区文件。解决办法就是从build阶段的镜像里拷贝时区文件到最终的应用镜像。 FROMgolang:alpine as buildRUN apk --no-cache add tzdataWORKDIR/appADD . /appRUN CGO_ENABLED=0 GOOS=linux go build -o myappFROMscratch as finalCOPY --from=build /app/myapp .COPY --from=build /usr/share/zoneinfo /usr/share/zoneinfoENV TZ=Asia/ShanghaiCMD ["/myapp"] 0.3.3. 挂载宿主机时区文件 docker run -v /etc/timezone:/etc/timezone:ro [image-name]volumes: - "/etc/timezone:/etc/timezone:ro" - "/etc/localtime:/etc/localtime:ro" 但是,挂载的这个方式总感觉不是很好,容器还是要尽量少的和宿主机有交互,减少攻击面。