10 分钟带你从入门到精通的 Docker 小白实战教程

640?wx_fmt=png

笔者:花生 PeA,百度前端汪、ACGer。个人博客:pea3nut.blog;个人资料:pea3nut.info。
本篇文章详细而又简短的介绍了:一名完全不了解 Docker 小白,将全站 Docker 化的过程。内容主要包含:

  • Docker 基本概念

  • 真实站点迁移过程:

    • 静态站点

    • Nodejs 站点

    • WordPress(PHP)

  • 一些必备技巧:开机启动、常用 Shell


文章会讲解使用 Docker 过程中用到的全部技术栈(GitHub CI、Nginx 反向代理、docker-compose),绝不会出现“详见:http://xxx”甩链接的情况。

无需再查阅其他文档,看着一篇就够了!

640?wx_fmt=png

当前有哪些问题

640?wx_fmt=png

手动部署成本太高

笔者维护了诸多网站,其中包含:

  • 我的简历:pea3nut.info,使用 Vuejs 构建的SPA单页应用,纯静态

  • 我的博客:pea3nut.blog,使用著名的 WordPress 搭建(PHP+Apache+MySQL)

  • 一个开源项目——Pxer:pxer.pea3nut.org,官网使用 Nodejs + Express SSR 搭建

而每次我想修改某个网站内容是十分麻烦的。拿大家熟悉的纯静态站点来说,修改过程如下:

  • 下载:从 GitHub 下载代码,然后本地部署

  • 开发:本地修改代码,测试

  • 编译:编译前端项目,产出静态资源

  • 上传:打开FTP软件,上传替换文件

  • 测试:看看网站是否在线上工作正常

  • 提交:将代码提交到 GitHub


哪怕我只是修改个错别字,都要十几分钟。

网站太多,改动太频繁,而每次不管多小的改动都很麻烦。简直让我感觉自己在维护一个万级 QPS 的大型项目。

某个服务挂了,我不懂 Linux 无法排查

最近我发现我的 MySQL 进程总是挂掉,导致所有依赖于 MySQL 的站点都挂了。

我也不知道为什么,之前还是好好的……

640?wx_fmt=png

我尝试了重启进程、重启服务器、捞报错日志百度,均未奏效……

好吧,其实我不太懂 Linux,也不太懂 MySQL,我只是想用下他们搭建 WordPress 站点。而最近总出问题,让我意识到:

我不仅要维护站点,我还要维护环境!

这个对于一名非专业 OP 运维来说太难了,远程 VPS 服务器会随着时间推移越来越复杂,我根本没办法阻止它。现在 MySQL 已经开始无缘无故挂掉,明天会是谁呢?Apache吗?

我不仅要保证站点本地能跑通,还必须要保证远程 VPS 的健康运行……

640?wx_fmt=jpeg

重启不行……那就只能重装系统了……

可是,由于搭建了许多站点,VPS 服务器环境相当复杂(或许这就是 MySQL 挂掉的原因),光 Apache 配置文件都几百行了。重做系统的迁移成本,光是想一想就耗光了我所有的勇气。

新的技术方案——Docker

640?wx_fmt=png

总结一下有如下问题:

  • 手动部署成本太高,改错别字都很麻烦

  • 一台服务器由于时间累积导致环境变得“脏乱差”

  • 重装系统成本太高,难以迁移

而 Docker,正是我解决所有问题SCP-500万能药!

那么 Docker 是如何做的呢?

镜像与容器

Docker 中有两个重要概念。

一个是容器(Container):容器特别像一个虚拟机,容器中运行着一个完整的操作系统。可以在容器中装 Nginx,可以执行 curl www.baidu.com,可以做一切你当前操作系统能做的事情。

另一个是镜像(Image):镜像是一个文件,它是用来创建容器的。如果你有装过 Windows 操作系统,那么 Docker 镜像特别像 “Win7纯净版.rar” 文件。

上边就是你所需要了解的 Docker 全部基础知识,就这么简单。

顺便一提,在 Docker 中,我们通常称你当前使用的真实操作系统为“宿主机(Host)”。

安装 Docker

安装 Docker 在你的电脑上就像安装 QQ 一样简单。

