使用 Dockerfile 定制镜像详解

使用 Dockerfile 定制镜像详解

  • 1.Dockerfile
    • FROM 指定基础镜像
    • RUN 执行命令
    • 构建镜像
  • 2.COPY 复制文件
  • 3.ADD 更高级的复制文件
  • 4.CMD 容器启动命令
  • 5.ENTRYPOINT 入口点
  • 6.ENV 设置环境变量
  • 7.ARG 构建参数
  • 8.VOLUME 定义匿名卷
  • 9.EXPOSE 暴露端口
  • 10.WORKDIR 指定工作目录
  • 11.USER 指定当前用户
  • 12.LABEL 为镜像添加元数据
  • 13.SHELL 指令

1.Dockerfile

Dockerfile 是一个文本文件,其内包含了一条条的指令,每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建

还以之前定制 nginx 镜像为例,这次我们使用 Dockerfile 来定制:

FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

FROM 指定基础镜像

FROM 就是指定 基础镜像,因此一个 Dockerfile 中 FROM 是必备的指令,并且必须是第一条指令

在 Docker Hub 上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像,如 nginx、redis、mongo、mysql、httpd、php、tomcat 等;也有一些方便开发、构建、运行各种语言应用的镜像,如 node、openjdk、python、ruby、golang 等。可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制

除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像:

FROM scratch
...

如果你以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在

RUN 执行命令

RUN 指令是用来执行命令行命令的。由于命令行的强大能力,RUN 指令在定制镜像时是最常用的指令之一。其格式有两种:

  • shell 格式:RUN <命令>,就像直接在命令行中输入的命令一样
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
  • exec 格式:RUN ["可执行文件", "参数1", "参数2"],这更像是函数调用中的格式

Dockerfile 中每一个指令都会建立一层,RUN 也不例外。每一个 RUN 的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后,commit 这一层的修改,构成新的镜像。所以在执行多条命令的场景下,最好遵守如下的规范:

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

这一组命令的最后添加了清理工作的命令,删除了为了编译构建所需要的软件,清理了所有下载、展开的文件,并且还清理了 apt 缓存文件。这是很重要的一步,镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉

构建镜像

1、编写dockerfile

FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

2、在 Dockerfile 文件所在目录执行

docker build -t nginx .

在这里插入图片描述

从命令的输出结果中,我们可以清晰的看到镜像的构建过程

镜像构建上下文中的坑🕳

当我们进行镜像构建的时候,并非所有定制都会通过 RUN 指令完成,经常会需要将一些本地文件复制进镜像,比如通过 COPY 指令、ADD 指令等。而 docker build 命令构建镜像,其实并非在本地构建,而是在服务端,也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端获得本地文件呢?

这就引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径,docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件

有些初学者在发现 COPY /opt/xxxx /app 不工作后,于是干脆将 Dockerfile 放到了硬盘根目录去构建,结果发现 docker build 执行后,在发送一个几十 GB 的东西,极为缓慢而且很容易构建失败。那是因为这种做法是在让 docker build 打包整个硬盘,这显然是使用错误

一般来说,应该会将 Dockerfile 置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎,那么可以用 .gitignore 一样的语法写一个 .dockerignore,该文件是用于剔除不需要作为上下文传递给 Docker 引擎的


2.COPY 复制文件

COPY 指令将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置。比如:

COPY package.json /usr/src/app/

使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等

在使用该指令的时候还可以加上 --chown=<user>:<group> 选项来改变文件的所属用户及所属组:

COPY --chown=55:mygroup files* /mydir/
COPY --chown=bin files* /mydir/
COPY --chown=1 files* /mydir/
COPY --chown=10:11 files* /mydir/

如果源路径为文件夹,复制的时候不是直接复制该文件夹,而是将文件夹中的内容复制到目标路径


3.ADD 更高级的复制文件

ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能

