一、Dockerfile 概念

1、Dockerfile是什么

Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。

镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来构建、定制镜像,那么之前提及的无法重复的问题、镜像构建透明性的问题、体积的问题就都会解决。这个脚本就是 Dockerfile。

Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。有了 Dockerfile,当我们需要定制自己额外的需求时,只需在 Dockerfile 上添加或者修改指令,重新生成 image 即可,省去了敲命令的麻烦。

二、Dockerfile构建方式

Docker通过对Dockerfile中的一系列指令的顺序解析实现自动镜像构建,构建方式:
  1. 通过使用build命令,根据Dockerfie的命令来构建镜像,默认加载当前目录下的Dockerfile文件
  2. 通过源代码路径的方式,即指定Dockerfile文件位置,比如Git仓库位置
  3. 通过标准输入流的方式

通过源代码路径方式

  • Dockerfile需要放置在项目的根目录位置
  • 在构建的时候,Dockerfile client会把整个context打包发送到Docker Server端,然后由server端负责build镜像,在构建成功后,会删除context目录
  • docker build -t {镜像名字} {项目路径可以是相对路径}

  

通过标准输入流方式

  • 通过标准输入流的方式获取Dockerfile的内容
  • client不会打包上传context目录,因此对于一些ADD、COPY等涉及host本地文件复制的操作不能够支持
  • docker build -t {镜像名字} - < Dockerfile路径
 

通过build命令

  • 这是最常用的方式,docker build -t {镜像名字} {项目路径可以是相对路径,也可以是网络文件}
  • docker build -t="xuequn/nginx:v1" git@github:loveliuli/custom_dockerfile
  • 注意:custom_dockerfile目录下必须存在Dockerfile文件才行!

三、Dockerfile构建缓存

  • Dockerfile中的每一个指令执行完毕后,都会提交为一个image,这样保证了指令之间不会有影响
  • Dockerfile会尽可能尝试重用之前已经构建的镜像
  • 可以通过在build命令中增加--no-cache的方式来禁用这个cache

四、Dockerfile最佳实践

Docker镜像由只读层组成,每个层都代表一个Dockerfile指令。这些层是堆叠的,每一层都是前一层变化的增量。

1、了解构建上下文

发出docker build命令时,当前工作目录称为构建上下文。默认情况下,假定Dockerfile位于此处,但您可以使用文件flag(-f)指定其他位置。无论Dockerfile实际存在的位置如何,当前目录中的所有文件和目录的递归内容都将作为构建上下文发送到Docker守护程序。

构建上下文示例

为构建上下文创建一个目录并cd进入该目录。将“hello”写入名为的文本文件,hello并创建一个cat在其上运行的Dockerfile 。从构建上下文(.)中构建镜像:

mkdir myproject && cd myproject
echo "hello" > hello
echo -e "FROM busybox\nCOPY /hello /\nRUN cat /hello" > Dockerfile
docker build -t helloapp:v1 .

  

把Dockerfile文件移动到dockerfile文件夹,hello文件移动到context文件夹,构建第二个版本(不依赖于上一个版本的缓存)。使用-f 指向Dockerfile并指定构建上下文的目录:

mkdir -p dockerfiles context
mv Dockerfile dockerfiles && mv hello context
docker build --no-cache -t helloapp:v2 -f dockerfiles/Dockerfile context

  

2、管道Dockerfile通过stdin

Docker17.05增加了Dockerfile通过stdin使用本地或远程构建上下文进行管道来构建镜像的功能。在早期版本中,使用Dockerfilefrom构建镜像stdin并未发送构建上下文。

Docker17.04及更低版本


docker build -t foo -<<EOF
FROM busybox
RUN echo "hello world"
EOF

Docker 17.05及更高版本(本地构建上下文)


docker build -t foo . -f-<<EOF
FROM busybox
RUN echo "hello world"
COPY . /my-copied-files
EOF

Docker 17.05及更高版本(远程构建上下文)


