不用过多的介绍 Docker,最简单的理解方式就是单实例运行方案,其官网地址是 http://docker.com 。
用 Node.JS 来解释下,做 Node.JS 项目的会用到很多第三方的包,通常做法是通过执行命令 npm install package_name
直接在服务器上安装,但这样做是有一定的风险,主要是因为第三方包的不可控。Node.JS 就发生过比较著名的 left-pad
事件,所以通过 Docker 这类工具来部署尤其重要。
这是一篇系列的笔记,我会持续更新里面的内容。需要注意的是,我在是 macOS Catalina 上进行的操作,其它系统大同小异。
Docker 的安装无需说明,官网有相关的文档,请看这里 选择对应的系统版本安装即可,我使用的是 macOS,所以下载对应的客户端即可,我是通过 brew cask install docker
安装的。而 linux 下的安装方式也比较简单
curl -sSL https://get.docker.com/ | sh
service docker start
安装完成后,运行 docker,可以通过 docker version
查看当前安装的 docker 版本情况。我写了一个一键安装 Docker 和 Docker Compose 的脚本,可以从这里下载。
项目任务
完成 Hello World
访问 http://localhost:8080
时显示 Hello World
。
步骤
创建项目并初始化
mkdir dockerdemo && cd dockerdemo
npm init
先写好项目要求中的代码,并测试通过
创建文件,并写入代码,完整的代码内容如下
const Koa = require("koa");
const port = "3000";
const app = new Koa();
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(port);
因为用到了 koa,所以需要先安装,在源码根目录下安装 npm install koa
,完成后执行 node app.js
,这时候访问 http://localhost:3000
,就会出现 Hello World
,如下图
至此,项目代码完成,并通过了测试。
.dockerignore
文件
如同 .gitignore
文件一样作用一样,.dockerignore
文件会忽略掉里面的文件,比如 Node.JS 中的 node_module
等,内容如下
node_modules
package-lock.json
Dockerfile
文件
先看其内容
FROM node:latest
RUN mkdir -p /home/tourcoder/dockerplex
COPY . /home/tourcoder/dockerplex
WORKDIR /home/tourcoder/dockerplex
RUN npm install
EXPOSE 3000
CMD node app.js
第一行:使用 Node.JS 的最新版本(latest),这里的 latest 可以是其他版本,比如 node:12.0
,具体根据自己的需求来,版本信息可以看这里。
第二行:在容器中创建一个目录,其中 -p 参数是创建多层级文件夹,如果不存在该文件夹的话会自动创建,如果存在也不会报错。
第三行:将当前目录下的内容拷贝到之前在容器创建的目录下
第四行:定位容器的工作目录
第五行:在容器的工作目录下执行后面的命令 npm install
,后面也可以加上参数,比如在国内 Node.JS 的官方源慢,就可以使用国内的源,即 RUN npm install --registry http://npmreg.mirrors.ustc.edu.cn
。
第六行:expose 字面意思就是暴露,即将容器的某一端口暴露出来,允许外部使用,这里暴露的是 3000 端口。
第七行:执行命令,如果有多个命令,之间用 &&
来拼接。
创建 image 文件
执行如下命令
docker build -t dockernodejsdemo .
-t
参数的作用是指定后面的名字,也可以在名字后面增加 :版本号
,如果不增加,则是 latest
。
命令执行完成后,可以通过 docker images
命令看到已经生成了 image。
这样 image 文件创建完成。但这种情况下,创建的的 image 文件会很大,比如我这个文件就有 900 多 M,那么可以给它瘦身。查看 Dockerfile 文件,可以看出 node 来自 node:latest
,它本身就很大,把它改成精简版,做成一个基础镜像,具体看这里,它包含了大多数精简版的包。那么本文的 node 包就可以用这种方式精简。
在项目外新建一个 Dockerfile
,然后输入内容
FROM alpine:latest
RUN apk add --no-cache --update nodejs npm
打包
docker build -t basicnode:latest .
打包完成后会发现,一下子从 900 多 M 精简到了 50 多 M。
项目里调用的话,就是将之前项目的 Dockerfile
文件中的 FROM node:latest
换成 FROM basicnode:latest
即可,其它步骤一样。我这个项目最后打包出来,只有 80 多 M。
给 image 打 tag
给 image 打 tag,执行命令
docker tag IMAGE_ID docker.pkg.github.com/tourcoder/dockerdemo/IMAGE_NAME:VERSION
这里 IMAGE_ID 即为需要上传的 image 对应的 IMAGE ID
,可以通过 docker image ls
查看。
后面就是 IMAGE_NAME 和版本
发布 image 文件
私有的项目是不建议发布到第三方平台,这里只简单介绍下如何上传到 github package 上。
先获取权限,新建一个空白的 txt 文件 GH_TOKEN.txt,然后将 AccessToken 的内容粘贴在里面。然后执行如下命令登录
cat ~/GH_TOKEN.txt | docker login docker.pkg.github.com -u tourcoder --password-stdin
执行完成后,即可得到如下
表示已经登录成功。
发布 image,执行命令
docker push docker.pkg.github.com/tourcoder/dockerdemo/IMAGE_NAME:VERSION
完成即可。
使用 image
如果是使用本地的 image,则执行命令
docker run -d -p 3030:3000 dockernodejsdemo
其中
参数 -p
表示映射的端口,即将容器暴露出来的端口 3000 映射到本机的 3030 端口。参数 -d
表示在后台运行。
最后是 image 的名字。
如果使用的是刚才上传的 image,则执行命令
docker pull docker.pkg.github.com/tourcoder/dockerdemo/dockernodejsdemo:0.0.1
先将 image 拉到本地来,然后再执行上面本地的命令即可。需要注意在拉入到本地来之前,需要登录,即命令。
cat ~/GH_TOKEN.txt | docker login docker.pkg.github.com -u tourcoder --password-stdin
docker run -d -p 80:80 --name myNginx -v $PWD/nginx.conf:/etc/nginx/nginx.conf:ro -v $PWD/conf.d:/etc/nginx/conf.d nginx
几点注意点:
需要提前在本地创建 nginx.conf
文件,如果不创建该文件,上面的命令会自动创建一个 nginx.conf
的文件夹
创建容器后,nginx.conf
文件是空白,所以 nginx 这个容器是没有运行的,需要在 nginx.conf
写入相关内容
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
这是初始内容,也可以很省事的从容器里复制过来,执行命令 docker cp 容器 ID:/etc/nginx/nginx.conf nginx.conf
即可。
在 conf.d
这个文件夹里创建相关的配置文件,比如 default.conf
。写自己相关的配置文件。
关于如何禁止 IP 直接访问,可以查看这篇文章。
和 nginx 一样,Caddy 是一个 server,但不一样的是,它能自动配置 https,很方便。通过 docker 部署它也很方便。
sudo docker run -d -p 80:80 -p 443:443 -v $PWD/Caddyfile:/etc/caddy/Caddyfile -v $PWD/caddy_data:/data --name caddy_name caddy
解释
这里开启了两个端口的映射 80 和 443。
$PWD 是当前目录下
Caddyfile 是 Caddy 的配置文件,需要事先在当前文件夹下写好,不然该命令会自动生成一个 Caddyfile 的文件夹。
caddy_data 是对应的数据文件夹。
caddy 是选择了 caddy 这个 image,也可以用 caddy:版本号
来使用对应的版本。
caddy_name 是给当前运行的容器起个名字,比如这里可以写成 caddy_container 等
直接命令行部署
docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=password -d mysql
也可以通过 docker-compose.yml
来定义一个,然后通过 docker-compose up -d mariadb
启动。
mariadb:
image: mariadb:10.4
container_name: mariadb
ports:
- 3306:3306
environment:
- MYSQL_ROOT_PASSWORD=111111
- MYSQL_DATABASE=databasename
- MYSQL_USER=databaseuser
- MYSQL_PASSWORD=111111
volumes:
- ~/Docker/mysql/db:/var/lib/mysql
当然也需要进行一些初始化的内容,进入到该容器中
docker exec -it mysql bash
登录账号
mysql -u root -p
创建一个数据库
CREATE DATABASE IF NOT EXISTS DB_NAME DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
创建一个用户并赋予权限
CREATE USER 'db_username'@'%' IDENTIFIED WITH mysql_native_password BY 'db_password';
其中 % 表示从任何 IP 登录的用户,也可以写成 'db_username'@'localhost'
或者 'db_username'@'IP地址'
,如果没有写则默认是 %
如果需要更新密码,则用 update mysql.user set password='user_password' where user='db_username'
,更新完后,需要刷新权限 flush privileges;
。
grant select,delete,update,insert on db.* to db_username@'localhost' identified by 'db_username';
如果是全部权限,则是 grant all privileges on
,赋予权限后,则使用 flush privileges;
刷新下权限。
撤销权限用 revoke
,比如 revoke update on
表示撤销 update 权限,同样所有则用 revoke all on
。
执行命令
docker run --name mongodb -v /root/mongodb:/data/db -p 27017:27017 -d mongo --auth
其中 -v /root/mongodb:/data/db
是将容器的内容指向宿主机器 /root/mongodb
下,如果需要将备份的内容也指向宿主,则在上面命令中增加 -v /root/mongodb/backup:/data/backup
,--auth
表示需要验证账号密码,这就需要创建账号密码,执行命令
docker exec -it mongodb /bin/bash
或者
docker exec -it mongodb bash
进入容器后,输入命令 mongosh -u 用户名 -p 密码
进入数据库操作界面。mongosh
是 6.0 之后的版本,之前用 mongo
。然后选择数据库 use 数据库名
,比如给 admin
数据库增加用户,则执行下面的语句创建一个账户,并赋予其密码 123456
db.createUser({user:'admin', pwd:'123456', roles:[{role:'userAdminAnyDatabase', db:'admin'}]});
创建完成后,可以通过 db.auth('admin', '123456')
检查是否能够连接。想要修改密码的话则 db.changeUserPassword('admin','654321');
想给某个数据库创建账户,比如给 newdb
创建账户,则
use newdb;
db.createUser({user:'uname', pwd:'upassword', roles:['readWrite']});
也可以
db.createUser({user:'uname', pwd:'upassword', roles:[{role:'readWrite', db:'newdb'}]});
至于赋予某个用户的权限则使用 db.grantRolesToUser('admin', [{role:'root', db:'admin'}])
查看数据库中的用户 db.system.users.find()
,或则在某个数据库下使用 show users
查看当前库下的用户。
先在 Redis 的官方下载一个稳定版本的配置文件,即 redis.conf
wget http://download.redis.io/redis-stable/redis.conf
打开该文件,将 daemonize
后面改成 no
,将该文件保存在某一个文件夹中,比如,~/Docker/redis/redis.conf
。
通过命令即可创建一个 redis 的容器
docker run -d --name redisdemo -p 6379:6379 -v ~/Docker/redis/redis.conf:/etc/redis/redis.conf --privileged=true redis redis-server /etc/redis/redis.conf
还可通过命令 docker-compose up -d
将该容器拉起,docker-compose.yml
内容如下
version: '3'
services:
redis:
image: redis:latest
container_name: redisdemo
privileged: true
ports:
- "6379:6379"
volumes:
- ~/Docker/redis/redis.conf:/etc/redis/redis.conf
command: redis-server /etc/redis/redis.conf
完成。
有时候 Docker 会出现无法停止删除容器的情况,可以先执行命令
sudo systemctl restart docker.socket docker.service
然后就可以操作了。
> 可在 Twitter/X 上评论该篇文章或在下面留言(需要有 GitHub 账号)