如果你使用的是 Windows 电脑,需要购买支持虚拟化的版本。如 Win10 专业版、Win10 家庭版是不行的。

  • Mac

  • Windows

  • Linux

安装完 Docker 后,你可能会发现自己可以打开一个漂亮的 Docker 窗口。其实这个窗口没什么用处,通常我们都是通过CLI命令行的方式操作 Docker 的,就像 Git 一样。

运行 Docker

接下来我们搭建一个能够托管静态文件的 Nginx 服务器。

容器运行程序,而容器哪来的呢?容器是镜像创建出来的。那镜像又是哪来的呢?

镜像是通过一个 Dockerfile 打包来的,它只是一个纯文本文件

所以创建关系为:

Dockerfile:纯文本文件	
 |	
 V	
Image:类似于“Win7 纯净版.rar”	
 |	
 V	
Container:一个完整操作系统

创建文件

我们创建一个目录 hello-docker,在目录中创建一个 index.html 文件,内容为:

<h1>Hello docker</h1>

然后再在目录中创建一个 Dockerfile 文件,内容为:

FROM nginx	
COPY ./index.html /usr/share/nginx/html/index.html	
EXPOSE 80

此时,你的文件结构应该是:

hello-docker	
  |____index.html	
  |____Dockerfile

打包镜像

文件创建好了,现在我们就可以根据 Dockerfile 创建镜像了!

在命令行中(Windows 优先使用 PowerShell)键入:

cd hello-docker/ # 进入刚刚的目录	
docker image build ./ -t hello-docker:1.0.0 # 打包镜像

注意!Docker 中的选项(Options)放的位置非常有讲究,docker —help image 和 docker image —help 是完全不同的命令。

docker image build ./ -t hello-docker:1.0.0 的意思是:基于路径 ./(当前路径)打包一个镜像,镜像的名字是 hello-docker,版本号是 1.0.0。该命令会自动寻找 Dockerfile 来打包出一个镜像。

Tips:你可以使用 docker images 来查看本机已有的镜像。

不出意外,你应该能得到如下输出:

Sending build context to Docker daemon  3.072kB	
Step 1/3 : FROM nginx	
 ---> 5a3221f0137b	
Step 2/3 : COPY ./index.html /usr/share/nginx/html/index.html	
 ---> 1c433edd5891	
Step 3/3 : EXPOSE 80	
 ---> Running in c2ff9ec2e945	
Removing intermediate container c2ff9ec2e945	
 ---> f6a472c1b0a0	
Successfully built f6a472c1b0a0	
Successfully tagged hello-docker:1.0.0

可以看到其运行了 Dockerfile 中的内容,现在我们简单拆解下:

  • FROM nginx:基于哪个镜像

  • COPY ./index.html /usr/share/nginx/html/index.html:将宿主机中的 ./index.html 文件复制进容器里的 /usr/share/nginx/html/index.html

  • EXPOSE 80:容器对外暴露 80 端口


运行容器

我们刚刚使用 Dockerfile 创建了一个镜像。现在有镜像了,接下来要根据镜像创建容器:

docker container create -p 2333:80 hello-docker:1.0.0	
docker container start xxx # xxx 为上一条命令运行得到的结果

然后在浏览器打开 127.0.0.1:2333,你应该能看到刚刚自己写的 index.html 内容。

在上边第一个命令中,我们使用 docker container create 来创建基于 hello-docker:1.0.0 镜像的一个容器,使用 -p 来指定端口绑定——将容器中的 80 端口绑定在宿主机的 2333 端口。执行完该命令,会返回一个容器 ID。

而第二个命令,则是启动这个容器。

启动后,就能通过访问本机的 2333 端口来达到访问容器内 80 端口的效果了。

Tips:你可以使用 docker containers ls 来查看当前运行的容器。

当容器运行后,可以通过如下命令进入容器内部:

docker container exec -it xxx /bin/bash # xxx 为容器ID

原理实际上是启动了容器内的 /bin/bash,此时你就可以通过 bash shell 与容器内交互了。就像远程连接了SSH 一样。

发生了什么