比如 <源路径> 可以是一个 URL,这种情况下,Docker 引擎会试图去下载这个链接的文件放到 <目标路径> 去。下载后的文件权限自动设置为 600

如果 <源路径> 为一个 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,ADD 指令将会自动解压缩这个压缩文件到 <目标路径> 去

Docker 官方的 Dockerfile 最佳实践文档 中要求,尽可能的使用 COPY,因为 COPY 的语义很明确,就是复制文件而已,而 ADD 则包含了更复杂的功能,其行为也不一定很清晰。最适合使用 ADD 的场合,就是所提及的需要自动解压缩的场合

ADD 指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢


4.CMD 容器启动命令

在启动容器的时候,需要指定所运行的程序及参数。CMD 指令就是用于指定默认的容器主进程的启动命令的

CMD 指令的格式和 RUN 相似,也是两种格式:

  • shell 格式:CMD <命令>
  • exec 格式:CMD [“可执行文件”, “参数1”, “参数2”…]

在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 ",而不要使用单引号

如果使用 shell 格式的话,实际的命令会被包装为 sh -c 的参数的形式进行执行。比如:

CMD echo $HOME

在实际执行中,会将其变更为:

CMD [ "sh", "-c", "echo $HOME" ]

坑🕳:容器执行后就立即退出了

容器内没有后台服务的概念。一些初学者将 CMD 写为:

CMD service nginx start

然后发现容器执行后就立即退出了。这就是因为没有搞明白前台、后台的概念

对于容器而言,其启动程序就是容器应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西

而使用 service nginx start 命令,则是希望 init 系统以后台守护进程的形式启动 nginx 服务。而刚才说了 CMD service nginx start 会被理解为 CMD [ "sh", "-c", "service nginx start"],因此主进程实际上是 sh。那么当 service nginx start 命令结束后,sh 也就结束了,sh 作为主进程退出了,自然就会令容器退出

正确的做法是直接执行 nginx 可执行文件,并且要求以前台形式运行。比如:

CMD ["nginx", "-g", "daemon off;"]

5.ENTRYPOINT 入口点

ENTRYPOINT 的目的和 CMD 一样,都是在指定容器启动程序及参数

那么有了 CMD 后,为什么还要有 ENTRYPOINT 呢?

1、场景一:让镜像变成像命令一样使用

假设我们需要一个得知自己当前公网 IP 的镜像

