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

✨ 文章独立博客地址:https://blog.xez.cc/posts/f255ffad.html
✨ 本文在我的独立博客网页阅读更佳嗷~

首先我们要知道两种文件:Dockerfile和docker-compose.yml,在一些开源项目中很常见。

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的教程:

  1. 我们先要准备一个脚本,因为在数据库和我们自己的程序并行部署构建时,会出现数据库还没启动,我们自己的程序就开始连接,导致连接失败。这里我们需要一个脚本:wait-for-it.sh

    🔮 脚本仓库:https://github.com/vishnubob/wait-for-it,直接下载仓库中的 wait-for-it.sh 即可,这里演示我们将它放在我们程序的根目录。

  2. 我们先要拉取一个基础运行的镜像,这里就用官方镜像库中的 golang:lastest 来作为基础镜像(也可以指定版本),方便后续操作,我们将他别名为 builder

    FROM golang:lastest AS builder
    

    在这个基础镜像环境下,相当于我们的程序已经在有Go语言环境下的虚拟机下运行了。

  3. 我们要配置好该有的一些设置。在Go语言写的后端程序中,有一些包下载的时候因为众所周知网络原因 (我想学习Go语言的同学都有感受),一些包没法直接拉取下来,需要借助设置 GOPROXY ,借助代理来下载包。同时保证我们编译的程序是 Linux 系统、 X86 架构环境运行 (使用arm架构的同学自行修改),所以我们需要设置程序的环境变量。

    ENV GO111MODULE=on \
        GOPROXY=https://goproxy.cn,direct \
        CGO_ENABLED=0 \
        GOOS=linux \
        GOARCH=amd64
    
  4. 我们设置一个工作目录 /build (叫啥都行,举个例子而已),让程序在这个目录中编译和执行。

    WORKDIR /build
    

    为什么要设置工作目录: WORKDIR 指令用于设置 Docker 容器中的工作目录。它会在容器中创建一个指定的目录,并将其作为后续指令(如 RUN, CMD, ENTRYPOINT)的默认工作目录。也就是说,当我们执行其他指令时,会在这个工作目录下进行。

  5. 将我们的代码复制到容器中。

    COPY . .
    

    为什么COPY指令两边都是. 使用 COPY 指令时,第一个参数表示要复制的源文件或目录的路径,第二个参数表示目标路径。当第二个参数是一个相对路径时,它将被视为相对于 WORKDIR 指令设置的工作目录。
    设置工作目录后,COPY . . 中第一个参数 . 表示当前目录,也就是 Dockerfile 所在的目录,第二个参数 . 表示相对于工作目录 /build 的当前目录,也就是将当前目录复制到容器的 /build 目录中。

  6. 我们就要开始下载程序中用到的第三方包了,这里需要执行命令 go mod download

    RUN go mod download
    
  7. 我们将程序编译成二进制可执行文件,执行命令 go build -o main

    RUN go build -o main
    
  8. 为了节省运行时的性能开销,能不使用到资源的尽量不要。因为我们已经编译好了可运行的二进制文件,所以运行起来并不需要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
    
  9. 然后从原来的编译虚拟机中将编译好的可执行文件以及配置文件(示例为 config.yaml)复制到这个新的镜像环境中。

    COPY --from=builder /build/main /
    COPY ./config.yaml /
    

    下面为啥不用 --from=builder:因为第一个是在上面镜像构建的环境中的build工作目录中编译好的文件,不加是直接从当前目录(没在容器中)复制文件。当然也可以加,从上面镜像构建的环境中获取,两个文件一样,所以没必要加。

  10. 将我们的脚本也复制过来,并且给他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服务配置来支撑。

  1. 打上版本号和services服务。

    # 版本号
    version: '3'
    
    # 各种服务
    services:
    # ...写下面的服务
    

    注意下面服务配置每行的缩进,都是在services下的,这里体现不出来。

  2. 配置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

  3. 配置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_PASSWORDMYSQL_DATABASEMYSQL_USERMYSQL_PASSWORD作为初始化数据库的配置,更多查看:https://hub.docker.com/_/mysql

  4. 配置自己写好的后端服务,这里和上面两个不一样的是,上面是使用的 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命令

  1. 上面的运行过程之后,需要保证shell界面不关闭,否则程序将会终止,所以我们需要他在后台运行。

    在启动时,加一个参数 -d 就能让他在后台运行:

    $ sudo docker-compose up -d
    
  2. 如果想要看到后台运行服务的日志,则 logs 命令就能看到其日志:

    $ sudo docker-compose logs
    
  3. 若要关闭服务,则 down 命令就能关闭:

    $ sudo docker-compose down
    
  4. 当修改过程序或者Dockerfile,则启动时需要重新构建,加上参数 --build 就能重新构建:

    $ sudo docker-compose up --build
    

    这里可以和第一个命令联动,后面再加 -d 可以让程序重新构建后在后台运行。

以上就是所有要讲的教程啦, 希望能对你有所帮助! 如果有错误或者有疑问,欢迎在评论区指出~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值