我们总结下都发生了什么:

  • 写一个 Dockerfile

  • 使用 docker image build 来将 Dockerfile 打包成镜像

  • 使用 docker container create 来根据镜像创建一个容器

  • 使用 docker container start 来启动一个创建好的容器

640?wx_fmt=png

虽然很简单,但是也没有感觉到“广阔天地,大有可为,为所欲为”呢?

迁移静态站点

640?wx_fmt=png

接下来我们实战迁移一个纯前端的 SPA 单页站点:

  • 网址:pea3nut.info

  • 源码:github/pea3nut-info

我打算怎么做

在没迁移 Docker 之前,若我想更新线上网站中内容时,需要:

  1. 本地打包产出静态文件

  2. 手动通过 FTP 上传到服务器

  3. git push 更新 GitHub 源码


稍微有点麻烦,因此我打算这样改:

  1. 执行 git push

  2. 自动检测到 GitHub 有代码更新,自动打包出一个 Docker 镜像

  3. CI 编译完成后,SSH 登录 VPS,删掉现有容器,用新镜像创建一个新容器


而这样做的好处是:

  1. 不必再手动 FTP 上传文件

  2. 当我进行修改错别字这样的简单操作时,可以免测。改完直接 git push,而不必本地编译前端站点

GitHub 中的 CI

首先是让 GitHub 在我每次更新代码时打包出一个镜像。

在 GitHub,可以有免费的 CI 资源用,它就是 Travis CI。

在项目中根目录中添加 .travis.yml 文件,内容如下:

language: node_js # Nodejs 环境	
node_js:	
  - "12"	
services:	
  - docker	
before_install:	
  - npm install # 安装前端编译依赖	
script:	
  - npm run build # 编译前端项目,产出静态文件	
  - echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin	
  - docker build -t pea3nut/pea3nut-info:latest .	
  - docker push pea3nut/pea3nut-info:latest

文件内容非常简单,就是使用编译静态产出后,打包一个镜像并且 push 到远程。有几点需要详细说一下:

为了能够让镜像上传到服务器,你需要在 hub.docker.com 中注册一个账号,然后替换代码中的 pea3nut/pea3nut-info:latest 为用户名/包名 :latest 即可。

使用 GitHub 登录 Travis CI 后,在左边点击 + 加号添加自己的 GitHub 仓库后,需要移步到 Setting 为项目添加 DOCKERUSERNAME 和 DOCKERPASSWORD 环境变量。这样保证我们可以秘密的登录 Docker Hub 而不被其他人看到自己的密码。如下图:

640?wx_fmt=png

然后需要添加 Dockerfile 文件来描述如何打包 Docker 镜像。

按照 .travis.yml 的命令次序,在打包镜像时,前端已经编译成了静态文件,项目产出已经有了。不必在 Docker 容器中安装前端编译环境之类的,直接复制文件即可:

FROM nginx	
COPY ./dist/ /usr/share/nginx/html/	
EXPOSE 80

Note:过程虽然简单但是线条很长,建议本地多测试测试再进行 git push。

若你编译出的静态站点也是一个 SPA 单页应用(所有的请求都由一个 .html 文件处理),需要增加额外的 Nginx 配置来保证请求都能打到 index.html。下边是我写的 vhost.nginx.conf Nginx 配置文件,将不访问文件的请求全部重定向到 /index.html:

server {	
    listen 80;	
    server_name localhost;	
    location / {	
        root /usr/share/nginx/html;	
        index index.html index.htm;	
        proxy_set_header Host $host;	
        if (!-f $request_filename) {	
          rewrite ^.*$ /index.html break;	
        }	
    }	
    error_page 500 502 503 504 /50x.html;	
    location = /50x.html {	
        root /usr/share/nginx/html;	
    }	
}

然后在 Dockerfile 中新加一行,将本机的 vhost.nginx.conf 文件复制到容器的 /etc/nginx/conf.d/pea3nut-info.conf,让 Nginx 能够读取该配置文件:

  FROM nginx	
  COPY ./dist/ /usr/share/nginx/html/	
+ COPY ./vhost.nginx.conf /etc/nginx/conf.d/pea3nut-info.conf	
  EXPOSE 80