docker build -t foo https://github.com/thajeztah/pgadmin4-docker.git -f-<<EOF
FROM busybox
COPY LICENSE config_local.py /usr/local/lib/python2.7/site-packages/pgadmin4/
EOF 

3、使用.dockerignore

要排除与构建无关的文件(不重构源存储库),请使用.dockerignore文件。此文件支持与.gitignore文件类似的排除模式。有关创建一个的信息,请参阅 .dockerignore文件

4、使用多阶段构建

多阶段构建(在Docker 17.05或更高版本中)允许您大幅减小最终镜像的大小,而不必费力地减少中间层和文件的数量。

由于镜像是在构建过程的最后阶段构建的,因此可以通过利用构建缓存来最小化镜像层。

例如,如果您的构建包含多个镜像层,则可以从较不频繁更改(以确保构建缓存可重用)到更频繁更改的顺序对它们进行排序:

  • 安装构建应用程序所需的工具  #yum install gcc-c++等

  • 安装或更新库依赖项 #yum install nginx

  • 生成您的应用程序  #COPY ./* /var/www/

例如Go应用程序的Dockerfile可能如下所示:

FROM golang:1.9.2-alpine3.6 AS build

# Install tools required for project#安装项目必须的工具
# Run `docker build --no-cache .` to update dependencies #使用nocache更新依赖
RUN apk add --no-cache git
RUN go get github.com/golang/dep/cmd/dep # List project dependencies with Gopkg.toml and Gopkg.lock #列出项目依赖
# These layers are only re-built when Gopkg files are updated #这些层只会在文件有更新时才会被重构
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
# Install library dependencies
RUN dep ensure -vendor-only # Copy the entire project and build it #拷贝整个项目并构建
# This layer is rebuilt when a file changes in the project directory #当项目目录里有文件变化时,这一层将会被重新构建
COPY . /go/src/project/
RUN go build -o /bin/project # This results in a single layer image #这样会导致一个独立的镜像层
FROM scratch
COPY --from=build /bin/project /bin/project
ENTRYPOINT ["/bin/project"]
CMD ["--help"]

5、不要安装不必要的包

为了降低复杂性,依赖性,文件大小和构建时间,避免安装额外的或不必要的软件包,因为它们可能“很好”。例如,您不需要在数据库镜像中包含文本编辑器。

6、解耦应用程序

每个容器应该只承担一个功能。

将应用程序分散到多个容器中可以更容易地水平扩展和重用容器。例如,Web应用程序可能包含三个独立的容器,每个容器都有自己独特的镜像,以分离的方式管理Web应用程序,数据库和内存缓存。

将每个容器限制为一个进程是一个很好的经验法则,但它并不是一个严格的规则。例如,不仅可以使用init进程生成容器 ,而且某些程序可能会自行生成其他进程。例如,Celery可以生成多个工作进程,Apache可以为每个请求创建一个进程。

使用您的最佳判断,尽可能保持容器简单和模块化。如果容器彼此依赖,则可以使用Docker容器网络来确保这些容器可以进行通信。

一个容器就是一个进程,承担一个功能,这个是最重要的!

7、最小化层数

在旧版本的Docker中,最大限度地减少镜像中的层数以确保它们具有高性能非常重要。

添加了以下功能以减少此限制:

  • 在Docker 1.10和更高,只有指令RUNCOPYADD创建镜像。其他指令创建临时中间镜像,而不是直接增加构建的大小。

  • 在Docker 17.05及更高版本中,您可以执行多阶段构建, 并仅将所需的东西复制到最终镜像中。这允许您在中间构建阶段中包含工具和调试信息,而不会增加最终镜像的大小。

8、对多行参数进行排序

只要有可能,通过按字母数字方式对多行参数进行排序,可以缓解以后的更改。这有助于避免重复包并使列表更容易更新。这也使PR更容易阅​​读和审查。在反斜杠(\)之前添加空格也有帮助。

例如:

RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion

9、利用构建缓存

