Docker技术入门| Part03:Dockerfile详解(Dockerfile概念、Dockerfile 指令、使用Dockerfile构建镜像)
文章目录
- 1. Dockerfile概念
- 2. Dockerfile 指令
- FROM 指定基础镜像
- RUN执行命令
- CMD 容器启动命令
- COPY 复制文件
- ADD 更高级的复制文件
- ENV 设置环境变量
- ARG 构建参数
- VOLUME 定义匿名卷
- EXPOSE 暴露端口
- WORKDIR 指定工作目录
- USER 指定当前用户
- LABEL 为镜像添加元数据
- SHELL 指令
- 3. 使用Dockerfile构建镜像
1. Dockerfile概念
Docker镜像原理
-
Docker镜像是由特殊的文件系统叠加而成
-
最底端是bootfs,并使用宿主机的bootfs
-
第二层是root文件系统rootfs,称为base image
-
然后再往上可以叠加其他的镜像文件
-
统文件系统(UnionFile System)技术能够将不同的层整合成一个文件系统,为这些层提供了一个统的视角,这样就隐藏了多层的存在,在用户的角度看来,只存在一个文件系统。
-
一个镜像可以放在另一个镜像的上面。位于下面的镜像称为父镜像,最底部的镜像成为基础镜像。
-
当从一个镜像启动容器时,Docker会在最顶层加载一个读写文件系统作为容器
Dockerfile是什么
-
Dockerfile是一个文本文件
-
包含了一条条的指令
-
每一条指令构建-层,基于基础镜像,最终构建出一个新的镜像
-
对于开发人员:可以为开发团队提供个完全一致的开发环境
-
对于测试人员:可以直接拿开发时所构建的镜像或者通过Dockerfile文件构建一个新的镜像开始工作了
-
对于运维人员:在部署时,可以实现应用的无缝移植
2. Dockerfile 指令
一般的,Dockerfile 分为四部分:基础镜像信息、维护者信息、镜像操作指令和容器启动时执行指令。
| 关键字 | 作用 | 备注 |
|---|---|---|
| FROM | 指定父镜像 | 指定dockerfile基于哪个image构建。 |
| MAINTAINER | 作者信息 | 用来标明这个dockerfile谁写的 |
| LABEL | 标签 | 用来标明dockerfile的标签。可以使用Label代替Maintainer最终都是在docker image基本信息中可以查看 |
| RUN | 执行命令 | 执行一段命令,默认是/bin/sh,格式: RUN command或者RUN [“command”,"param1L ",“param2”] |
| CMD | 容器启动命令 | 提供启动容器时候的默认命令,和ENTRYPOINT配合使用.格式CMD command param1 param2或者CMD [“command” , “param1”, “param2”] |
| COPY | 复制文件 | build的时候复制本地文件到image中 |
| ADD | 添加文件 | build的时候添加文件到image中,可自动解压缩,不仅仅局限于当前build上下文,可以来源于远程服务 |
| EXPOSE | 暴露端口 | 定义容器运行的时候监听的端口,启动容器的使用-p来绑定暴露端口,格式:EXPOSE 8080或者EXPOSE 8080/udp |
| WORKDIR | 工作目录 | 指定容器内部的工作目录,如果没有创建则自动创建,如果指定"/“,使用的是绝对地址,从根目录开始,如果不是”/"开头,那么是在上一条workdir的路径的相对路径 |
| ENV | 环境变量 | 指定build时候的环境变量,可以在启动容器的时候通过-e覆盖,格式ENVname=value |
| ARG | 构建参数 | 只在构建的时候使用的参数,如果有ENV,那么ENV的相同名字的值始终覆盖arg的参数 |
| VOLUME | 定义外部可以挂载的数据卷 | 指定build的image哪些目录可以启动的时候挂载到文件系统中,启动容器的时候使用-v绑定,格式VOLUME [“目录”] |
| USER | 指定执行用户 | 指定build或者启动的时候,在RUN CMD ENTRYPONT执行的时候的用户 |
FROM 指定基础镜像
- Dockerfile 中 FROM 是必备的指令,并且必须是第一条指令。
- FROM指定构建镜像的基础源镜像,如果本地没有指定的镜像,则会自动从 Docker 的公共库 pull 镜像下来。
- FROM可以在一个 Dockerfile 中出现多次,如果有需求在一个 Dockerfile 中创建多个镜像。
- 如果FROM语句没有指定镜像标签,则默认使用latest标签。
- Docker 还存在一个特殊的镜像,名为 scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。
RUN执行命令
每条RUN指令将在当前镜像基础上执行指定命令,并提交为新的镜像,后续的RUN都在之前RUN提交后的镜像为基础
FROM debian:stretchRUN apt-get update
RUN apt-get install -y gcc libc6-dev make wget
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install
上面的这种写法,创建了 7 层镜像。这是完全没有意义的,而且很多运行时不需要的东西,都被装进了镜像里
上面的 Dockerfile 正确的写法应该是这样:
FROM debian:stretchRUN set -x; buildDeps='gcc libc6-dev make wget' \&& apt-get update \&& apt-get install -y $buildDeps \&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \&& mkdir -p /usr/src/redis \&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \&& make -C /usr/src/redis \&& make -C /usr/src/redis install \&& rm -rf /var/lib/apt/lists/* \&& rm redis.tar.gz \&& rm -r /usr/src/redis \&& apt-get purge -y --auto-remove $buildDeps
这里为了格式化还进行了换行。Dockerfile 支持 Shell 类的行尾添加 \ 的命令换行方式,以及行首 # 进行注释的格式。
此外,还可以看到这一组命令的最后添加了清理工作的命令,删除了为了编译构建所需要的软件,清理了所有下载、展开的文件,并且还清理了 apt 缓存文件。这是很重要的一步,镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。
CMD 容器启动命令
CMD 指令的格式和 RUN 相似,也是两种格式:
- shell 格式:CMD <命令>
- exec 格式:CMD [“可执行文件”, “参数1”, “参数2”…]
- 参数列表格式:CMD [“参数1”, “参数2”…]。在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数。
之前介绍容器的时候曾经说过,Docker 不是虚拟机,容器就是进程。既然是进程,那么在启动容器的时候,需要指定所运行的程序及参数。CMD 指令就是用于指定默认的容器主进程的启动命令的。
在运行时可以指定新的命令来替代镜像设置中的这个默认命令,比如,ubuntu 镜像默认的 CMD 是 /bin/bash,如果我们直接 docker run -it ubuntu 的话,会直接进入 bash。我们也可以在运行时指定运行别的命令,如 docker run -it ubuntu cat /etc/os-release。这就是用 cat /etc/os-release 命令替换了默认的 /bin/bash 命令了,输出了系统版本信息。
在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 ",而不要使用单引号。
如果使用 shell 格式的话,实际的命令会被包装为 sh -c 的参数的形式进行执行。比如:
CMD echo $HOME
在实际执行中,会将其变更为:
CMD [ "sh", "-c", "echo $HOME" ]`在这里插入代码片`
这就是为什么我们可以使用环境变量的原因,因为这些环境变量会被 shell 进行解析处理。
提到 CMD 就不得不提容器中应用在前台执行和后台执行的问题。这是初学者常出现的一个混淆。
Docker 不是虚拟机,容器中的应用都应该以前台执行,而不是像虚拟机、物理机里面那样,用 systemd 去启动后台服务,容器内没有后台服务的概念。
一些初学者将 CMD 写为:
CMD service nginx start
然后发现容器执行后就立即退出了。甚至在容器内去使用 systemctl 命令结果却发现根本执行不了。这就是因为没有搞明白前台、后台的概念,没有区分容器和虚拟机的差异,依旧在以传统虚拟机的角度去理解容器。
对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。
而使用 service nginx start 命令,则是希望 upstart 来以后台守护进程形式启动 nginx 服务。而刚才说了 CMD service nginx start 会被理解为 CMD [ “sh”, “-c”, “service nginx start”],因此主进程实际上是 sh。那么当 service nginx start 命令结束后,sh 也就结束了,sh 作为主进程退出了,自然就会令容器退出。
正确的做法是直接执行 nginx 可执行文件,并且要求以前台形式运行。比如:
CMD ["nginx", "-g", "daemon off;"]
COPY 复制文件
格式:
COPY [--chown=<user>:<group>] <源路径>... <目标路径>
COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]
和 RUN 指令一样,也有两种格式,一种类似于命令行,一种类似于函数调用。
用法同ADD,唯一的不同是不能指定远程文件 URLS。
COPY 指令将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置。比如:
COPY package.json /usr/src/app/
<源路径> 可以是多个,甚至可以是通配符,其通配符规则要满足 Go 的 filepath.Match 规则,如:
COPY hom* /mydir/
COPY hom?.txt /mydir/
<目标路径> 可以是容器内的绝对路径,也可以是相对于工作目录的相对路径(工作目录可以用 WORKDIR 指令来指定)。目标路径不需要事先创建,如果目录不存在会在复制文件前先行创建缺失目录。
此外,还需要注意一点,使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git 进行管理的时候。
在使用该指令的时候还可以加上 --chown=: 选项来改变文件的所属用户及所属组。
COPY --chown=55:mygroup files* /mydir/
COPY --chown=bin files* /mydir/
COPY --chown=1 files* /mydir/
COPY --chown=10:11 files* /mydir/
如果源路径为文件夹,复制的时候不是直接复制该文件夹,而是将文件夹中的内容复制到目标路径。
ADD 更高级的复制文件
ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能。
比如 <源路径> 可以是一个 URL,这种情况下,Docker 引擎会试图去下载这个链接的文件放到 <目标路径> 去。下载后的文件权限自动设置为 600,如果这并不是想要的权限,那么还需要增加额外的一层 RUN 进行权限调整,另外,如果下载的是个压缩包,需要解压缩,也一样还需要额外的一层 RUN 指令进行解压缩。所以不如直接使用 RUN 指令,然后使用 wget 或者 curl 工具下载,处理权限、解压缩、然后清理无用文件更合理。因此,这个功能其实并不实用,而且不推荐使用。
如果 <源路径> 为一个 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,ADD 指令将会自动解压缩这个压缩文件到 <目标路径> 去。
在某些情况下,这个自动解压缩的功能非常有用,比如官方镜像 ubuntu 中:
FROM scratch
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /
...
但在某些情况下,如果我们真的是希望复制个压缩文件进去,而不解压缩,这时就不可以使用 ADD 命令了。
在 Docker 官方的中要求,尽可能的使用 COPY,因为 COPY 的语义很明确,就是复制文件而已,而 ADD 则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 ADD 的场合,就是所提及的需要自动解压缩的场合。
另外需要注意的是,ADD 指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。
因此在 COPY 和 ADD 指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用 COPY 指令,仅在需要自动解压缩的场合使用 ADD。
在使用该指令的时候还可以加上 --chown=: 选项来改变文件的所属用户及所属组。
ADD --chown=55:mygroup files* /mydir/
ADD --chown=bin files* /mydir/
ADD --chown=1 files* /mydir/
ADD --chown=10:11 files* /mydir/
ENV 设置环境变量
格式有两种:
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
这个指令很简单,就是设置环境变量而已,无论是后面的其它指令,如 RUN,还是运行时的应用,都可以直接使用这里定义的环境变量。
ENV VERSION=1.0 DEBUG=on \NAME="Happy Feet"
这个例子中演示了如何换行,以及对含有空格的值用双引号括起来的办法,这和 Shell 下的行为是一致的。
定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。比如在官方 node 镜像 Dockerfile 中,就有类似这样的代码:
ENV NODE_VERSION 7.2.0
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \&& gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc \&& grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - \&& tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 \&& rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt \&& ln -s /usr/local/bin/node /usr/local/bin/nodejs
在这里先定义了环境变量 NODE_VERSION,其后的 RUN 这层里,多次使用 $NODE_VERSION 来进行操作定制。可以看到,将来升级镜像构建版本的时候,只需要更新 7.2.0 即可,Dockerfile 构建维护变得更轻松了。
下列指令可以支持环境变量展开: ADD、COPY、ENV、EXPOSE、FROM、LABEL、USER、WORKDIR、VOLUME、STOPSIGNAL、ONBUILD、RUN。
可以从这个指令列表里感觉到,环境变量可以使用的地方很多,很强大。通过环境变量,我们可以让一份 Dockerfile 制作更多的镜像,只需使用不同的环境变量即可。
ARG 构建参数
格式:ARG <参数名>[=<默认值>]
构建参数和 ENV 的效果一样,都是设置环境变量。所不同的是,ARG 所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。但是不要因此就使用 ARG 保存密码之类的信息,因为 docker history 还是可以看到所有值的。
Dockerfile 中的 ARG 指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令 docker build 中用 --build-arg <参数名>=<值> 来覆盖。
灵活的使用 ARG 指令,能够在不修改 Dockerfile 的情况下,构建出不同的镜像。
ARG 指令有生效范围,如果在 FROM 指令之前指定,那么只能用于 FROM 指令中。
ARG DOCKER_USERNAME=library
FROM ${DOCKER_USERNAME}/alpine
RUN set -x ; echo ${DOCKER_USERNAME}
使用上述 Dockerfile 会发现无法输出 ${DOCKER_USERNAME} 变量的值,要想正常输出,你必须在 FROM 之后再次指定 ARG
# 只在 FROM 中生效
ARG DOCKER_USERNAME=library
FROM ${DOCKER_USERNAME}/alpine
# 要想在 FROM 之后使用,必须再次指定
ARG DOCKER_USERNAME=library
RUN set -x ; echo ${DOCKER_USERNAME}
对于多阶段构建,尤其要注意这个问题
# 这个变量在每个 FROM 中都生效
ARG DOCKER_USERNAME=library
FROM ${DOCKER_USERNAME}/alpine
RUN set -x ; echo 1
FROM ${DOCKER_USERNAME}/alpine
RUN set -x ; echo 2
对于上述 Dockerfile 两个 FROM 指令都可以使用 ${DOCKER_USERNAME},对于在各个阶段中使用的变量都必须在每个阶段分别指定:
ARG DOCKER_USERNAME=library
FROM ${DOCKER_USERNAME}/alpine
# 在FROM 之后使用变量,必须在每个阶段分别指定
ARG DOCKER_USERNAME=library
RUN set -x ; echo ${DOCKER_USERNAME}
FROM ${DOCKER_USERNAME}/alpine
# 在FROM 之后使用变量,必须在每个阶段分别指定
ARG DOCKER_USERNAME=library
RUN set -x ; echo ${DOCKER_USERNAME}
VOLUME 定义匿名卷
格式为:
- VOLUME [“<路径1>”, “<路径2>”…]
- VOLUME <路径>
之前我们说过,容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中,后面的章节我们会进一步介绍 Docker 卷的概念。为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。
VOLUME /data
这里的 /data 目录就会在容器运行时自动挂载为匿名卷,任何向 /data 中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行容器时可以覆盖这个挂载设置。比如:
$ docker run -d -v mydata:/data xxxx
在这行命令中,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置。
EXPOSE 暴露端口
EXPOSE 指令是声明容器运行时提供服务的端口,这只是一个声明,在容器运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。
要将 EXPOSE 和在运行时使用 -p <宿主端口>:<容器端口> 区分开来。-p,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。
WORKDIR 指定工作目录
格式为 WORKDIR <工作目录路径>。
使用 WORKDIR 指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录。
之前提到一些初学者常犯的错误是把 Dockerfile 等同于 Shell 脚本来书写,这种错误的理解还可能会导致出现下面这样的错误:
RUN cd /app
RUN echo "hello" > world.txt
如果将这个 Dockerfile 进行构建镜像运行后,会发现找不到 /app/world.txt 文件,或者其内容不是 hello。原因其实很简单,在 Shell 中,连续两行是同一个进程执行环境,因此前一个命令修改的内存状态,会直接影响后一个命令;而在 Dockerfile 中,这两行 RUN 命令的执行环境根本不同,是两个完全不同的容器。这就是对 Dockerfile 构建分层存储的概念不了解所导致的错误。
之前说过每一个 RUN 都是启动一个容器、执行命令、然后提交存储层文件变更。第一层 RUN cd /app 的执行仅仅是当前进程的工作目录变更,一个内存上的变化而已,其结果不会造成任何文件变更。而到第二层的时候,启动的是一个全新的容器,跟第一层的容器更完全没关系,自然不可能继承前一层构建过程中的内存变化。
因此如果需要改变以后各层的工作目录的位置,那么应该使用 WORKDIR 指令。
WORKDIR /appRUN echo "hello" > world.txt
如果你的 WORKDIR 指令使用的相对路径,那么所切换的路径与之前的 WORKDIR 有关:
WORKDIR /a
WORKDIR b
WORKDIR cRUN pwd
RUN pwd 的工作目录为 /a/b/c。
USER 指定当前用户
格式:USER <用户名>[:<用户组>]
USER 指令和 WORKDIR 相似,都是改变环境状态并影响以后的层。WORKDIR 是改变工作目录,USER 则是改变之后层的执行 RUN, CMD 以及 ENTRYPOINT 这类命令的身份。
注意,USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。
RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN [ "redis-server" ]
如果以 root 执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立好的用户来运行某个服务进程,不要使用 su 或者 sudo,这些都需要比较麻烦的配置,而且在 TTY 缺失的环境下经常出错。建议使用 gosu。
# 建立 redis 用户,并使用 gosu 换另一个用户执行命令
RUN groupadd -r redis && useradd -r -g redis redis
# 下载 gosu
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.12/gosu-amd64" \&& chmod +x /usr/local/bin/gosu \&& gosu nobody true
# 设置 CMD,并以另外的用户执行
CMD [ "exec", "gosu", "redis", "redis-server" ]
LABEL 为镜像添加元数据
LABEL 指令用来给镜像以键值对的形式添加一些元数据(metadata)。
LABEL <key>=<value> <key>=<value> <key>=<value> ...
我们还可以用一些标签来申明镜像的作者、文档地址等:
LABEL org.opencontainers.image.authors="yeasy"
LABEL org.opencontainers.image.documentation="https://yeasy.gitbooks.io"
SHELL 指令
格式:SHELL [“executable”, “parameters”]
SHELL 指令可以指定 RUN ENTRYPOINT CMD 指令的 shell,Linux 中默认为 [“/bin/sh”, “-c”]
SHELL ["/bin/sh", "-c"]
RUN lll ; ls
SHELL ["/bin/sh", "-cex"]
RUN lll ; ls
两个 RUN 运行同一命令,第二个 RUN 运行的命令会打印出每条命令并当遇到错误时退出。
3. 使用Dockerfile构建镜像
在 Dockerfile 文件所在目录执行:
$ docker build -t nginx:v3 .
docker build 命令最后有一个 “.”,"."表示当前目录,而 Dockerfile 就在当前目录,因此不少初学者以为这个路径是在指定 Dockerfile 所在路径,这么理解其实是不准确的。这是在指定 上下文路径。那么什么是上下文呢?
当我们进行镜像构建的时候,并非所有定制都会通过 RUN 指令完成,经常会需要将一些本地文件复制进镜像,比如通过 COPY 指令、ADD 指令等。而 docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?
这就引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径,docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。
如果在 Dockerfile 中这么写:
COPY ./package.json /app/
这并不是要复制执行 docker build 命令所在的目录下的 package.json,也不是复制 Dockerfile 所在目录下的 package.json,而是复制 上下文(context) 目录下的 package.json。
因此,COPY 这类指令中的源文件的路径都是相对路径。
相关文章:
Docker技术入门| Part03:Dockerfile详解(Dockerfile概念、Dockerfile 指令、使用Dockerfile构建镜像)
文章目录 1. Dockerfile概念2. Dockerfile 指令FROM 指定基础镜像RUN执行命令CMD 容器启动命令COPY 复制文件ADD 更高级的复制文件ENV 设置环境变量ARG 构建参数VOLUME 定义匿名卷EXPOSE 暴露端口WORKDIR 指定工作目录USER 指定当前用户LABEL 为镜像添加元数据SHELL 指令 3. 使…...
分享一个有意思的线程相关的程序运行题
翻开之前的代码,发现了一个有意思的代码,猜以下代码的运行结果: package thread;/*** author heyunlin* version 1.0*/ public class ThreadMethodExample {public static void main(String[] args) {Thread thread new Thread(new Runnabl…...
集合的进阶学习
集合体系结构 Collection 单列集合 包含List Set List 包含ArrayList LinkedList Set包含HashSet TreeSet HashSet包含LinkedHashSet List系列集合:添加的元素是有序的、可重复、有索引 Set系列集合:添加的元素是无序的、不重复、无索引 Collectio…...
Java真过饱和了吗?现在学Java迟了?
Java行业内幕揭秘 我是某有名机构的线下课Java老师,负责Java热门框架教学,如Spring、Spring MVC、Spring Boot。但最近被解雇了,让我来吐槽一下。Java现在的学习人数真的太多太多了。 Java的学习饱和度 Java学习的人太多,给你一…...
glibc2.35-通过tls_dtor_list劫持exit执行流程
前言 glibc2.35删除了malloc_hook、free_hook以及realloc_hook,通过劫持这三个hook函数执行system已经不可行了。 传统堆漏洞利用是利用任意地址写改上上述几个hook从而执行system,在移除之后则需要找到同样只需要修改某个地址值并且能够造成程序流劫持…...
linux-OpenSSL升级
1.安装编译所需的安装包 yum install -y gcc make perl zlib-devel 2.从 OpenSSL 官网下载(https://www.openssl.org/source/openssl-1.1.1v.tar.gz) 注:如果原先版本为1.x.x,升级时还是需要选择1.x.x 3. 编译安装 # 解压tar -xvf openssl-1.1.1v.tar…...
Nginx全家桶配置详解
源码包安装NGINX A,搭建Web Server,任意HTML页面,其8080端口提供Web访问服务,截图成功访问http(s)://[Server1]:8080并且回显Web页面。保留Server1,但是不允许直接访问Server 1,再部署1套NGINX …...
CMake生成Visual Studio工程
CMake – 生成Visual Studio工程 C/C项目经常使用CMake构建工具。CMake 项目文件(例如 CMakeLists.txt)可以直接由 Visual Studio 使用。本文要说明的是如何将CMake项目转换到Visual Studio解决方案(.sln)或项目(.vcxproj) 开发环境 为了生成Visual S…...
数学建模--K-means聚类的Python实现
目录 1.算法流程简介 2.1.K-mean算法核心代码 2.2.K-mean算法效果展示 3.1.肘部法算法核心代码 3.2.肘部法算法效果展示 1.算法流程简介 #k-means聚类方法 """ k-means聚类算法流程: 1.K-mean均值聚类的方法就是先随机选择k个对象作为初始聚类中心. 2.这…...
防坠安全带上亚马逊美国站要求的合规标准是什么?
防坠安全带 防坠安全带是一种防护装备,适合工人在高空作业时或在可能发生跌落的无防护边缘行走时穿着。防坠安全带设计用于包裹身体躯干,并将坠落力至少分布到大腿上部、骨盆、胸部和肩部。防坠安全带是固定物体与非固定物体之间的连接物,通…...
PDF转Word的方法分享与注意事项。
PDF和Word是两种常用的文档格式,它们各有优点,适用于不同的场景。然而,有时候我们需要将PDF转换为Word,以便更好地进行编辑和排版。本文将介绍几种常用的PDF转Word的方法,并分享一些注意事项。 一、PDF转Word的方法 使…...
gitlab配置webhook,commit message的时候校验提交的信息
在 GitLab 中配置 Webhook 来调用 Java 接口以校验 commit 信息,是很多公司的一些要求,因为提交信息的规范化是必要的 在 GitLab 项目中进入设置页面。 在左侧导航栏中选择 “Webhooks”(Web钩子)。 在 Webhooks 页面中点击 “…...
借助CIFAR10模型结构理解卷积神经网络及Sequential的使用
CIFAR10模型搭建 CIFAR10模型结构 0. input : 332x32,3通道32x32的图片 --> 特征图(Feature maps) : 3232x32即经过32个35x5的卷积层,输出尺寸没有变化(有x个特征图即有x个卷积核。卷积核的通道数与输入的通道数相等,即35x5&am…...
Java # Java基础八股
1、JVM、JRE、JDK之间的关系 个人理解:JVM可以帮助屏蔽底层的操作系统,使程序一次编译到处都可以运行,JVM可以运行class文件。JRE是java文件运行的环境,但不能新建程序,JRE包含JVM。JDK功能最齐全,包含了编…...
【Spring Boot】SpringBoot 2.6.6 集成 SpringDoc 1.6.9 生成swagger接口文档
文章目录 前言一、SpringDoc是什么?二、使用步骤1.引入库2.配置类3.访问测试 总结其他配置立个Flag 前言 之前常用的SpringFox在2020年停止更新了,新项目集成SpringFox出来一堆问题,所以打算使用更活跃的SpringDoc,这里简单介绍一…...
【算法】快速排序 详解
快速排序 详解 快速排序1. 挖坑法2. 左右指针法 (Hoare 法)3. 前后指针法4. 快排非递归 代码优化 排序: 排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。 稳定性&…...
架构师spring boot 面试题
spring boot 微服务有哪些特点? Spring Boot 微服务具有以下特点: 独立性:每个微服务都是独立的部署单元,有自己的代码库和数据库。这使得微服务可以独立开发、测试、部署和扩展。 分布式:微服务架构将一个大型应用程…...
电商系统架构设计系列(十一):在电商的交易类系统中,如何正确地使用 Redis 这样的缓存系统呢?需要考虑哪些问题?
上篇文章中,我给你留了一个思考题:在电商的交易类系统中,如何正确地使用 Redis 这样的缓存系统呢?需要考虑哪些问题? 这篇文章,我们来聊聊。 引言 我们知道,大部分面向公众用户的互联网系统&a…...
MySQL数据库和表的操作
数据库基础 存储数据用文件就可以了,为什么还要弄个数据库? 文件保存数据有以下几个缺点: 1、文件的安全性问题 2、文件不利于数据查询和管理 3、文件不利于存储海量数据 4、文件在程序中控制不方便 数据库存储介质: 磁盘 内存 为了解决上…...
DAY-01--分布式微服务基础概念
一、项目简介 了解整体项目包含后端、前端、周边维护。整个项目的框架知识。 二、分布式基础概念 1、微服务 将应用程序 基于业务 拆分为 多个小服务,各小服务单独部署运行,采用http通信。 2、集群&分布式&节点 集群是个物理形态,…...
C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...
2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)
本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