然后执行 git push 后,你可以在 Travis CI 看到 CI 的编译结果。如果编译没问题,远程实际上就有了 pea3nut/pea3nut-info:latest 这个镜像。本地可以试试看该镜像工作是否正常:

docker image pull pea3nut/pea3nut-info:latest	
docker container create -p 8082:80 pea3nut/pea3nut-info:latest	
docker container start xxx # xxx 为上一条命令执行的返回值

运行完成后,浏览器访问 127.0.0.1:8082 应该就能看到效果了!

然后你可以登录远程 VPS 服务器,安装 Docker,执行同样的命令。然后访问远程 VPS 服务器的公网 IP + 8082 端口号,应该能看到和本地相同的效果。

Tips:忘了如何在 VPS 上安装 Docker?在上文“安装 Docker”一节,你可能需要的是 Linux 的安装方式:

curl https://get.docker.com/ > install-docker.sh # 下载安装脚本	
sh install-docker.sh # 执行安装脚本

Nginx 反向代理

Note:接下来的操作都是在你的远程 VPS 服务器上操作,并非本地电脑,或者容器中。

目前我们将容器挂到了 8082 端口,但是线上不可能让用户手动输入 8082 端口进行访问。而如果将容器直接挂到 80 端口,虽然这样用户可以直接不加端口直接访问,但是如果有第二个容器,或者更多容器呢?

这时候就需要在宿主机跑一个 Nginx,由它来独占 80 端口,然后根据域名来讲请求分发给响应的容器。如下图:

640?wx_fmt=png

这种方案叫做“反向代理”。

登录 VPS 服务器,安装 Nginx。因为我是 Ubuntu,所以可以用 apt 安装。其他 Linux 发行版可以百度下安装方法,通常 2 行内可以搞定:

apt update # 更新软件包	
apt-get install nginx # 安装 Nginx	
systemctl status nginx # 查看 Nginx 状态

此时本地通过浏览器访问 VPS 的公网 IP 可用看到 Nginx 的欢迎页面。

640?wx_fmt=png

然后在 VPS 服务器的 /etc/nginx/conf.d/ 中建立一个 vhost.conf 文件,配置如下内容:

server {	
    listen 80;	
    server_name pea3nut.info;	
    location / {	
        proxy_pass http://127.0.0.1:8082;	
    }	
}

配置的意思是,监听来自 80 端口的流量,若访问域名是 pea3nut.info(替换为你自己的域名),则全部转发到http://127.0.0.1:8082中。

配置完成后,重启 Nginx 服务器。若是 Ubuntu 可以使用 systemctl restart nginx 命令,不同 Linux 发行版稍有不同。

配置成功后,访问 pea3nut.info 会看到和 VPS 公网 IP:8082 相同的效果。

更新站点

而迁移完成 Docker 后,我想改一个错别字的流程变为:

  • 本地修改完成,执行 git push

  • 等待 CI 编译完成

  • 登录 VPS 服务器,执行:

docker image pull pea3nut/pea3nut-info:latest	
docker container create -p 8082:80 pea3nut/pea3nut-info:latest # 得到 yyy	
docker container stop xxx # xxx 为当前运行的容器ID,可用 docker container ls 查看	
docker container start yyy # yyy 第二条命令返回值

命令还是有些长?我们在下面会进一步优化它。

迁移 Nodejs 站点

640?wx_fmt=png

接下来我们实战迁移一个由 Nodejs 写的站点。

  • 网址:http://pxer.pea3nut.org/

  • 源码:https://github.com/pea3nut/pxer-homepage

我打算怎么做

网站没有前后端分离,使用服务器渲染页面。在没迁移 Docker 之前,若我想更新线上网站中内容时,需要:

  1. 本地修改好前端文件

  2. 手动通过 FTP 上传到服务器

  3. 在服务器端重启 Nodejs 进程。若有环境依赖更新,需要在 VPS 服务器上同步更新依赖

  4. git push更新 GitHub 源码

稍微有点麻烦,因此我打算这样改:

  1. 执行 git push

  2. 自动检测到 GitHub 有代码更新,自动打包出一个 Docker 镜像

  3. CI 编译完成后,SSH 登录 VPS,删掉现有容器,用新镜像创建一个新容器