构建镜像时,Docker会逐步执行Dockerfile中的指令,按指定的顺序执行每个指令。在检查每条指令时,Docker会在其缓存中查找可以重用的现有镜像,而不是创建新的(重复)镜像。

如果您根本不想使用缓存,可以使用命令中的--no-cache=true选项来docker build。但是,如果你让Docker使用它的缓存,重要的是要了解它何时可以找到匹配的镜像。

Docker遵循的基本规则概述如下:

  • 从已经在高速缓存中的父镜像开始,将下一条指令与从该基本镜像导出的所有子镜像进行比较,以查看它们中的一个是否使用完全相同的指令构建。如果不是,则缓存无效。

  • 在大多数情况下,只需将Dockerfile其中一个子镜像中的指令进行比较就足够了。但是,某些说明需要更多的检查和解释。

  • 对于ADDCOPY指令,检查镜像中文件的内容,并计算每个文件的校验和。在这些校验和中不考虑文件的最后修改时间和最后访问时间。在高速缓存查找期间,将校验和与现有镜像中的校验和进行比较。如果文件中的任何内容(例如内容和元数据)发生了任何变化,则缓存无效。

  • 除了ADDCOPY命令之外,缓存检查不会查看容器中的文件来确定缓存匹配。例如,在处理RUN apt-get -y update命令时,不检查容器中更新的文件以确定是否存在缓存命中。在这种情况下,只需使用命令字符串本身来查找匹配项。

一旦高速缓存失效,所有后续Dockerfile命令都会生成新镜像,并且不使用高速缓存。

五、Dockerfile常用指令和最佳实践

  • 只支持Docker自己定义的一套指令,不支持自定义
  • 大小写不敏感,但是建议全部使用大写
  • 根据Dockerfile的内容顺序执行
Dockerfile常用指令如下:
 

1、FROM

FROM {base镜像}
必须放在Dockerfile的第一行,表示从哪个baseimage开始构建

  

FROM最佳实践

尽可能使用当前的官方存储库作为镜像的基础。
我们推荐Alpine图像,因为它受到严格控制并且尺寸较小(目前小于5 MB),同时仍然是完整的Linux发行版。

  

2、LABLE最佳实践

您可以为镜像添加标签,以帮助按项目组织镜像,记录许可信息,帮助实现自动化或出于其他原因。

对于每个标签,添加LABEL以一个或多个键值对开头的行。以下示例显示了不同的可接受格式。内容包括解释性意见。

