前言

Docker是目前开发者在发布webapp时的主流打包方式,无代码基础的小白也可以很轻松的通过复杂开发者给出cli指令快速的搭建属于自己的微服务框架,但开发者给出部署并不适合所有人的运行环境,后期也难以进行维护,本教程主要是面向需要从零开始搭建自己的个人服务器或Nas玩家,学习本篇内容之后能够快速的搭建起自己的webapp集群并能够进行可视化的维护。

Docker部署方式的选择

通过docker-cli指令进行部署

docker-cli部署容器的指令是docker run ,这条指令后面需要很多的附加参数,来定制容器所需要的各种资源和环境变量。

这里我以embyserver这种需要多种资源的微服务为例来讲解如何进行docker-cli方式的部署。

首先从dockerhub上拉取embyserver的最新镜像,docke run指令会自动从仓库拉取需要的镜像,但一般情况下我建议分开执行方便维护。

docker pull emby/embyserver:latest
docker run -d \
  --name emby-server \
  -p 8096:8096 \
  -p 8920:8920 \
  -e UID=1000 -e GID=1000 \
  -v /path/to/library:/config \
  -v /path/to/media:/media \
  -v /path/to/temp:/temp \
  --device /dev/dri:/dev/dri
  emby/embyserver:latest

让我来解释一下参数的作用:

在bash命令中反斜杠\ 用来表示命令换行 能让命令更加的简洁

-d: 表示以后台方式运行容器。

--name emby-server: 给容器命名为emby-server。

-p 8096:8096: 将容器内的8096端口映射到宿主机的8096端口,这是Emby的默认HTTP访问端口。

-p 8920:8920: 将容器内的8920端口映射到宿主机的8920端口,这是Emby的默认HTTPS访问端口。

-e UID=1000: 设置环境变量UID,这代表在容器中运行Emby进程的用户ID。

-e GID=1000: 设置环境变量GID,这代表在容器中运行Emby进程的用户组ID。

-v /path/to/library:/config: 将宿主机的/path/to/library目录映射到容器内的/config路径,这个目录用来存储Emby的配置文件和数据。

-v /path/to/media:/media: 将宿主机的/path/to/media目录映射到容器内的/media路径,这里是媒体文件存放的位置。

-v /path/to/temp:/temp: 将宿主机的/path/to/media目录映射到容器内的/temp路径,这里是服务缓存文件(略缩图)存放的位置。

--device /dev/dri:/dev/dri :将宿主机的设备驱动目录映射到容器内的设备驱动目录位置,使容器内的emby进程可以调用显卡进行硬解。

显然通过cli命令行的方式进行部署非常的方便和灵活,但如果要部署多个容器和方便后期的维护来说就显得比较的复杂了,在这里我引入第二种部署方式。

通过docker-compose进行docker的部署

docker-compose是用来定义和运行复杂应用的docker工具,一个由容器构成的应用通常需要多个容器来构成,使用docker-compose可以非常方便的通过一个yml文件来管理多个容器,实现多个容器的启动,停止和应用。

对于个人搭建wepapp来说,docker-compose可以非常方便的进行容器内程序和环境变量的更新。

在最新版本的docker-ce中默认安装了docker compose工具集成在了cli中,所以在这里我们应该注意两者的区别:docker-compose 指令是独立于docker-cli之外需要额外安装的工具,docker compose 指令是默认集成在docker-cli指令中的工具,两者除了版本之外没有太大的区别,所以我们可以使用默认集成在docker-cli中的docker compose 指令。

首先创建一个名为docker-compose.yml的文件,写入以下内容,注意文件名字不能更改。

sudo cd /docker/emby/docker-compose.yml
version: '3'
services:
    embyserver:
        container_name: emby-server
        ports:
            - 8096:8096
            - 8920:8920
        environment:
            - UID=1000
            - GID=1000
        volumes:
            - /path/to/library:/config
            - /path/to/media:/media
            - /path/to/temp:/temp
        devices:
            - /dev/dri:/dev/dri
        image: emby/embyserver:latest

version: 指定Docker Compose的文件格式版本。

services: 定义要运行的服务列表。

embyserver: 定义服务名。

image: 指定要使用的Docker镜像。