而这样做的好处是:

  1. 不必再手动 FTP 上传文件

  2. 不必手动维护服务器的 Nodejs 运行环境


实施

具体的过程和处理静态站点没有什么特别的区别,无非是:

  1. 编写 Dockerfile 文件

  2. 在 CI 时自动打包镜像

  3. 在 VPS 增加一个 Nginx 反向代理


这次就不重复讲了,具体的配置可以参考项目中的相关文件。

Tips:你可能发现了 Dockerfile 中的 ENTRYPOINT 命令必须指定一个前台进程。若你的 Nodejs 应用是使用 PM2 进行保活的,你需要替换 pm2 start app.js为pm2-docker app.js。

docker-compose

当将 Nodejs 站点迁移完成,我们的 VPS 服务器上已经运行了2 个容器。每次镜像更新都要手动的 docker container create 带一堆参数是比较麻烦的,尤其是当日后容器日益增多的时候。而这时,就轮到 docker-compose 登场了~

docker-compose 是 Docker 官方提供的一个 Docker 管理工具。若你是通过桌面端的 Docker 安装包安装的 Docker,它是会默认为你安装 docker-compose 的。可以试试如下命令:

docker-compose --help

如果是在 Linux,可以通过如下命令安装 docker-compose:

curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose	
chmode +x /usr/local/bin/docker-compose

docker-compose 和 Docker 差不多,也是只要一份文件就能跑起来。docker-compose 主要的作用就是能够让你不必手敲那么多 Docker 命令。

建立一个目录,然后在目录中建立 docker-compose.yml,内容如下:

version: "3.7" # 这个是配置文件的版本,不同的版本号声明方式会有细微的不同	
services:	
    info:	
        container_name: pea3nut-info	
        image: pea3nut/pea3nut-info:latest	
        ports:	
            - "8082:80"	
        restart: on-failure

然后在目录中键入如下命令就能将服务跑起来:

docker-compose up info

docker-compose 会帮我们自动去拉镜像,创建容器,将容器中的 80 端口映射为宿主机的8082端口。restart 字段还要求 docker-compose 当发现容器意外挂掉时重新启动容器,类似于 pm2,所以你不必再在容器内使用 pm2。

如果想要更新一个镜像创建新容器,只需要:

docker-compose pull info	
docker-compose stop info	
docker-compose rm info	
docker-compose up -d info # -d 代表后台运行

笔者已将自己网站部署方式开源,可参考:https://github.com/pea3nut/pea3nut-hub。

迁移 WordPress 站点(Apache + PHP + MySQL)

640?wx_fmt=png

接下来我们实战迁移一个 WordPress 站点。

  • 网址:http://pea3nut.blog/

  • 源码:非公开

可能你也发现了这个站点和其他站点的一个非常大的不同——他的源码和数据是不能公开的。

之前我们打包镜像时,都是直接将代码打进镜像内的。这条方案用在这里显然是不行的,有两个问题:

  1. 我不想公开 MySQL 数据文件和网站内容(如图片)。若将这些打包进镜像,任何人都能 docker image pull 下载到镜像,然后取得镜像内的文件

  2. 当容器被删掉,存储的 MySQL 数据都将丢失

Volume

Docker 提供了一个叫做 Volume 的东西,可以将容器内和宿主机的某个文件夹进行”绑定“,任何文件改动都会得到同步。所以,我可以将整个站点目录和 MySQL 目录都挂载为 Volume。这样,当容器删除时,所有数据文件和源码都会保留。

在本地建立 ./blog/mysql-data 目录存储 MySQL 数据,建立 ./blog/wordpress 目录存储 WordPress 源码。然后修改 docker-compose.yml 如下:

version: "3.7"	
services:	
    info:	
        container_name: pea3nut-info	
        image: pea3nut/pea3nut-info:latest	
        ports:	
            - "8082:80"	
        restart: on-failure	
+   blog:	
+       container_name: pea3nut-blog	
+       image: tutum/lamp:latest	
+       ports:	
+           - "8081:80"	
+       volumes:	
+           - ./blog/mysql-data:/var/lib/mysql	
+           - ./blog/wordpress:/app	
+       restart: on-failure