必须引用带空格的字符串或必须转义空格。内引号字符(")也必须进行转义。

镜像可以有多个标签。

# 设置一个或多个标签
LABEL com.example.version="0.0.1-beta"
LABEL vendor1="ACME Incorporated"
LABEL vendor2=ZENITH\ Incorporated
LABEL com.example.release-date="2015-02-12"
LABEL com.example.version.is-production=""

3、MAINTAINER

可选的,用来标识image作者的地方

  

4、RUN

每一个RUN指令都会是在一个新的container里面运行,并提交为一个image作为下一个RUN的base
一个Dockerfile中可以包含多个RUN,按定义顺序执行
RUN支持两种运行方式:
 RUN <cmd> 这个会当作/bin/sh -c “cmd” 运行
 RUN [“executable”,“arg1”,。。],Docker把他当作json的顺序来解析,因此必须使用双引号,而且executable需要是完整路径
  RUN 都是启动一个容器、执行命令、然后提交存储层文件变更。第一层 RUN command1 的执行仅仅是当前进程,一个内存上的变化而已,其结果不会造成任何文件。而到第二层的时候,启动的是一个全新的容器,跟第一层的容器更完全没关系,自然不可能继承前一层构建过程中的内存变化。而如果需要将两条命令或者多条命令联合起来执行需要加上&&。如:cd /usr/local/src && wget xxxxxxx
 

RUN最佳实践

RUN在使用反斜杠分隔的多行上拆分长或复杂语句,以使您Dockerfile更具可读性,可理解性和可维护性。
 例如:
 RUN apt-get update && apt-get install -y \
package-bar \
package-baz \
package-foo

  使用管道:RUN wget -O - https://some.site | wc -l > /number

 

5、CMD

CMD的作用是作为执行container时候的默认行为(容器默认的启动命令)
当运行container的时候声明了command,则不再用image中的CMD默认所定义的命令
一个Dockerfile中只能有一个有效的CMD,当定义多个CMD的时候,只有最后一个才会起作用,即会被覆盖。
CMD和ENTRPOINT之间的相互关系需要理解,ENTRPOINT不容易被覆盖,而且docker run中指定的任何参数都会当做参数再次传递给ENTRPOINT。
CMD定义的三种方式:
  CMD <cmd> 这个会当作/bin/sh -c "cmd"来执行
  CMD ["executable","arg1",....]
  CMD ["arg1","arg2"],这个时候CMD作为ENTRYPOINT的参数
 

CMD最佳实践

在大多数其他情况下,CMD应该给出一个交互式shell,例如bash,python和perl。例如,CMD ["perl", "-de0"]CMD ["python"],或CMD [“php”, “-a”]
使用此表单意味着当您执行类似的操作时 docker run -it python,您将被放入可用的shell中,随时可以使用。 
CMD应该很少的方式使用CMD [“param”, “param”]会配合ENTRYPOINT,除非你和你预期的用户已经非常熟悉ENTRYPOINT是如何工作的。

6、EXPOSE声明端口

格式为 EXPOSE <端口1> [<端口2>...]。

EXPOSE 指令是声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。

在 Dockerfile 中写入这样的声明有两个好处,

  1、帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;

  2、在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。

EXPOSE最佳实践:

尽量使用常规端口,比如Mysql的3306,Mongo的27017。
 

7、ENTRPOINT

entrypoint的作用是,把整个container变成了一个可执行的文件,
这样不能够通过替换CMD的方法来改变创建container的方式。
但是可以通过参数传递的方法影响到container内部
每个Dockerfile只能够包含一个entrypoint,多个entrypoint只有最后一个有效
当定义了entrypoint以后,CMD只能够作为参数进行传递
entrypoint定义方式:
entrypoint ["executable","arg1","arg2"],这种定义方式下,CMD可以通过json的方式来定义entrypoint的参数,可以通过在运行container的时候通过指定command的方式传递参数
entrypoint <cmd>,当作/bin/bash -c "cmd"运行命令
 

ENTRPOINT最佳实践

1、最好的用法是把ENTRYPOINT设置为镜像的主命令,允许该镜像和该命令一样运行(然后CMD用作默认标志)。
例如:
ENTRYPOINT ["s3cmd"]
CMD ["--help"]
2、ENTRYPOINT指令还可以与辅助脚本结合使用,使其能够以与上述命令类似的方式运行,即使启动该工具可能需要多个步骤。
docker-entrpoint.sh文件:
#!/bin/bash
set -e if [ "$1" = 'postgres' ]; then
chown -R postgres "$PGDATA" if [ -z "$(ls -A "$PGDATA")" ]; then
gosu postgres initdb
fi exec gosu postgres "$@"
fi exec "$@"

  

COPY ./docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["postgres"]

8、ADD & COPY

当在源代码构建的方式下,可以通过ADD和COPY的方式,把host上的文件或者目录复制到image中
ADD和COPY的源必须在context路径下
当src为网络URL的情况下,ADD指令可以把它下载到dest的指定位置,这个在任何build的方式下都可以work
ADD相对COPY还有一个多的功能,能够进行自动解压压缩包。
ADD latest.tar.gz /var/www/wordpress 他会自动将tar包解压到wordpress目录下。

ADD & COPY最佳实践

一般而言,虽然ADD并且COPY在功能上类似,但是COPY 是优选的。因为COPY相对ADD来说,是更透明的,比如ADD在添加tar包时,会自动解压。
如果您的Dockerfile有多个步骤使用上下文中的不同文件,则COPY它们是单独的,而不是一次性完成。这可确保每个步骤的构建缓存仅在特定所需文件更改时失效(强制重新执行该步骤)。
COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
COPY . /tmp/

  尽量减小镜像文件大小,不是一次性的拷贝文件,而是只拷贝需要的文件,这样镜像文件会更小。

由于镜像大小很重要,ADD因此强烈建议不要使用从远程URL获取包。你应该使用curlwget代替。这样,您可以删除提取后不再需要的文件,也不必在图像中添加其他图层。例如,你应该避免以下做法:
ADD http://example.com/big.tar.xz /usr/src/things/
RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
RUN make -C /usr/src/things all

而是要这样做:

RUN mkdir -p /usr/src/things \
&& curl -SL http://example.com/big.tar.xz \
| tar -xJC /usr/src/things \
&& make -C /usr/src/things all

9、ENV

ENV key value
用来设置环境变量,后续的RUN可以使用它所创建的环境变量
当创建基于该镜像的container的时候,会自动拥有设置的环境变量

ENV最佳实践

要使新软件更易于运行,您可以使用ENV更新PATH容器安装的软件的 环境变量。例如,ENV PATH /usr/local/nginx/bin:$PATH确保CMD [“nginx”] 正常工作。

ENV指令还可用于提供特定于您希望容纳的服务的必需环境变量,例如Postgres PGDATA

最后,ENV还可以用来设置常用的版本号,以便更容易维护版本的变化,如下例所示:

ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH

每一行ENV就会创建一个新的中间层,就像RUN命令一样。这意味着即使您在将来的镜像中取消设置环境变量,它仍然会在此图层中保留,并且可以转储其值。

您可以通过创建如下所示的Dockerfile来测试它,然后构建它。

FROM alpine
ENV ADMIN_USER="mark"
RUN echo $ADMIN_USER > ./mark
RUN unset ADMIN_USER
CMD sh
$ docker run --rm -it test sh echo $ADMIN_USER

mark

要防止这种情况发生,并且确实取消了之前设置的环境变量,请使用RUN带有shell命令的命令,在单个镜像中设置,使用和取消设置变量all。您可以使用;或分隔命令&&

如果您使用第二种方法,并且如果其中一个命令失败,则docker build也会失败。这通常是一个好主意。使用\作为行继续符可以提高可读性。

您还可以将所有命令放入shell脚本中,并让RUN命令运行该shell脚本。 

10、WORKDIR

用来指定当前工作目录(或者称为当前目录)
当使用相对目录的情况下,采用上一个WORKDIR指定的目录作为基准 

WORKDIR最佳实践

为了清晰和可靠,您应该始终使用绝对路径的 WORKDIR。此外,您应该使用WORKDIR,而不是使用难以阅读,排除故障和维护指令RUN cd … && do-something

11、USER

指定UID或者username,来决定运行RUN指令的用户

  

USER最佳实践
如果服务可以在没有权限的情况下运行,请把USER更改为非root用户。
首先在Dockerfile中创建用户和组RUN groupadd -r postgres && useradd --no-log-init -r -g postgres postgres。
镜像中的用户和组被分配了不确定的UID/GID,因为无论镜像如何构建,都会分配一个UID/GID。因此,如果它很重要,您应该分配一个显式的UID / GID。
由于Go归档/tar包在处理松散文件时有一个未解决的错误,尝试在Docker容器内创建具有非常大的UID的用户可能导致磁盘耗尽,因为/var/log/faillog在容器层中填充了NULL(\ 0)字符。
解决方法是将--no-log-init标志传递给useradd。Debian / Ubuntu adduser装饰器不支持此标志。

避免安装或使用sudo,因为它具有可能导致不可预测的TTY和信号转发行为的问题。如果您绝对需要类似的功能sudo,例如将守护程序初始化root为非运行它root,请考虑使用“gosu”

最后,为了减少层次和复杂性,避免USER频繁地来回切换。

12、ONBUILD

ONBUILD作为一个trigger的标记,可以用来trigger任何Dockerfile中的指令
可以定义多个ONBUILD指令
当下一个镜像B使用镜像A作为base的时候,在FROM A指令前,会先按照顺序执行在构建A时候定义的ONBUILD指令
ONBUILD <DOCKERFILE 指令> <content>

ONBUILD最佳实践

ONBUILD指令能为镜像添加触发器,当一个镜像被用作其他镜像的基础镜像时,改镜像中的触发器将会被执行。

ONBUILD指令是紧跟在FROM之后指定的

Docker构建ONBUILD在子代中的任何命令之前执行命令Dockerfile

把时要小心,ADDCOPYONBUILD。如果新构建的上下文缺少正在添加的资源,则“onbuild”映像将发生灾难性故障。

例如:

FROM ubuntu:14.04
MAINTAINER xuequn "xuequn@kingsoft.com"
RUN apt-get update
RUN apt-get install -y apache2
ENV APACHE_RUN_USER www-data
ENV APACHE_RUN_GROUP www-data
ENV APACHE_LOG_DIR /var/log/apache2
ONBUILD ADD . /var/www/
EXPOSE 80
ENTRPOINT ["/usr/sbin/apache2"]
CMD ["-D","FOREGOUND"]

  在新构建的镜像包含一条ONBUILD指令,该指令会使用ADD 指令将构建环境所在的目录下的所有文件拷贝到/var/www/下面。

当我们使用上面的镜像作为基础镜像,再构建一个新的镜像时:

FROM xuequn/apache2
MAINTAINER xuequn 'xuequn@kingsoft.com'
ENV APPLICATION_NAME webapp01

  当执行完FROM时,就进入了构建阶段,此时会出发基础镜像中的ONBUILD指令,会将当期目录下的所有文件拷贝到/var/www/下面,这样就完成了个性化镜像制作功能,这就是ONBUILD的绝妙之处!!

注意:

  • ONBUILD指令只会被继承一次,也就是在子镜像制作时会出发ONBUILD指令,而孙子镜像构建时不会再触发此指令!
  • ONBUILD指令中有几条指令是不能使用的:FROM/MAINTAINER/ONBUILD。因为这样会进入递归调用而进入死循环!

13、VOLUME

用来创建一个在image之外的mount point,用来在多个container之间实现数据共享
运行使用json array的方式定义多个volume
VOLUME ["/var/data1","/var/data2"]
或者plain text的情况下定义多个VOLUME指令

VOLUME最佳实践

该VOLUME指令应用于公开由docker容器创建的任何数据库存储区域,配置存储或文件/文件夹。

  

8、Dockerfile介绍和最佳实践的更多相关文章

  1. JSR 303 - Bean Validation 介绍及最佳实践

    JSR 303 - Bean Validation 介绍及最佳实践 JSR 303 – Bean Validation 是一个数据验证的规范,2009 年 11 月确定最终方案.2009 年 12 月 ...

  2. Spring MVC学习笔记——JSR303介绍及最佳实践

    JSR 303 – Bean Validation 是一个数据验证的规范,2009 年 11 月确定最终方案.2009 年 12 月 Java EE 6 发布,Bean Validation 作为一个 ...

  3. JSR 303 - Bean Validation 介绍及最佳实践(转)

    JSR 303 – Bean Validation 是一个数据验证的规范,2009 年 11 月确定最终方案.2009 年 12 月 Java EE 6 发布,Bean Validation 作为一个 ...

  4. 使用 Dockerfile 的一些最佳实践

  5. Dockerfile 最佳实践

    之前 一篇文章介绍 docker 的镜像基本原理和概念 ,主要介绍在编写 docker 镜像的时候一些需要注意的事项和推荐的做法. 虽然 Dockerfile 简化了镜像构建的过程,并且把这个过程可以 ...

  6. [转]在 Azure 云服务上设计大规模服务的最佳实践

    本文转自:http://technet.microsoft.com/zh-cn/magazine/jj717232.aspx 英文版:http://msdn.microsoft.com/library ...

  7. Prometheus Metrics 设计的最佳实践和应用实例,看这篇够了!

    Prometheus 是一个开源的监控解决方案,部署简单易使用,难点在于如何设计符合特定需求的 Metrics 去全面高效地反映系统实时状态,以助力故障问题的发现与定位.本文即基于最佳实践的 Metr ...

  8. GraalVM最佳实践,使用Java开发CLI、Desktop(JavaFX)、Web(SpringBoot)项目,并使用native-image技术把Java代码静态编译为独立可执行文件(本机映像)

    原创文章,转载请注明出处! 源码地址: Gitee Gtihub 介绍 GraalVM最佳实践,使用Java开发CLI.Desktop(JavaFX).Web(SpringBoot)项目,并使用nat ...

  9. Docker笔记(十一):Dockerfile详解与最佳实践

    Dockerfile是一个文本文件,包含了一条条指令,每条指令对应构建一层镜像,Docker基于它来构建一个完整镜像.本文介绍Dockerfile的常用指令及相应的最佳实践建议. 1. 理解构建上下文 ...

随机推荐

  1. innodb_file_per_table - 转换为InnoDB

    共享InnoDB / var / lib / mysql / ibdata1存储的问题InnoDB表当前将数据和索引存储到共享表空间(/ var / lib / mysql / ibdata1).由于 ...

  2. 做为一个.net码农,打开公司的一个项目,大叔我哭了

    先说下背景,楼主在上海,之前一直是做BS互联网开发的,今年进入这家公司,是做软件产品的小外企. 然后,啥也不说了,直接上图吧: 因为一个屏幕没有办法显示出来,所以我截了3张图,然后拼成一张,这还是我花 ...

  3. 安全之路 —— C/C++开3389端口(远程终端)

    简介 在渗透测试中开启对方电脑的3389端口是入侵者加入对方计算机账户后要想直接控制对方计算机的必须步骤,即开启对方计算机的远程终端功能,不同的Windows系统要开启3389需要修改不同的注册表项, ...

  4. PHP 与 YAML

    PHP 与 YAML 这一段时间都没有写blog,并不是因为事情多,而是自己变懒了.看到新技术也不愿意深入思考其背后的原理,学习C++语言了近一个多月,由于学习方法有问题,并没有什么项目可以练手.靠每 ...

  5. dns服务器测试工具

    下载地址:https://www.eatm.app/wp-content/uploads/2018/08/eDnsTest.20180810.zip

  6. October 27th, 2017 Week 43rd Friday

    The only thing predictable about life is its unpredictability. 人生唯一可以预知的,就是它的变化莫测. Is it really unpr ...

  7. Netty入门(五)ChanneHandler

    本节主要讨论了 Netty 的数据处理组件 ChannelHandler. 一.Channel 生命周期 Channel 有个简单但强大的状态模型,下面是 Channel 的四个状态: Channel ...

  8. CF558E A Simple Task

    题目大意: 给定一个长度不超过10^5的字符串(小写英文字母),和不超过5000个操作. 每个操作 L R K 表示给区间[L,R]的字符串排序,K=1为升序,K=0为降序. 最后输出最终的字符串 首 ...

  9. Unicode,ISO-8859-1,GBK,UTF-8编码及相互转换(转载)

    第二篇:JAVA字符编码系列二:Unicode,ISO-8859-1,GBK,UTF-8编码及相互转换 1.函数介绍在Java中,字符串用统一的Unicode编码,每个字符占用两个字节,与编码有关的两 ...

  10. 软件测试QA、QC、QM的关系与区别

    01概念上 QA:Quality Assurance (质量保证) QC:Quality Control (质量控制) QM:Quality Manage (质量管理) 02定义上 QA:为达到质量要 ...