FROM ubuntu:18.04
RUN apt-get update \&& apt-get install -y curl \&& rm -rf /var/lib/apt/lists/*
ENTRYPOINT [ "curl", "-s", "http://myip.ipip.net" ]

构建镜像:

docker build -t myip .

我们来尝试直接使用 docker run myip -i

$ docker run myip
当前 IP:xx.xx.xx.xx 来自:纽约市 联信$ docker run myip -i
HTTP/1.1 200 OK
Server: nginx/1.8.0
Date: Tue, 22 Nov 2016 05:12:40 GMT
Content-Type: text/html; charset=UTF-8
Vary: Accept-Encoding
X-Powered-By: PHP/5.6.24-1~dotdeb+7.1
X-Cache: MISS from cache-2
X-Cache-Lookup: MISS from cache-2:80
X-Cache: MISS from proxy-2_6
Transfer-Encoding: chunked
Via: 1.1 cache-2:80, 1.1 proxy-2_6:8006
Connection: keep-alive当前 IP:xx.xx.xx.xx 来自:纽约市 联信

当存在 ENTRYPOINT 后,CMD 的内容将会作为参数传给 ENTRYPOINT,而这里 -i 就是新的 CMD,因此会作为参数传给 curl,从而达到了我们预期的效果

2、场景二:应用运行前的准备工作

mysql 类的数据库,可能需要一些数据库配置、初始化的工作,这些工作要在最终的 mysql 服务器运行之前解决

此外,可能希望避免使用 root 用户去启动服务,从而提高安全性,而在启动服务前还需要以 root 身份执行一些必要的准备工作,最后切换到服务用户身份启动服务。或者除了服务外,其它命令依旧可以使用 root 身份执行,方便调试等

这些准备工作是和容器 CMD 无关的,无论 CMD 为什么,都需要事先进行一个预处理的工作。这种情况下,可以写一个脚本,然后放入 ENTRYPOINT 中去执行,而这个脚本会将接到的参数(也就是 <CMD>)作为命令,在脚本最后执行。比如官方镜像 redis 中就是这么做的:

FROM alpine:3.4
...
RUN addgroup -S redis && adduser -S -G redis redis
...
ENTRYPOINT ["docker-entrypoint.sh"]EXPOSE 6379
CMD [ "redis-server" ]

6.ENV 设置环境变量

格式有两种:

  • ENV <key> <value>
  • ENV <key1>=<value1> <key2>=<value2>...

定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。比如在官方 node 镜像 Dockerfile 中,就有类似这样的代码:

ENV NODE_VERSION 7.2.0RUN 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 构建维护变得更轻松了

通过环境变量,我们可以让一份 Dockerfile 制作更多的镜像,只需使用不同的环境变量即可


7.ARG 构建参数

格式:ARG <参数名>[=<默认值>]

构建参数和 ENV 的效果一样,都是设置环境变量。所不同的是,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}

8.VOLUME 定义匿名卷

格式为:

  • VOLUME [“<路径1>”, “<路径2>”…]
  • VOLUME <路径>

容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中

为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在 Dockerfile 中,我们可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据

VOLUME /data

在这行命令中,就使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置


9.EXPOSE 暴露端口

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

EXPOSE 指令是声明容器运行时提供服务的端口,这只是一个声明,在容器运行时并不会因为这个声明应用就会开启这个端口的服务。在 Dockerfile 中写入这样的声明有两个好处,一个是帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;另一个用处则是在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口

要将 EXPOSE 和在运行时使用 -p <宿主端口>:<容器端口> 区分开来

-p,是映射宿主端口和容器端口,换句话说,就是将容器的对应端口服务公开给外界访问,而 EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射


10.WORKDIR 指定工作目录

格式为 WORKDIR <工作目录路径>

使用 WORKDIR 指令可以来指定工作目录,以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR 会帮你建立目录

如果需要改变以后各层的工作目录的位置,那么应该使用 WORKDIR 指令,例如:

WORKDIR /app
RUN echo "hello" > world.txt

如果你的 WORKDIR 指令使用的相对路径,那么所切换的路径与之前的 WORKDIR 有关:

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

RUN pwd 的工作目录为 /a/b/c


11.USER 指定当前用户

格式:USER <用户名>[:<用户组>]

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" ]

12.LABEL 为镜像添加元数据

LABEL 指令用来给镜像以键值对的形式添加一些元数据

语法:

LABEL <key>=<value> <key>=<value> <key>=<value> ...

我们还可以用一些标签来申明镜像的作者、文档地址等:

LABEL org.opencontainers.image.authors="dahezhiquan"
LABEL org.opencontainers.image.documentation="https://dahezhiquan.gitbooks.io"

13.SHELL 指令

SHELL 指令可以指定 RUN ENTRYPOINT CMD 指令的 shell,Linux 中默认为 ["/bin/sh", "-c"]

SHELL ["/bin/sh", "-c"]RUN lll ; lsSHELL ["/bin/sh", "-cex"]RUN lll ; ls

两个 RUN 运行同一命令,第二个 RUN 运行的命令会打印出每条命令并当遇到错误时退出

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/662922.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

通过Netbackup恢复Oracle备份实操手册

1、系统环境描述 1 2、恢复前数据备份 2 2.1 在NBU上执行一次完整的备份 2 2.2 查看ORACLE的备份集 3 2.2.1在备份客户端上查看备份集 3 2.2.2在备份服务器netbackup上查看客户端备份集 4 3、本机恢复方法 5 3.1丢失SPFILE文件恢复方法 5 3.2丢失CONTROLFILE文件恢复方…

51单片机编程应用(C语言):模块化编程

下面我们模块化几个函数&#xff1a; Delay.c //延时子函数 void Delay(unsigned int xms) {unsigned char i, j;while(xms--){i 2;j 239;do{while (--j);} while (--i);} } Delay.h #ifndef __DELAY_H__ #define __DELAY_H__void Delay(unsigned int xms);#endifNixie.h …

js 设置、获取、删除标签属性以及H5自定义属性

1. 设置标签属性 使用setAttribute()(‘属性名’, ‘属性值’)方法可以添加、修改、删除属性。   下面的demo是为input添加、修改、删除value属性&#xff1a; 1.1. HTML <input type"text" class"input"> <input type"text" class…

有没有合适写毕业论文的AI工具?

最近挺多同学在忙着写毕业论文&#xff0c;不断在“提交-打回-修改-提交”过程里循环着&#xff0c;好不容易写完了&#xff0c;还得考虑论文查重的问题&#xff01;基哥作为一名曾经的毕业生&#xff0c;当然也体验过这种痛苦了。 但是&#xff0c;大人&#xff0c;时代变了&…

无法在 word 中登录 Grammarly

目录 1. 情况描述 2. 解决方法 3. 原因分析 1. 情况描述 在浏览器中可以登录 Grammarly&#xff0c;但是在 word 中登录失败&#xff0c;大致如下图所示&#xff1a; 我自己没有截图&#xff0c;这是网上别人的图&#xff0c;但差不多都长这个样子。 2. 解决方法 我点击了…

推荐一个好用的人脑解剖结构及功能注释3D图谱——3D Brain

大脑是一个非凡的结构&#xff0c;它定义了我们是谁&#xff0c;以及我们如何体验世界。神经成像技术的最新进展使我们能够看到大脑内部&#xff0c;让我们能够看到构成大脑的组成部分并了解它们对应的功能。大脑的大体结构对大多数人来说都很熟悉。 前脑的外层构成了我们熟悉…

shell - 正则表达式和grep命令和sed命令

一.正则表达式概述 1.正则表达式定义 1.1 定义 使用字符串描述、匹配一系列符合某个规则的字符串 1.2 了解 普通字符&#xff1a; 大小写字母、数字、标点符号及一些其它符号元字符&#xff1a; 在正则表达式中具有特殊意义的专用字符 1.3 层次分类 基础正则表达式扩展正…

MySQL 备份恢复

1.1 MySQL日志管理 在数据库保存数据时&#xff0c;有时候不可避免会出现数据丢失或者被破坏&#xff0c;这样情况下&#xff0c;我们必须保证数据的安全性和完整性&#xff0c;就需要使用日志来查看或者恢复数据了。 数据库中数据丢失或被破坏可能原因&#xff1a; 误删除数…

机器学习2-简单的二分类问题

需求&#xff1a; 假设现在需要对数据进行二分类&#xff0c;小于0.5的&#xff0c;打上0的标记&#xff0c;大于0.5的&#xff0c;打上1的标记&#xff0c;怎么做&#xff1f; 分析&#xff1a; 这是一个简单的二分类问题&#xff0c;使用逻辑回归模型。 代码&#xff1a; …

【PostgresSQL系列】 ltree简介及基于SpringBoot实现 ltree数据增删改查

本文将对PostgresSQL中的ltree进行相关概念介绍&#xff0c;并以示例代码讲解ltree数据增删改查功能的实现。 作者&#xff1a;后端小肥肠 目录 1.前言 2. 基础概念 2.1. ltree 2.2. lquery 2.3. ltxtquery 2.4. ltree函数及操作符 2.4.1. ltree函数 2.4.2. ltree操作符…

Python实现TCP和UDP通信

目录 一&#xff1a;TCP 二&#xff1a;UDP 一&#xff1a;TCP 在Python中实现TCP通信可以通过使用内置的socket模块来完成。以下是一个简单的示例&#xff0c;展示了如何使用Python的socket模块创建一个TCP客户端和服务器。 TCP服务器 import socket def start_server(): s…

Linux之系统安全与应用续章

目录 一. PAM认证 1.2 初识PAM 1.2.1 PAM及其作用 1.2.2 PAM认证原理 1.2.3 PAM认证的构成 1.2.4 PAM 认证类型 1.2.5 PAM 控制类型 二. limit 三. GRUB加密 /etc/grub.d目录 四. 暴力破解密码 五. 网络扫描--NMAP 六. 总结 一. PAM认证 1.2 初识PAM PAM是Linux系…

【深蓝学院】移动机器人运动规划--第3章 基于采样的路径规划--作业

0. Assignment T1. MATLAB实现RRT 1.1 GPT-4任务分析 RRT伪代码&#xff1a; 任务1即使用matlab实现RRT&#xff0c;结合作业所给框架&#xff0c;简单梳理&#xff0c;可结合1.2代码理解&#xff1a; 设置start&#xff0c;goal&#xff0c;near to goal threshold Thr&am…

react+ts

1.概念 React和TypeScript集合使用的重点集中在 存储数据/状态有关的Hook函数以及组件接口的位置&#xff0c;这些地方最需要数据类型校验 2.使用Vite创建项目 Vite是前端工具链工具&#xff0c;可以帮助我们快速创建一个 reactts 的工程化环境出来 Vite官网&#xff1a;ht…

2024年美赛 (D题ICM)| 湖流网络水位控制 |数学建模完整代码+建模过程全解全析

当大家面临着复杂的数学建模问题时&#xff0c;你是否曾经感到茫然无措&#xff1f;作为2022年美国大学生数学建模比赛的O奖得主&#xff0c;我为大家提供了一套优秀的解题思路&#xff0c;让你轻松应对各种难题。 让我们来看看美赛的D题&#xff01; 完整内容可以在文章末尾领…

子查询练习2

数据表 链接&#xff1a;https://pan.baidu.com/s/1dPitBSxLznogqsbfwmih2Q 提取码&#xff1a;b0rp --来自百度网盘超级会员V5的分享 1.查询和Zlotkey相同部门的员工姓名和工资 2.查询工资比公司平均工资高的员工的员工号,姓名和工资 3.查询工资大于所有JOB_IDSA_MAN的员工…

Python中的单元测试框架:使用unittest进行有效测试

一、介绍 在软件开发中&#xff0c;单元测试是一种测试方法&#xff0c;它用于检查单个软件组件&#xff08;例如函数或方法&#xff09;的正确性。Python 提供了一个内置的单元测试库&#xff0c;名为 unittest&#xff0c;可以用来编写测试代码&#xff0c;然后运行测试&…

如何在 Golang 中使用 crypto/ed25519 进行数字签名和验证

如何在 Golang 中使用 crypto/ed25519 进行数字签名和验证 引言crypto/ed25519 算法简介环境搭建和准备工作生成密钥对进行数字签名 验证签名实际应用场景案例总结 引言 在当今数字化时代&#xff0c;网络安全显得尤为重要。无论是在网上进行交易、签署合同&#xff0c;还是发…

BioTech - 小分子药物设计与优化 概述

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/135998902 小分子药物设计与优化&#xff0c;是利用计算机辅助技术&#xff0c;根据特定的生物学靶点&#xff0c;发现和改进具有治疗作用的小分子…

C++ Webserver从零开始:基础知识(七)——多进程编程

前言 在学习操作系统时&#xff0c;我们知道现代计算机往往都是多进程多线程的&#xff0c;多进程和多线程技术能大大提高了CPU的利用率&#xff0c;因此在web服务器的设计中&#xff0c;不可避免地要涉及到多进程多线程技术。 这一章将简要讲解web服务器中的多进程编程&#x…