✨ 文章独立博客地址:https://blog.xez.cc/posts/f255ffad.html
✨ 本文在我的独立博客网页阅读更佳嗷~
首先我们要知道两种文件:Dockerfile和docker-compose.yml,在一些开源项目中很常见。
Docker安装
- 详见官方安装文档:Install Docker Engine
- 或者菜鸟教程:Docker 安装
(我每次安装都去菜鸟教程复制命令)
Dockerfile
Dockerfile 是用于构建 Docker 镜像的一种脚本语言,它包含一系列的指令和参数,用于指定如何构建镜像。在 Dockerfile 中,我们可以指定基础镜像、容器中运行的命令、需要复制到容器中的文件、环境变量等等。通过编写 Dockerfile,我们可以将应用程序打包到一个独立的、可移植的镜像中,这使得应用程序的部署和运行变得非常简单和可靠。
上面的文本通俗来说,Dockerfile 是一个用于构建 Docker 镜像的脚本语言,它包含一系列的指令和参数,用于指定如何构建镜像并完成应用程序的打包和部署。
下面是一个示例,将一个简单的Go语言程序用Docker运行起来:
# 拉取运行的镜像
FROM golang:latest
# 设置工作目录
WORKDIR /app
# 拷贝文件
COPY . .
# 构建项目,编译成可执行的二进制文件
RUN go build -o main .
# 暴露端口
EXPOSE 8080
# 运行构建项目的二进制文件
CMD ["./main"]
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 容器组合在一起,并通过一个配置文件来定义它们之间的关系和运行方式。
下面是一个示例:
version: '3'
services:
web:
build: .
ports:
- "8080:8080"
depends_on:
- db
db:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test
这个示例使用了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
。FROM golang:lastest AS builder
在这个基础镜像环境下,相当于我们的程序已经在有Go语言环境下的虚拟机下运行了。
-
我们要配置好该有的一些设置。在Go语言写的后端程序中,有一些包下载的时候因为众所周知网络原因 (我想学习Go语言的同学都有感受),一些包没法直接拉取下来,需要借助设置
GOPROXY
,借助代理来下载包。同时保证我们编译的程序是Linux
系统、X86
架构环境运行 (使用arm架构的同学自行修改),所以我们需要设置程序的环境变量。ENV GO111MODULE=on \ GOPROXY=https://goproxy.cn,direct \ CGO_ENABLED=0 \ GOOS=linux \ GOARCH=amd64
-
我们设置一个工作目录
/build
(叫啥都行,举个例子而已),让程序在这个目录中编译和执行。WORKDIR /build
为什么要设置工作目录: WORKDIR 指令用于设置 Docker 容器中的工作目录。它会在容器中创建一个指定的目录,并将其作为后续指令(如 RUN, CMD, ENTRYPOINT)的默认工作目录。也就是说,当我们执行其他指令时,会在这个工作目录下进行。
-
将我们的代码复制到容器中。
COPY . .
为什么COPY指令两边都是
.
: 使用 COPY 指令时,第一个参数表示要复制的源文件或目录的路径,第二个参数表示目标路径。当第二个参数是一个相对路径时,它将被视为相对于 WORKDIR 指令设置的工作目录。
设置工作目录后,COPY . .
中第一个参数.
表示当前目录,也就是 Dockerfile 所在的目录,第二个参数.
表示相对于工作目录 /build 的当前目录,也就是将当前目录复制到容器的 /build 目录中。 -
我们就要开始下载程序中用到的第三方包了,这里需要执行命令
go mod download
。RUN go mod download
-
我们将程序编译成二进制可执行文件,执行命令
go build -o main
。RUN go build -o main
-
为了节省运行时的性能开销,能不使用到资源的尽量不要。因为我们已经编译好了可运行的二进制文件,所以运行起来并不需要Go语言环境,这里可以选择在更小的基础镜像中运行,这里我们选择
debian:bullseye-slim
,只包含了最基本的系统组件和软件包,用不到的东西基本没有。FROM debian:bullseye-slim
❗ 在运行起来的时候可能会发现,当程序有对https协议的API接口等请求时会出现 “docker tls: failed to verify certificate: x509: certificate signed by unknown authority” 这表明我们运行的环境里面没有配置证书,这时我们可以通过apt包管理来安装相关包。
RUN apt update && apt install apt-utils && apt install -y ca-certificates
❗ 在运行起来的时候还可能会发现,时区不太对,所以还要安装响应的包和指令来更改时区。
RUN apt-get install -y tzdata &&\ ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&\ dpkg-reconfigure -f noninteractive tzdata
❗ 如果有其他的需求也可自己装一些包。
❗ 如果是国内服务器换源会更快,可以在上面指令之前加上换源指令。
RUN 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
)复制到这个新的镜像环境中。COPY --from=builder /build/main / COPY ./config.yaml /
下面为啥不用 --from=builder:因为第一个是在上面镜像构建的环境中的build工作目录中编译好的文件,不加是直接从当前目录(没在容器中)复制文件。当然也可以加,从上面镜像构建的环境中获取,两个文件一样,所以没必要加。
-
将我们的脚本也复制过来,并且给他775权限,可运行。
COPY ./wait-for-it.sh / RUN chmod 775 wait-for-it.sh
这里编写Dockerfile文件就到尾声了,下面是上面步骤的完整 Dockerfile
文件内容。
因为每个指令都会产生一个新的镜像层,不精简指令的话会产生很多镜像,所以下面代码为精简后的代码。
# 构建的基础镜像
FROM golang:latest AS builder
# 为我们的镜像设置必要的环境变量
ENV GO111MODULE=on \
GOPROXY=https://goproxy.cn,direct \
CGO_ENABLED=0 \
GOOS=linux \
GOARCH=amd64
# 设置工作目录
WORKDIR /build
# 将代码复制到容器中
COPY . .
# 下载依赖项和编译成二进制可执行文件
RUN go mod download &&\
go build -o main
# 使用一个更小的镜像运行
FROM debian:bullseye-slim
# 从builder环境COPY编译的二进制文件
COPY --from=builder /build/main /
# COPY配置文件
COPY ./config.yaml /
# COPY等待脚本
COPY ./wait-for-it.sh /
# 换源 & 安装证书 & 调整时区 & 增加执行权限
RUN echo "deb http://mirrors.aliyun.com/debian bullseye main" > /etc/apt/sources.list &&\
echo "deb http://mirrors.aliyun.com/debian-security bullseye-security main" >> /etc/apt/sources.list &&\
echo "deb http://mirrors.aliyun.com/debian bullseye-updates main" >> /etc/apt/sources.list &&\
apt update && \
apt install apt-utils &&\
apt install -y ca-certificates &&\
apt-get install -y tzdata &&\
ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&\
dpkg-reconfigure -f noninteractive tzdata &&\
chmod +x wait-for-it.sh &&\
chmod +x ./main
这里我们编写的Dockerfile文件是给到下面docker-compose.yml使用的,所以不需要将编译好的程序在这里使用执行指令执行。
如果你的后端程序程序用不着数据库
如果那你的后端程序程序用不着数据库,那么就不存在容器之间的关联了,第1步和第10步就无需使用,因为你不需要连接数据库(滑稽)。
当然,程序运行的话则需要在Dockerfile这里写了:
CMD ["./main"]
完整版Dockerfile代码:
因为每个指令都会产生一个新的镜像层,不精简指令的话会产生很多镜像,所以下面代码为精简后的代码。
# 构建的基础镜像
FROM golang:latest
# 为我们的镜像设置必要的环境变量
ENV GO111MODULE=on \
GOPROXY=https://goproxy.cn,direct \
CGO_ENABLED=0 \
GOOS=linux \
GOARCH=amd64
# 设置工作目录
WORKDIR /build
# 将代码复制到容器中
COPY . .
# 下载依赖项和编译成二进制可执行文件
RUN go mod download &&\
go build -o main
# 使用一个更小的镜像运行
FROM debian:bullseye-slim
# 从builder环境COPY编译的二进制文件
COPY --from=builder /build/main /
# COPY配置文件
COPY ./config.yaml /
# 换源 & 安装证书 & 调整时区 & 增加执行权限
RUN echo "deb http://mirrors.aliyun.com/debian bullseye main" > /etc/apt/sources.list &&\
echo "deb http://mirrors.aliyun.com/debian-security bullseye-security main" >> /etc/apt/sources.list &&\
echo "deb http://mirrors.aliyun.com/debian bullseye-updates main" >> /etc/apt/sources.list &&\
apt update && \
apt install apt-utils &&\
apt install -y ca-certificates &&\
apt-get install -y tzdata &&\
ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime &&\
dpkg-reconfigure -f noninteractive tzdata &&\
chmod +x ./mian
# 运行
CMD ["./main"]
执行以下命令:
docker run -p <host-port>:<container-port> <image-name>
CMD和RUN的区别: CMD 指令用于在容器启动时运行一个命令。当我们使用 docker run 命令启动容器时,Docker 会在容器内部执行 CMD 指令中指定的命令。如果在 Dockerfile 中定义了多个 CMD 指令,只有最后一个 CMD 指令会生效。RUN 指令用于在 Docker 镜像构建过程中执行命令。
编写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服务。
# 版本号 version: '3' # 各种服务 services: # ...写下面的服务
注意下面服务配置每行的缩进,都是在services下的,这里体现不出来。
-
配置Redis服务,这里我们使用使用
redis
作为服务名,redis:latest
镜像作为redis数据库服务,project_redis
作为运行时容器名称,将容器内的6379端口映射到本机运行环境上(因为默认端口为6379),以及使用REDIS_PASSWORD
这个镜像规定的环境变量来设置Redis数据库的密码。# redis服务名 redis: # 使用的镜像 image: redis:latest # 容器名 container_name: project_redis # 端口映射 ports: - "6379:6379" # 环境变量配置 environment: # Redis服务器密码 REDIS_PASSWORD: redis_password
ports
参数的端口映射的形式为:<本机端口>:<容器内端口>environment
中,还有更多的参数,这里不常用就没有列举出来,具体看:https://hub.docker.com/_/redis -
配置MySQL服务,这里我们使用使用
mysql
作为服务名,mysql:latest
镜像作为mysql数据库服务。mysql: 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
可以放一些环境变量供我们的程序调用。api_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 - mysql
build
关键字下的 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的示例已经完成,下面是完整代码:
# 版本号
version: '3'
# 各种服务
services:
# redis服务
redis:
# 使用的镜像
image: redis:latest
# 容器名
container_name: project_redis
# 端口映射
ports:
- "6379:6379"
# 环境变量配置
environment:
# Redis服务器密码
REDIS_PASSWORD: redis_password# ...写下面的服务
# mysql服务
mysql:
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
# 自己的后端程序服务
api_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: db
MYSQL_USER: user
MYSQL_PASSWORD: user_password
REDIS_HOST: redis
REDIS_PORT: 6379
# 依赖
depends_on:
- redis
- mysql
上面只是一个示例程序的部署编写过程,如果需要更复杂的功能请自行添加。
执行程序
我们这里需要用到docker-compose
指令,首先打开我们的项目根目录,将Dockerfile文件和docker-compose.yml文件放在项目根目录中,输入指令:
$ sudo docker-compose up
可以看到服务就在按照我们编写的docker-compose.yml中的服务按照顺序进行构建,在对于本地没有下载的镜像也会进行下载。
如果mysql以及redis服务已经完成之后,接下来会进入到我们自己程序的构建中。可以看到如图是在按照我们写的Dockerfile一步一步在完成构建。
在全部服务构建完成后,就会开始运行我们的服务。
这里注意,运行的时候不会按照我们编写服务的顺序来执行,可以看到图中框住的部分,我们自己的后端服务是写在mysql服务后的,可是还没等mysql服务完成,我们自己的后端服务就已经开始执行等待命令了。
最后我们的程序已经成功运行起来。
常用的docker-compose命令
-
上面的运行过程之后,需要保证shell界面不关闭,否则程序将会终止,所以我们需要他在后台运行。
在启动时,加一个参数
-d
就能让他在后台运行:$ sudo docker-compose up -d
-
如果想要看到后台运行服务的日志,则
logs
命令就能看到其日志:$ sudo docker-compose logs
-
若要关闭服务,则
down
命令就能关闭:$ sudo docker-compose down
-
当修改过程序或者Dockerfile,则启动时需要重新构建,加上参数
--build
就能重新构建:$ sudo docker-compose up --build
这里可以和第一个命令联动,后面再加
-d
可以让程序重新构建后在后台运行。
以上就是所有要讲的教程啦, 希望能对你有所帮助! 如果有错误或者有疑问,欢迎在评论区指出~