小白都能看懂的Docker一步步构建后端程序教程

小白都能看懂的Docker一步步构建后端程序教程
星溢栈首先我们要知道两种文件:Dockerfile和docker-compose.yml,在一些开源项目中很常见。
Docker安装
- 详见官方安装文档:Install Docker Engine
- 或者菜鸟教程:Docker 安装
(我每次安装都去菜鸟教程复制命令)
Dockerfile
Dockerfile 是用于构建 Docker 镜像的一种脚本语言,它包含一系列的指令和参数,用于指定如何构建镜像。在 Dockerfile 中,我们可以指定基础镜像、容器中运行的命令、需要复制到容器中的文件、环境变量等等。通过编写 Dockerfile,我们可以将应用程序打包到一个独立的、可移植的镜像中,这使得应用程序的部署和运行变得非常简单和可靠。
上面的文本通俗来说,Dockerfile 是一个用于构建 Docker 镜像的脚本语言,它包含一系列的指令和参数,用于指定如何构建镜像并完成应用程序的打包和部署。
下面是一个示例,将一个简单的Go语言程序用Docker运行起来:
1 | # 拉取运行的镜像 |
Docker Compose
🌟 使用之前需要安装docker-compose:Installation scenarios,这里推荐在安装了Docker之后,在 Ubuntu 等类 Unix 系统上使用 sudo apt install docker-compose 安装,CentOS 等类 Red Hat 的系统上使用 sudo yum install docker-compose 安装。
Docker Compose 是一个 Docker 官方提供的工具,用于简化多个 Docker 容器的管理。通过 Docker Compose,我们可以使用一个 YAML 文件来定义和配置多个容器,然后使用一个命令就可以启动、停止和管理这些容器。使用 Docker Compose 可以帮助我们更方便地管理多个容器,同时也可以加快应用程序的开发和部署流程。
简言之就是可以将多个 Docker 容器组合在一起,并通过一个配置文件来定义它们之间的关系和运行方式。
下面是一个示例:
1 | version: '3' |
这个示例使用了Docker Compose的YAML文件格式,定义了两个服务:web和db,在该示例中,web服务是一个基于Dockerfile构建的Web应用程序,该应用程序监听8080端口。db服务则是一个使用MySQL 5.7镜像启动的数据库服务。
Docker compose中可以使用服务名称来指定服务之间的连接,在这个示例中,web服务可以使用 db 作为数据库的主机名来连接 db 服务。
部署后端程序
一般来说,一个后端程序需要有程序主体、配置文件、关系型数据库 和 Redis数据库,这里关系型数据库用MySQL作为示例部署一个Go语言的后端程序。
确定部署流程
我们部署一个后端程序一般的步骤是:
- 编写配置文件,方便使用时随时修改
- 有Golang环境,将程序编译成可执行二进制文件
- 运行程序
- 程序连接数据库
- 用户调用接口使用
那么我们就可以按照上面步骤来编写Dockerfile和docker-compose.yml文件了
编写Dockerfile文件
Dockerfile中常用的指令解释,还是很好理解的:
FROM: 指定基础镜像,例如 FROM golang:1.16-alpine,表示以 golang:1.16-alpine 为基础镜像构建 (这里的基础镜像来自 Docker 镜像仓库)。RUN: 在容器内部执行命令,例如 RUN apt-get update && apt-get install -y curl,表示在容器内部执行更新和安装 curl 命令。COPY: 复制本地文件到容器内部,例如 COPY ./app /app,表示将主机当前目录下的 app 目录复制到容器内部的 /app 目录。WORKDIR: 设置容器内部的工作目录,例如 WORKDIR /app,表示将容器内部的工作目录设置为 /app。EXPOSE: 暴露容器内部的端口,例如 EXPOSE 8080,表示暴露容器内部的 8080 端口。CMD: 设置容器启动时需要执行的命令,例如 CMD [“./app”],表示容器启动时执行 /app 命令。ENTRYPOINT: 用于设置容器启动时需要执行的程序。ENV: 用于设置环境变量。
下面就是编写Dockerfile的教程:
我们先要准备一个脚本,因为在数据库和我们自己的程序并行部署构建时,会出现数据库还没启动,我们自己的程序就开始连接,导致连接失败。这里我们需要一个脚本:
wait-for-it.sh。🔮 脚本仓库:https://github.com/vishnubob/wait-for-it,直接下载仓库中的
wait-for-it.sh即可,这里演示我们将它放在我们程序的根目录。我们先要拉取一个基础运行的镜像,这里就用官方镜像库中的
golang:lastest来作为基础镜像(也可以指定版本),方便后续操作,我们将他别名为builder。1
FROM golang:lastest AS builder
在这个基础镜像环境下,相当于我们的程序已经在有Go语言环境下的虚拟机下运行了。
我们要配置好该有的一些设置。在Go语言写的后端程序中,有一些包下载的时候因为众所周知网络原因 (我想学习Go语言的同学都有感受),一些包没法直接拉取下来,需要借助设置
GOPROXY,借助代理来下载包。同时保证我们编译的程序是Linux系统、X86架构环境运行 (使用arm架构的同学自行修改),所以我们需要设置程序的环境变量。1
2
3
4
5ENV GO111MODULE=on \
GOPROXY=https://goproxy.cn,direct \
CGO_ENABLED=0 \
GOOS=linux \
GOARCH=amd64我们设置一个工作目录
/build(叫啥都行,举个例子而已),让程序在这个目录中编译和执行。1
WORKDIR /build
为什么要设置工作目录:WORKDIR 指令用于设置 Docker 容器中的工作目录。它会在容器中创建一个指定的目录,并将其作为后续指令(如 RUN, CMD, ENTRYPOINT)的默认工作目录。也就是说,当我们执行其他指令时,会在这个工作目录下进行。
将我们的代码复制到容器中。
1
COPY . .
为什么COPY指令两边都是
.:使用 COPY 指令时,第一个参数表示要复制的源文件或目录的路径,第二个参数表示目标路径。当第二个参数是一个相对路径时,它将被视为相对于 WORKDIR 指令设置的工作目录。
设置工作目录后,COPY . .中第一个参数.表示当前目录,也就是 Dockerfile 所在的目录,第二个参数.表示相对于工作目录 /build 的当前目录,也就是将当前目录复制到容器的 /build 目录中。我们就要开始下载程序中用到的第三方包了,这里需要执行命令
go mod download。1
RUN go mod download
我们将程序编译成二进制可执行文件,执行命令
go build -o main。1
RUN go build -o main
为了节省运行时的性能开销,能不使用到资源的尽量不要。因为我们已经编译好了可运行的二进制文件,所以运行起来并不需要Go语言环境,这里可以选择在更小的基础镜像中运行,这里我们选择
debian:bullseye-slim,只包含了最基本的系统组件和软件包,用不到的东西基本没有。1
FROM debian:bullseye-slim
❗ 在运行起来的时候可能会发现,当程序有对https协议的API接口等请求时会出现 “docker tls: failed to verify certificate: x509: certificate signed by unknown authority” 这表明我们运行的环境里面没有配置证书,这时我们可以通过apt包管理来安装相关包。
1
RUN apt update && apt install apt-utils && apt install -y ca-certificates
❗ 在运行起来的时候还可能会发现,时区不太对,所以还要安装响应的包和指令来更改时区。
1
2
3RUN apt-get install -y tzdata &&\
ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&\
dpkg-reconfigure -f noninteractive tzdata如果有其他的需求也可自己装一些包。
❗ 如果是国内服务器换源会更快,可以在上面指令之前加上换源指令。
1
2
3RUN echo "deb http://mirrors.aliyun.com/debian bullseye main" > /etc/apt/sources.list
RUN echo "deb http://mirrors.aliyun.com/debian-security bullseye-security main" >> /etc/apt/sources.list
RUN echo "deb http://mirrors.aliyun.com/debian bullseye-updates main" >> /etc/apt/sources.list然后从原来的编译虚拟机中将编译好的可执行文件以及配置文件(示例为
config.yaml)复制到这个新的镜像环境中。1
2COPY --from=builder /build/main /
COPY ./config.yaml /下面为啥不用 --from=builder:因为第一个是在上面镜像构建的环境中的build工作目录中编译好的文件,不加是直接从当前目录(没在容器中)复制文件。当然也可以加,从上面镜像构建的环境中获取,两个文件一样,所以没必要加。
将我们的脚本也复制过来,并且给他775权限,可运行。
1
2COPY ./wait-for-it.sh /
RUN chmod 775 wait-for-it.sh
如果你的后端程序用不着数据库
如果那你的程序用不着数据库,那么就不存在容器之间的关联了,第1步和第10步就无需使用,因为你不需要连接数据库(滑稽)。
当然,程序运行的话则需要在Dockerfile这里写了:
1 | CMD ["./main"] |
完整版Dockerfile代码:
因为每个指令都会产生一个新的镜像层,不精简指令的话会产生很多镜像,所以下面代码为精简后的代码。
1 | # 构建的基础镜像 |
执行以下命令:
1 | docker run -p <host-port>:<container-port> <image-name> |
CMD和RUN的区别:CMD 指令用于在容器启动时运行一个命令。当我们使用 docker run 命令启动容器时,Docker 会在容器内部执行 CMD 指令中指定的命令。如果在 Dockerfile 中定义了多个 CMD 指令,只有最后一个 CMD 指令会生效。RUN 指令用于在 Docker 镜像构建过程中执行命令。
这里编写Dockerfile文件就到尾声了,下面是上面步骤的完整 Dockerfile 文件内容。
因为每个指令都会产生一个新的镜像层,不精简指令的话会产生很多镜像,所以下面代码为精简后的代码。
1 | # 构建的基础镜像 |
这里我们编写的Dockerfile文件是给到下面docker-compose.yml使用的,所以不需要将编译好的程序在这里使用执行指令执行。
编写docker-compose.yml文件
Docker compose文件使用的是.yml后缀的YAML格式配置文件,文件内主要包括:
version: 指定 Docker Compose 文件的版本。该字段值需要使用字符串格式进行指定,例如 version: ‘3’,目前最新版本使用’3’版本就好。services: 多个容器的服务配置都在services下:container_name:字段用于指定容器的名称。每个容器都有自己的名称,方便来标识和管理容器。image 或 build:指定服务使用的镜像名称或 Dockerfile 路径。如果同时指定了 image 和 build,则 Compose 将使用 build 选项构建镜像,并使用 image 选项指定的名称来标记该镜像。command:指定容器启动时运行的命令。environment:指定容器运行时需要的环境变量。ports:指定主机端口与容器端口的映射关系。格式为 host_port:container_port。depends_on:指定容器间的依赖关系。这可以确保某个服务在其所依赖的服务启动之后再启动。volumes:用于定义容器和主机之间的数据卷(volume)映射关系。数据卷是一种持久化存储数据的方式,它可以在容器和主机之间共享数据。
认识这些后,我们就可以编写docker-compose.yml文件了,再确定一下我们需要的服务:后端程序、关系型数据库和Redis缓存数据库,所以我们需要编写3个services服务配置来支撑。
打上版本号和services服务。
1
2
3
4
5
6# 版本号
version: '3'
# 各种服务
services:
# ...写下面的服务注意下面服务配置每行的缩进,都是在services下的,这里体现不出来。
配置Redis服务,这里我们使用使用
redis作为服务名,redis:latest镜像作为redis数据库服务,project_redis作为运行时容器名称,将容器内的6379端口映射到本机运行环境上(因为默认端口为6379),以及使用REDIS_PASSWORD这个镜像规定的环境变量来设置Redis数据库的密码。1
2
3
4
5
6
7
8
9
10
11
12
13# redis服务名
redis:
# 使用的镜像
image: redis:latest
# 容器名
container_name: project_redis
# 端口映射
ports:
- "6379:6379"
# 环境变量配置
environment:
# Redis服务器密码
REDIS_PASSWORD: redis_passwordports参数的端口映射的形式为:<本机端口>:<容器内端口>environment中,还有更多的参数,这里不常用就没有列举出来,具体看:https://hub.docker.com/_/redis配置MySQL服务,这里我们使用使用
mysql作为服务名,mysql:latest镜像作为mysql数据库服务。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17mysql:
image: mysql:latest
container_name: project_mysql
ports:
- "3306:3306"
# 映射存储数据
volumes:
- ./mysql/data:/var/lib/mysql
environment:
# mysql中ROOT密码
MYSQL_ROOT_PASSWORD: password
# 数据库名
MYSQL_DATABASE: db
# 数据库普通用户名
MYSQL_USER: user
# 数据库普通用户密码
MYSQL_PASSWORD: user_password我们使用
volumes关键字来将容器内的数据映射到本地目录 (<本地目录>:<容器内目录>),防止容器被删除后在容器内的数据取不出来,不会轻易丢失数据。这份数据还能作为本机运行的mysql使用。environment中,必须的有MYSQL_ROOT_PASSWORD、MYSQL_DATABASE、MYSQL_USER和MYSQL_PASSWORD作为初始化数据库的配置,更多查看:https://hub.docker.com/_/mysql配置自己写好的后端服务,这里和上面两个不一样的是,上面是使用的
image关键字构建项目,而这里我们使用build来构建项目,volumes将配置文件放在本地映射到容器内,方便我们及时修改配置文件不用重新构建项目,command作为我们此服务最终执行的命令,environment可以放一些环境变量供我们的程序调用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27api_service:
container_name: project_api
# build构建自己的项目
build:
# 项目路径
context: .
# Dockerfile文件名
dockerfile: Dockerfile
ports:
- "3000:3000"
# 暴露配置文件到本机
volumes:
- ./config.yaml:/config.yaml
# 执行命令等待
command: sh -c "./wait-for-it.sh mysql:3306 -s -t 60 && ./wait-for-it.sh redis:6379 -s -t 60 -- ./main"
environment:
MYSQL_HOST: mysql
MYSQL_PORT: 3306
MYSQL_DATABASE: text2solution
MYSQL_USER: user
MYSQL_PASSWORD: user_password
REDIS_HOST: redis
REDIS_PORT: 6379
# 依赖
depends_on:
- redis
- mysqlbuild关键字下的 context 表示构建上下文路径,即Dockerfile所在的路径,我们的Dockerfile就在当前目录下,和docker-compose.yml文件在同一个目录下,那么就指定为当前目录;dockerfile 表示Dockerfile文件的名称。depends_on表示我们这个服务要依赖那些服务,这里我们填写上面的mysql以及redis服务。填写依赖后,我们就可以通过使用其服务名作为主机名来连接其服务。比如连接mysql的host我们直接填写mysql就行,程序执行时会自动解析成mysql服务的主机IP地址。command表示要执行的指令,这里我们使用上面提到的 wait-for-it.sh 脚本,这个脚本可以等待自定义秒数之内,某个服务可以连通后再执行后面的命令,这里就不过多赘述使用方法,具体看仓库中的介绍。我们这里等待60秒内Mysql服务和Redis服务后再启动我们自己编译好的程序。
至此,搭建后端程序的docker-compose.yml的示例已经完成,下面是完整代码:
1 | # 版本号 |
上面只是一个示例程序的部署编写过程,如果需要更复杂的功能请自行添加。
执行程序
我们这里需要用到docker-compose指令,首先打开我们的项目根目录,将Dockerfile文件和docker-compose.yml文件放在项目根目录中,输入指令:
1 | $ sudo docker-compose up |
可以看到服务就在按照我们编写的docker-compose.yml中的服务按照顺序进行构建,在对于本地没有下载的镜像也会进行下载。
如果mysql以及redis服务已经完成之后,接下来会进入到我们自己程序的构建中。可以看到如图是在按照我们写的Dockerfile一步一步在完成构建。
在全部服务构建完成后,就会开始运行我们的服务。
这里注意,运行的时候不会按照我们编写服务的顺序来执行,可以看到图中框住的部分,我们自己的后端服务是写在mysql服务后的,可是还没等mysql服务完成,我们自己的后端服务就已经开始执行等待命令了。
最后我们的程序已经成功运行起来。
常用的docker-compose命令
上面的运行过程之后,需要保证shell界面不关闭,否则程序将会终止,所以我们需要他在后台运行。
在启动时,加一个参数
-d就能让他在后台运行:1
$ sudo docker-compose up -d
如果想要看到后台运行服务的日志,则
logs命令就能看到其日志:1
$ sudo docker-compose logs
若要关闭服务,则
down命令就能关闭:1
$ sudo docker-compose down
当修改过程序或者Dockerfile,则启动时需要重新构建,加上参数
--build就能重新构建:1
$ sudo docker-compose up --build
这里可以和第一个命令联动,后面再加
-d可以让程序重新构建后在后台运行。
以上就是所有要讲的教程啦,希望能对你有所帮助!如果有错误或者有疑问,欢迎在评论区指出~









