前端集成化部署 docker篇
我们可能会遇到这样的问题,我们手动部署项目,可能是node项目,可能是java项目,可能是前端项目,我们安装的node版本或者jdk,tomcat版本不一致,导致项目会发生各种诡异问题,有的服务器就是好使,有的服务器就是有问题,正常来说都是部署漏了点东西。
我们就不能把好的服务打成包直接拿来使用么?
布署软件的问题
- 如果想让软件运行起来要保证操作系统的设置,各种库和组件的安装都是正确的
- 热带鱼&冷水鱼 冷水鱼适应的水温在5-30度,而热带鱼只能适应22-30度水温,低于22度半小时就冻死了
常用解决方案和对比
虚拟机
虚拟机(virtual machine)就是带环境安装的一种解决方案。它可以在一种操作系统里面运行另一种操作系统
- 资源占用多
- 冗余步骤多
- 启动速度慢
Linux容器
由于虚拟机存在这些缺点,Linux 发展出了另一种虚拟化技术:Linux 容器(Linux Containers,缩写为 LXC)。
Linux 容器不是模拟一个完整的操作系统,而是对进程进行隔离。或者说,在正常进程的外面套了一个保护层。对于容器里面的进程来说,它接触到的各种资源都是虚拟的,从而实现与底层系统的隔离。
- 启动快
- 资源占用少
- 体积小
Docker
- Docker 属于 Linux 容器的一种封装,提供简单易用的容器使用接口。它是目前最流行的 Linux 容器解决方案。
- Docker 将应用程序与该程序的依赖,打包在一个文件里面。运行这个文件,就会生成一个虚拟容器。程序在这个虚拟容器里运行,就好像在真实的物理机上运行一样
Docker和KVM
- 启动时间
- Docker秒级启动
- KVM分钟级启动
- 轻量级 容器镜像通常以M为单位,虚拟机以G为单位,容器资源占用小,要比虚拟要部署更快速
- 容器共享宿主机内核,系统级虚拟化,占用资源少,容器性能基本接近物理机
- 虚拟机需要虚拟化一些设备,具有完整的OS,虚拟机开销大,因而降低性能,没有容器性能好
- 安全性
- 由于共享宿主机内核,只是进程隔离,因此隔离性和稳定性不如虚拟机,容器具有一定权限访问宿>- 主机内核,存在一下安全隐患
- 使用要求
- KVM基于硬件的完全虚拟化,需要硬件CPU虚拟化技术支持
- 容器共享宿主机内核,可运行在主机的Linux的发行版,不用考虑CPU是否支持虚拟化技术
Docker的应用场景
-
节省项目环境部署时间
-
单项目打包
-
整套项目打包
-
新开源技术
-
环境一致性
-
持续集成
-
微服务
-
弹性伸缩
Docker 体系结构
- containerd 是一个守护进程,使用runc管理容器,向Docker Engine提供接口
- shim 只负责管理一个容器
- runC是一个轻量级工具,只用来运行容器
Docker安装
- docker分为企业版(EE)和社区版(CE)
- docker-ce
- hub.docker
安装社区版本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版本
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常用方法
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 数据盘操作
- volume
#创建数据盘
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 #删除数据库
- Bind mounts
#此方式与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
- None:该模式关闭了容器的网络功能,对外界完全隔离
- host:容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。
- bridge 桥接网络,此模式会为每一个容器分配IP
可以使用该--network标志来指定容器应连接到哪些网络
#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做编排
部署环境
node环境部署
安装完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,自己使用不会有那么大的量,所以没走实时刷新。
我们再来看看效果,是不是很香。
一次发布,终身好用
最后送大家一波福利
在这里特地讲我自己这两个月整理的相关面试题分享给大家,免费获取哦~
获取方式:
一、搜索QQ群,前端学习交流群:954854084
三、QQ扫描下方二维码!