我们可能会遇到这样的问题,我们手动部署项目,可能是node项目,可能是java项目,可能是前端项目,我们安装的node版本或者jdk,tomcat版本不一致,导致项目会发生各种诡异问题,有的服务器就是好使,有的服务器就是有问题,正常来说都是部署漏了点东西。
我们就不能把好的服务打成包直接拿来使用么?
虚拟机(virtual machine)就是带环境安装的一种解决方案。它可以在一种操作系统里面运行另一种操作系统
- 资源占用多
- 冗余步骤多
- 启动速度慢
由于虚拟机存在这些缺点,Linux 发展出了另一种虚拟化技术:Linux 容器(Linux Containers,缩写为 LXC)。
Linux 容器不是模拟一个完整的操作系统,而是对进程进行隔离。或者说,在正常进程的外面套了一个保护层。对于容器里面的进程来说,它接触到的各种资源都是虚拟的,从而实现与底层系统的隔离。
- 启动快
- 资源占用少
- 体积小
- Docker秒级启动
- KVM分钟级启动
- 容器共享宿主机内核,系统级虚拟化,占用资源少,容器性能基本接近物理机
- 虚拟机需要虚拟化一些设备,具有完整的OS,虚拟机开销大,因而降低性能,没有容器性能好
- 由于共享宿主机内核,只是进程隔离,因此隔离性和稳定性不如虚拟机,容器具有一定权限访问宿>- 主机内核,存在一下安全隐患
- KVM基于硬件的完全虚拟化,需要硬件CPU虚拟化技术支持
- 容器共享宿主机内核,可运行在主机的Linux的发行版,不用考虑CPU是否支持虚拟化技术
- 单项目打包
- 整套项目打包
- 新开源技术
安装社区版本docker yum install -y yum-utils device-mapper-persistent-data lvm2 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo yum-config-manager --enable docker-ce-nightly #要每日构建版本的 Docker CE yum-config-manager --enable docker-ce-test yum install docker-ce docker-ce-cli containerd.io 复制代码
docker 启动
systemctl start docker 复制代码
查看docker版本
docker version docker info 复制代码
阿里云镜像加速
sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": ["https://fwvjnv59.mirror.aliyuncs.com"] } EOF # 重载所有修改过的配置文件 sudo systemctl daemon-reload sudo systemctl restart docker 复制代码
docker image镜像操作
命令 | 含义 | 案例 |
---|---|---|
ls | 查看全部镜像 | docker image ls |
search | 查找镜像 | docker search [imageName] |
history | 查看镜像历史 | docker history [imageName] |
inspect | 显示一个或多个镜像详细信息 | docker inspect [imageName] |
pull | 拉取镜像 | docker pull [imageName] |
push | 推送一个镜像到镜像仓库 | docker push [imageName] |
rmi | 删除镜像 | docker rmi [imageName] docker image rmi 2 |
prune | 移除未使用的镜像,没有被标记或补任何容器引用 | docker image prune |
tag | 标记本地镜像,将其归入某一仓库 | docker image tag [imageName] [username]/[repository]:[tag] |
export | 导出容器文件系统tar归档文件创建镜像 | docker export -o mysqlv1.tar a404c6c174a2 |
import | 导入容器快照文件系统tar归档文件创建镜像 | docker import mysqlv1.tar wp/mysql:v2 |
save | 保存一个或多个镜像到一个tar归档文件 | docker save -o mysqlv2.tar wp/mysqlv2:v3 |
load | 加载镜像存储文件来自tar归档或标准输入 | docker load -i mysqlv2.tar |
build | 根据Dockerfile构建镜像 |
docker 容器操作
命令 | 含义 | 案例 |
---|---|---|
run | 从镜像运行一个容器 | docker run ubuntu /bin/echo 'hello-world' |
ls | 列出容器 | docker container ls |
inspect | 显示一个或多个容器详细信息 | docker inspect |
attach | 要attach上去的容器必须正在运行,可以同时连接上同一个container来共享屏幕 | docker attach |
stats | 显示容器资源使用统计 | docker container stats |
top | 显示一个容器运行的进程 | docker container top |
update | 显示一个容器运行的进程 | docker container update |
port | 更新一个或多个容器配置 | docker container port |
ps | 查看当前运行的容器 | docker ps -a -l |
kill [containerId] | 终止容器(发送SIGKILL ) | docker kill [containerId] |
rm [containerId] | 删除容器 | docker rm [containerId] |
start [containerId] | 启动已经生成、已经停止运行的容器文件 | docker start [containerId] |
stop [containerId] | 终止容器运行 (发送 SIGTERM ) | docker stop [containerId] |
logs [containerId] | 查看 docker 容器的输出 | docker logs [containerId] |
exec [containerId] | 进入一个正在运行的 docker 容器执行命令 | docker container exec -it [containerID] /bin/bash |
cp [containerId] | 从正在运行的 Docker 容器里面,将文件拷贝到本机 | docker container cp [containID]:app/package.json . |
commit [containerId] | 创建一个新镜像来自一个容器 | docker commit -a "wp" -m "mysql" a404c6c174a2 mynginx:v1 |
docker 数据盘操作
#创建数据盘 docker volume create nginx-vol docker volume ls docker volume inspect nginx-vol #把nginx-vol数据卷挂载到/usr/share/nginx/html,挂载后容器内的文件会同步到数据卷中 docker run -d --name=nginx1 --mount src=nginx-vol,dst=/usr/share/nginx/html nginx docker run -d --name=nginx2 -v nginx-vol:/usr/share/nginx/html -p 3000:80 nginx #删除数据卷 docker container stop nginx1 #停止容器 docker container rm nginx1 #删除容器 docker volume rm nginx-vol #删除数据库 复制代码
#此方式与Linux系统的mount方式很相似,即是会覆盖容器内已存在的目录或文件,但并不会改变容器内原有的文件,当umount后容器内原有的文件就会还原 #创建容器的时候我们可以通过-v或--volumn给它指定一下数据盘 #bind mounts 可以存储在宿主机系统的任意位置 #如果源文件/目录不存在,不会自动创建,会抛出一个错误 #如果挂载目标在容器中非空目录,则该目录现有内容将被隐藏 docker run -v /mnt:/mnt -it --name logs centos bash cd /mnt echo 1 > 1.txt docker inspect logs #可以查看到挂载信息 "Mounts": [ { "Source":"/mnt/sda1/var/lib/docker/volumes/dea6a8b3aefafa907d883895bbf931a502a51959f83d63b7ece8d7814cf5d489/_data", "Destination": "/mnt", } ] # 指定数据盘容器 docker create -v /mnt:/mnt --name logger centos docker run --volumes-from logger --name logger3 -i -t centos bash cd /mnt touch logger3 docker run --volumes-from logger --name logger4 -i -t centos bash cd /mnt touch logger4 复制代码
docker 网络
安装Docker时,它会自动创建三个网络,bridge(创建容器默认连接到此网络)、 none 、host
#bridge模式使用 --net=bridge 指定,默认设置 docker network ls #列出当前的网络 docker inspect bridge #查看当前的桥连网络 docker run -d --name nginx1 nginx docker run -d --name nginx2 --link nginx1 nginx docker exec -it nginx2 bash apt update apt install -y inetutils-ping #ping apt install -y dnsutils #nslookup apt install -y net-tools #ifconfig apt install -y iproute2 #ip apt install -y curl #curl cat /etc/hosts ping nginx1 # none模式使用--net=none指定 # --net 指定无网络 docker run -d --name nginx_none --net none nginx docker inspect none docker exec -it nginx_none bash ip addr # host模式使用 --net=host 指定 docker run -d --name nginx_host --net host nginx docker inspect host docker exec -it nginx_host bash ip addr 复制代码
端口映射
# 查看镜像里暴露出的端口号 docker image inspect nginx "ExposedPorts": {"80/tcp": {}} # 让宿主机的8080端口映射到docker容器的80端口 docker run -d --name port_nginx -p 8080:80 nginx # 查看主机绑定的端口 docker container port port_nginx #指向主机的随机端口 docker run -d --name random_nginx --publish 80 nginx docker port random_nginx docker run -d --name randomall_nginx --publish-all nginx docker run -d --name randomall_nginx --P nginx #创建自定义网络 docker network create --driver bridge myweb # 查看自定义网络中的主机 docker network inspect myweb # 创建容器的时候指定网络 指定同一个网络的容器是可以互相通信的 docker run -d --name mynginx1 --net myweb nginx docker run -d --name mynginx2 --net myweb nginx docker exec -it mynginx2 bash ping mynginx1 # 连接到指定网络 docker run -d --name mynginx3 nginx docker network connect myweb mynginx3 docker network disconnect myweb mynginx3 # 移除网络 docker network rm myweb 复制代码
compose 暂时先不说,暂时用到的不多,主要做编排使用,基本上都在使用jekins做编排
安装完docker环境 继续安装node环境
nvm: # nvm管理node版本 curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.1/install.sh | bash source ~/.bash_profile nvm ls nvm install stable 安装最新的稳定版本 nvm use stable nrm:# 切换node镜像,修改源为淘宝镜像 npm i -g nrm nrm use taobao 复制代码
安装pm2 部署线上 node服务
npm i -g pm2 cd /root/webhook pm2 start webhook.js --name webhook --watch pm2 list | pm2 ls 复制代码
回想起以前的前端部署都是前端打个目标文件,压缩成压缩包或者rpm安装包去发布,如果有多个环境还需要一步步的去连服务器去手动发布
为了解决这种耗人力的工作,这边推出了一款简易的docker发布项目
我们可以把其中一台服务器配置成发布服务器,用来编译新镜像发布新镜像,然后直接拷贝镜像到别的服务器直接启动
现在我们见一个node项目 docker-hook
此项目的核心是通过用户点击页面上的触发去动态调用sh去处理我们的脚本
中间一版本我们是通过接口调用触发,发现不是很好用,就做一个可视化平台去使用
也可以通过gitHub的webhook去动态触发CI/CD,提交即部署,这边我就不贴代码了
这边主要讲思路,贴上部分代码,如果有需要优化的部分麻烦指正
// 本项目使用的是通过node的child_process spawn开启一个子进程去处理sh命令 // console log日志是通过morgan 自定义输出 // 每个模块的sh脚本都会通过winston把实时日志存储到对应的模块日志文件中,文件大问题,我们就按天生成一个文件日志 /** logger.js **/ const winston=require('winston'); const { APP_LIST } = require('./constant') const { loggerTime } = require('./util') const loggerList = {}; APP_LIST.forEach(item => { loggerList[item.loggerName] = winston.createLogger({ transports: [ new (winston.transports.Console)(), new (winston.transports.File)({ filename: `public/logs/${item.loggerName}-${loggerTime()}.log`, timestamp:'true', maxsize: 10485760, //日志文件的大小 maxFiles: 10 }) ]}); }); loggerList['init'] = winston.createLogger({ transports: [ new (winston.transports.Console)(), new (winston.transports.File)({ filename: `public/logs/init-${loggerTime()}.log`, timestamp:'true', maxsize: 10485760, //日志文件的大小 maxFiles: 10 }) ]}); module.exports = loggerList; /** app.js **/ let { spawn } = require('child_process'); /** * 统一处理shell脚本执行 */ function handleShellFile(projectName, shellPath, res, req) { return resolveFile(shellPath).then(data => { // 判断当前是否是成功 if(!data.success) { errorHandle(res); } let child = spawn('sh', [data.filePath]) let buffers = []; child.stdout.on('data', (buffer) => { buffers.push(buffer); console.log('实时日志:', buffer.toString()); logger[projectName] && logger[projectName].log("info", `实时日志:${buffer.toString()}`); }) child.stdout.on('end',function(buffer){ let logs = Buffer.concat(buffers, buffer).toString(); console.log('执行完毕'); logger[projectName] && logger[projectName].log("info", '执行完毕'); res.setHeader('Content-Type', 'application/json'); res.end(JSON.stringify({ok: true})) }); child.on('close', (code) => { if (code !== 0) { console.log(`子进程退出,退出码 ${code}`); } }); }, error => { // 错误处理显示返回 errorHandle(res); }) } 复制代码
shell文件介绍:
├─ docker-hook │ ├─ sh // shell脚本文件 │ │ ├─ Archer-front-image.sh // 前端版本复制镜像 │ │ ├─ Archer-front-remote.sh // 前端版本远程打包 │ │ ├─ Archer-front.sh // 前端版本本地打包编译发布(本地使用) │ │ ├─ ar-mock-image.sh // armock项目复制镜像 │ │ ├─ ar-mock-remote.sh // armock项目远程打包 │ │ ├─ ar-mock.sh // armock项目本地打包编译发布(本地使用) │ │ ├─ env-init.sh // 环境初始化 │ │ └─ project-init.sh // git项目初始化,帮忙建目录 复制代码
env-init.sh 可以拷贝到服务器 一键去部署环境
#!/bin/bash echo 'docker 环境初始化' function docker_install() { echo "检查Docker......" docker -v if [ $? -eq 0 ]; then echo "检查到Docker已安装!" else echo "安装docker环境..." yum install -y yum-utils device-mapper-persistent-data lvm2 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo yum-config-manager --enable docker-ce-nightly #要每日构建版本的 Docker CE yum-config-manager --enable docker-ce-test yum install -y docker-ce docker-ce-cli containerd.io echo '启动docker' systemctl start docker echo '查看docker' docker version echo "安装docker环境...安装完成!" fi } # 执行函数 docker_install # nrm 是否安装 function nvm_install() { nvm --version if [ $? -eq 0 ]; then echo "检查到nvm已安装!" nvm install v13.14.0 #安装最新的稳定版本 nvm use v13.14.0 echo "安装node环境...安装完成!" else source /root/.bashrc echo "安装nvm失败..." fi } # node 是否安装 function node_install() { echo "检查node......" node -v if [ $? -eq 0 ]; then echo "检查到Node已安装!" else echo "安装nvm环境..." curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.1/install.sh | bash source /root/.bashrc nvm_install fi } # node_module库 安装监测 function node_module_install() { node --version if [ $? -eq 0 ]; then echo "安装nrm源和pm2库" nrm_install pm2_install else echo "node环境未安装成功" fi } # nrm 安装监测 function nrm_install() { echo "监测nrm源..." nrm --version if [ $? -eq 0 ]; then echo "已安装nrm源" else npm i -g nrm nrm use taobao echo "安装nrm源成功" fi } # pm2 安装监测 function pm2_install() { echo "监测pm2库..." pm2 --version if [ $? -eq 0 ]; then echo "已安装pm2库" else npm i -g pm2 echo "安装pm2库成功" fi } # 执行函数 echo '安装node环境' node_install node_module_install 复制代码
# 如果已经安装过node,确认下是否更新过~/.bash_profile,没有则添加,也可以安装nvm去管理node export NODE_ENV=/root/node/node-v12.16.2-linux-x64 PATH=$PATH:$HOME/bin:$NODE_ENV/bin 刷新配置文件 source ~/.bash_profile
project.sh 文件主要是建立文件目录,git clone文件并为后续的部署做准备
Archer-front.sh 拉代码部署,镜像生成,容器部署一个文件搞定
#!/bin/bash WORK_PATH='/root/front' cd $WORK_PATH echo "清除老代码" git reset --hard origin/master git clean -f echo "拉取最新代码" git pull origin master echo "删除node_modules文件" rm -rf ./node_modules echo "重新安装依赖" npm i echo "编译打包" npm run build echo "开始执行构建" docker build -f ./docker/Dockerfile -t archer-front:1.0 . echo "停止旧的容器并删除容器" docker stop archer-front-container docker rm archer-front-container echo "启动新容器" docker run -p 11001:11001 -v /etc/hosts:/etc/hosts --name archer-front-container -itd archer-front:1.0 复制代码
那么多节点部署怎么办呢?
我们可以考虑把当前的这个镜像导出并导入加载
Archer-front-image.sh
#!/bin/bash echo "进入目录/root/images" WORK_PATH='/root' cd $WORK_PATH if [ ! -d images ];then mkdir images fi IMAGES_PATH='images' cd $IMAGES_PATH echo "开始拷贝前端镜像" docker save -o image-Archer-front.tar archer-front:1.0 echo "拷贝前端镜像完成" 复制代码
其他节点怎么来拿呢?可以通过scp来拷贝这边打包出来的镜像去使用啊,这就是Archer-front-remote.sh里面的实现
查看日志功能主要是通过定时刷新调用接口去实现的,有些low,自己使用不会有那么大的量,所以没走实时刷新。
我们再来看看效果,是不是很香。