environment: 环境变量,这里更改了进程运行的账户和组。

volumes: 映射宿主机路径和容器路径,用于持久化数据,让容器内的重要数据得以保存。

ports: 映射端口,使得容器服务可以被宿主机访问。

restart: 设置容器的重启策略。

进入到刚才创建的docker-compose.yml文件夹内执行docker-compose命令.

cd /docker/emby/docker-compose.yml
docker compose up -d

docker compose up -d 表示根据当前工作目录内的docker-compose.yaml文件来定义和启动emby服务器。-d参数表示服务会在后台运行。

要注意的是/path/to/media/path/to/temp以及/path/to/library 需要替换成宿主机内实际的路径,我建议在宿主机创建一个docker文件夹专门用来映射config和database这些容器内需要可持久化存储的路径。

在这里我推荐一个工具叫做composerize,它可以在线帮助我们实现docker-cli指令和docker-compose.yml文件的相互转化,对于一些开发者只给出cli指令而没有给出docker-compose.yml文件的情况下还是非常的有用的.

通过Portainer的方式进行部署和管理

Portainer简介

Portainer是开源可视化的容器管理平台,用户可以直观的通过Portainer对容器进行监控和管理。Portainer可以分为Server和Agent两个部分,Portainer Agent部署到集群中的每个节点上,配置节点并向Portainer Server发送报告,Portainer可以非常方便的对k8s集群和Docker Swarm进行可视化管理。

Portainer和1Panel的对比

我通过水群发现,很多人搞不懂Portainer和1Panel这两个工具的区别(可能两者都带给P字?),部分博主会推荐在系统上安装1Panel进行容器可视化的管理,但1Panel是基于容器的Linux的国产运维面板,提供应用商店这一容器模板来实现非常简单的容器部署管理服务,适用于网站的建站和运维。对于容器的管理来说,局限性太大,我们使用可视化工具的最终目的就是方便我们后期能对容器进行更好的运维,1Panel对于webapp服务器来说还是不太适合的,所以以搭建网站为前提,我建议使用1Panel,但如果是搭建webapp服务器的话,portainer是更好的选择。

Portainer的安装

Portainer分为CE社区版和BE商业版,CE社区版只能搭建三个节点,不过足够我们使用了。面板是纯英文,但使用起来就几个单词,上手很快,也有中文版的镜像,但更新肯定是慢于原版。下面我给出PoratainerCE中文版镜像的安装方式。

先拉取中文版的镜像

docker pull 6053537/portainer-ce:latest

首先在根目录创建一个路径用于可持久化存储portainer数据,docker路径可以用作其它容器的可持久化存储路径,创建完该路径之后应该注意权限问题。

sudo mkdir -p /docker/portainer

/docker/portainer 中新建一个docker-compose.yml文件写入以下代码

version: "2"
services:
  portainer:
      container_name: portainer
      network_mode: bridge
      image: 6053537/portainer-ce
      ports:
        - 9000:9000
      volumes:
        - /docker/portainer:/data
        - /var/run/docker.sock:/var/run/docker.sock
      restart: unless-stopped

/var/run/docker.sock 是 Docker 守护进程 (Docker daemon) 的 Unix 套接字 (Unix socket) 路径。Unix 套接字是一种进程间通信的方式,它允许在同一台主机上的进程之间进行通信。/var/run/docker.sock是Docker守护进程暴露给portainer的 Unix 套接字,可以通过该套接字与 Docker 守护进程进行通信。

进入/docker/portainer 执行docker-compose程序

cd /docker/portainer
docker compose up -d

不出意外的话,容器已经成功运行了!

通过Portainer部署自己的第一个容器

打开浏览器输入自己的服务器ip地址加:9000 打开portainerserver的webui界面,创建好自己的账户名和密码以及本地环境,进入本地环境点击stacks(堆栈)进行容器的部署。Portainer可以通过应用模板(需要商业版)、可视化、堆栈等方式来进行容器的部署,这里我最推荐堆栈的部署方式,堆栈相较于直接进行可视化部署更加灵活,能同时部署多个容器,这也是生产环境中最常用的方案。