可以看到这次根本没有打包镜像,而是直接使用 tutum/lamp 镜像提供的 LAMP 环境(Linux + Apache + MySQL + PHP),然后将 MySQL 数据目录 /var/lib/mysql 和源码目录 /app 都挂载出来就可以了。

Tips:通过 Volume 我们只是解决了部署问题,而如何本地开发然后将源码同步到服务器呢?用 FTP 当然是可以的,但是稍微有点麻烦。其实你可以自建一个 Git 服务器!详见:http://pea3nut.blog/e127。

坑和其他技巧

640?wx_fmt=png

  • 设置开机启动:https://www.cnblogs.com/digdeep/p/9760025.html

  • 迁移后中文文件乱码:https://blog.csdn.net/shiyong1949/article/details/79462077

源码

640?wx_fmt=png

静态站点迁移(笔者简历):

  • 线上地址:http://pea3nut.info/

  • 源码:https://github.com/pea3nut/pea3nut-info

  • CI 配置文件:https://github.com/pea3nut/pea3nut-info/blob/master/.travis.yml

  • Dockerfile:https://github.com/pea3nut/pea3nut-info/blob/master/Dockerfile

  • docker-compose:https://github.com/pea3nut/pea3nut-hub/blob/558c39b0ddef379c218d499f3ad576208db9e35d/docker-compose.yml#L17

  • Nginx 反向代理配置:https://github.com/pea3nut/pea3nut-hub/blob/558c39b0ddef379c218d499f3ad576208db9e35d/vhost.conf#L1


PHP 站点迁移(笔者博客):

  • 线上地址:http://pea3nut.blog

  • Dockerfile:https://github.com/pea3nut/pea3nut-hub/blob/558c39b0ddef379c218d499f3ad576208db9e35d/blog/Dockerfile

  • docker-compose:https://github.com/pea3nut/pea3nut-hub/blob/558c39b0ddef379c218d499f3ad576208db9e35d/docker-compose.yml#L3

  • Nginx 反向代理配置:https://github.com/pea3nut/pea3nut-hub/blob/558c39b0ddef379c218d499f3ad576208db9e35d/vhost.conf#L10


Nodejs 迁移(Pxer 官网):

  • 线上地址:pxer.pea3nut.org

  • 源码:https://github.com/pea3nut/pxer-homepage/

  • CI 配置文件:https://github.com/pea3nut/pxer-homepage/blob/master/.travis.yml

  • Dockerfile:https://github.com/pea3nut/pxer-homepage/blob/master/Dockerfile

  • docker-compose:https://github.com/pea3nut/pea3nut-hub/blob/558c39b0ddef379c218d499f3ad576208db9e35d/docker-compose.yml#L46

  • Nginx 反向代理配置:https://github.com/pea3nut/pea3nut-hub/blob/558c39b0ddef379c218d499f3ad576208db9e35d/vhost.conf#L70


其他:

  • 汇总部署配置的仓库:https://github.com/pea3nut/pea3nut-hub/


后记

640?wx_fmt=png

你好,这里是花生 PeA。感谢你能看完这篇文章,非常感谢!

在文章撰写两个月前,我决定将站点全部迁移到 Docker。两个星期前,我决定将过程整理成一篇博客。没想到写了这么久,写了上万字

但是 Docker 真的很好用。全站 Docker 化后,当我再次迁移服务器时,我发现我竟可以在十行命令内完成整个环境的迁移,耗时十分钟!这种“爽快”的感觉也是我撰写文章的动力——我想将这份爽快分享给屏幕前的你。希望你也能喜欢上 Docker ~ ❤️

来源:知乎

原文:https://url.cn/5vdtCvZ

题图:来自谷歌图片搜索

版权:本文版权归原作者所有

投稿:欢迎投稿,邮箱: editor@hi-linux.com

640?wx_fmt=gif

640?wx_fmt=png

你可能还喜欢

点击下方图片即可阅读

关于高性能负载均衡架构,这些知识点大多数人不知道!

640?wx_fmt=png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值