堆栈部署实际就是利用portainer里docker-compose工具进行部署,但与直接通过docker-compose工具部署不同的是,可以直接在web editor里对compose.yaml文件进行编辑,需要更新容器的环境变量和其他配置时,可以直接点更新堆栈就能进行更新。一个环境里如果容器多起来的话,管理起来还是非常的方便的。

快速进行webapp的容器化部署

容器运行的必要参数

容器里的应用要正常使用不仅需要一定的硬件资源,还需要能够与外界进行正常的通信。所以我们在部署一个webapp时首先要了解它正常运行需要哪些资源,然后通过映射宿主机的资源来使容器可以正常的运行。这里我以docker-compose为例进行讲解之前没有提到过的必要参数。

  • network_mode 参数用于指定容器使用的网络模式:

bridge:默认模式。每个容器有自己的网络栈,但可以通过指定端口映射来允许容器之间通信。

host:容器使用主机的网络栈,与主机共享网络命名空间。这意味着容器可以访问主机上的所有网络接口,包括 localhost。

none:容器没有网络接口,即不连接到任何网络。通常用于需要完全隔离的场景。

container:<container_name_or_id>:容器共享另一个容器的网络栈。这意味着它们可以相互通信,就像它们在同一主机上一样。

service:<service_name>:容器共享另一个服务的网络栈。与上述类似,但是针对 Docker Compose 服务的情况。

  • ports:用于将容器内部的端口映射到主机上的端口,以便外部可以访问容器内的服务。在前面的部署中,ports:- 9000:9000 表示将容器的端口(后面的9000)映射到宿主机的端口(前面的9000)。

  • expose:与 ports 类似,但是只在 Docker 内部进行端口暴露,不会在主机上进行端口映射。可以用于容器间的内部通信。

  • environment:用于在容器中设置环境变量。环境变量允许将配置信息传递给容器,以便在运行时进行动态配置。在上述的配置中就通过环境变量参数更改了embyserver进程运行的用户id和组id。还可以从外部文件中加载环境变量,如 .env 文件。在这种情况下,可以使用 env_file 参数来指定外部文件,比如:

version: '3.8'
services:
  web:
    image: nginx
    env_file:
      - ./env_vars.env

Compose 将从 env_vars.env 文件中读取环境变量,并将它们传递给容器。

  • volumes :将宿主机的路径映射给容器容器内部,以便于一些数据的可持久化部署,前面的是宿主机的路径后面是容器的路径,我建议单独在/ 目录下创建一个docker文件夹来可持久化存储webapp的配置和数据库文件。如果在宿主机路径使用./ 表示在当前相对路径内进行创建,实际这个相对是相对于compose文件所在的位置的。

但在Portainer里的Web editor里使用这个./相对路径进行容器的部署,很多人就不知道把路径映射到哪去了,网上也没有相关的资料,实际上就自动映射到了操作系统根目录的data文件里。我建议使用stacks(堆栈)进行部署的时候一定要映射宿主机的绝对路径,最好统一映射到同一个路径中以项目名字命名的不同文件夹内。比如emby映射到宿主机/docker/emby路径,portainer映射到宿主机/docker/portainer路径

如何得知我需要修改哪些参数

拿到一个webapp,第一件事就是去阅读github中的readme.md或者开发者文档中的get-started,一般开发者都会给出docker-cli的一键部署指令或者yml文件,如果没有给出yml文件,请用我前面给出的转换工具将docker-cli指令转换成docker-compose.yaml文件,并将docker-compose.yaml的内容复制粘贴到stacks中的web editor之中,仔细阅读相关参数。

确保映射的宿主机端口与之前映射的端口不能重复,特别是8080,3000,5000 这几个开发者习惯用的端口,如果有,改成别的端口(建议端口号大于1000)。

确保要映射的宿主机路径是你需要的路径,比如可以挂载大容量硬盘的宿主机路径映射给emby的/media路径,让视频有足够的空间进行存储。

确保容器的网络模式是否选择正确,具有代理功能或者Bt下载器一类的app可能需要host模式。

确保环境变量参数是否正确,阅读开发者文档,一般都会给需要设置的环境变量参数进行定义,填入适合自己生产环境的参数,大部分变量都是gid或者uid。

确保app需要的相关硬件是否映射,比如显卡或者串口设备和USB 设备,如果有,请用device 参数进行映射。