我們可能會(huì)遇到這樣的問(wèn)題,我們手動(dòng)部署項(xiàng)目,可能是node項(xiàng)目,可能是java項(xiàng)目,可能是前端項(xiàng)目,我們安裝的node版本或者jdk,tomcat版本不一致,導(dǎo)致項(xiàng)目會(huì)發(fā)生各種詭異問(wèn)題,有的服務(wù)器就是好使,有的服務(wù)器就是有問(wèn)題,正常來(lái)說(shuō)都是部署漏了點(diǎn)東西。
我們就不能把好的服務(wù)打成包直接拿來(lái)使用么?
布署軟件的問(wèn)題
- 如果想讓軟件運(yùn)行起來(lái)要保證操作系統(tǒng)的設(shè)置,各種庫(kù)和組件的安裝都是正確的
- 熱帶魚&冷水魚 冷水魚適應(yīng)的水溫在5-30度,而熱帶魚只能適應(yīng)22-30度水溫,低于22度半小時(shí)就凍死了
常用解決方案和對(duì)比
虛擬機(jī)
虛擬機(jī)(virtual machine)就是帶環(huán)境安裝的一種解決方案。它可以在一種操作系統(tǒng)里面運(yùn)行另一種操作系統(tǒng)
- 資源占用多
- 冗余步驟多
- 啟動(dòng)速度慢
Linux容器
由于虛擬機(jī)存在這些缺點(diǎn),Linux 發(fā)展出了另一種虛擬化技術(shù):Linux 容器(Linux Containers,縮寫為 LXC)。
Linux 容器不是模擬一個(gè)完整的操作系統(tǒng),而是對(duì)進(jìn)程進(jìn)行隔離?;蛘哒f(shuō),在正常進(jìn)程的外面套了一個(gè)保護(hù)層。對(duì)于容器里面的進(jìn)程來(lái)說(shuō),它接觸到的各種資源都是虛擬的,從而實(shí)現(xiàn)與底層系統(tǒng)的隔離。
- 啟動(dòng)快
- 資源占用少
- 體積小
Docker
- Docker 屬于 Linux 容器的一種封裝,提供簡(jiǎn)單易用的容器使用接口。它是目前最流行的 Linux 容器解決方案。
- Docker 將應(yīng)用程序與該程序的依賴,打包在一個(gè)文件里面。運(yùn)行這個(gè)文件,就會(huì)生成一個(gè)虛擬容器。程序在這個(gè)虛擬容器里運(yùn)行,就好像在真實(shí)的物理機(jī)上運(yùn)行一樣
Docker和KVM
- 啟動(dòng)時(shí)間
- Docker秒級(jí)啟動(dòng)
- KVM分鐘級(jí)啟動(dòng)
- 輕量級(jí) 容器鏡像通常以M為單位,虛擬機(jī)以G為單位,容器資源占用小,要比虛擬要部署更快速
- 容器共享宿主機(jī)內(nèi)核,系統(tǒng)級(jí)虛擬化,占用資源少,容器性能基本接近物理機(jī)
- 虛擬機(jī)需要虛擬化一些設(shè)備,具有完整的OS,虛擬機(jī)開銷大,因而降低性能,沒(méi)有容器性能好
- 安全性
- 由于共享宿主機(jī)內(nèi)核,只是進(jìn)程隔離,因此隔離性和穩(wěn)定性不如虛擬機(jī),容器具有一定權(quán)限訪問(wèn)宿>- 主機(jī)內(nèi)核,存在一下安全隱患
- 使用要求
- KVM基于硬件的完全虛擬化,需要硬件CPU虛擬化技術(shù)支持
- 容器共享宿主機(jī)內(nèi)核,可運(yùn)行在主機(jī)的Linux的發(fā)行版,不用考慮CPU是否支持虛擬化技術(shù)
Docker的應(yīng)用場(chǎng)景
節(jié)省項(xiàng)目環(huán)境部署時(shí)間
單項(xiàng)目打包
整套項(xiàng)目打包
新開源技術(shù)
環(huán)境一致性
持續(xù)集成
微服務(wù)
彈性伸縮
Docker 體系結(jié)構(gòu)
- containerd 是一個(gè)守護(hù)進(jìn)程,使用runc管理容器,向Docker Engine提供接口
- shim 只負(fù)責(zé)管理一個(gè)容器
- runC是一個(gè)輕量級(jí)工具,只用來(lái)運(yùn)行容器
Docker安裝
- docker分為企業(yè)版(EE)和社區(qū)版(CE)
- docker-ce
- hub.docker
安裝社區(qū)版本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 #要每日構(gòu)建版本的 Docker CE
yum-config-manager --enable docker-ce-test
yum install docker-ce docker-ce-cli containerd.io
docker 啟動(dòng)
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
# 重載所有修改過(guò)的配置文件
sudo systemctl daemon-reload
sudo systemctl restart docker
Docker常用方法
docker image鏡像操作
| 命令 | 含義 | 案例 |
|---|---|---|
| ls | 查看全部鏡像 | docker image ls |
| search | 查找鏡像 | docker search [imageName] |
| history | 查看鏡像歷史 | docker history [imageName] |
| inspect | 顯示一個(gè)或多個(gè)鏡像詳細(xì)信息 | docker inspect [imageName] |
| pull | 拉取鏡像 | docker pull [imageName] |
| push | 推送一個(gè)鏡像到鏡像倉(cāng)庫(kù) | docker push [imageName] |
| rmi | 刪除鏡像 | docker rmi [imageName] docker image rmi 2 |
| prune | 移除未使用的鏡像,沒(méi)有被標(biāo)記或補(bǔ)任何容器引用 | docker image prune |
| tag | 標(biāo)記本地鏡像,將其歸入某一倉(cāng)庫(kù) | docker image tag [imageName] [username]/[repository]:[tag] |
| export | 導(dǎo)出容器文件系統(tǒng)tar歸檔文件創(chuàng)建鏡像 | docker export -o mysqlv1.tar a404c6c174a2 |
| import | 導(dǎo)入容器快照文件系統(tǒng)tar歸檔文件創(chuàng)建鏡像 | docker import mysqlv1.tar wp/mysql:v2 |
| save | 保存一個(gè)或多個(gè)鏡像到一個(gè)tar歸檔文件 | docker save -o mysqlv2.tar wp/mysqlv2:v3 |
| load | 加載鏡像存儲(chǔ)文件來(lái)自tar歸檔或標(biāo)準(zhǔn)輸入 | docker load -i mysqlv2.tar |
| build | 根據(jù)Dockerfile構(gòu)建鏡像 |
docker 容器操作
| 命令 | 含義 | 案例 |
|---|---|---|
| run | 從鏡像運(yùn)行一個(gè)容器 | docker run ubuntu /bin/echo 'hello-world' |
| ls | 列出容器 | docker container ls |
| inspect | 顯示一個(gè)或多個(gè)容器詳細(xì)信息 | docker inspect |
| attach | 要attach上去的容器必須正在運(yùn)行,可以同時(shí)連接上同一個(gè)container來(lái)共享屏幕 | docker attach |
| stats | 顯示容器資源使用統(tǒng)計(jì) | docker container stats |
| top | 顯示一個(gè)容器運(yùn)行的進(jìn)程 | docker container top |
| update | 顯示一個(gè)容器運(yùn)行的進(jìn)程 | docker container update |
| port | 更新一個(gè)或多個(gè)容器配置 | docker container port |
| ps | 查看當(dāng)前運(yùn)行的容器 | docker ps -a -l |
| kill [containerId] | 終止容器(發(fā)送SIGKILL ) | docker kill [containerId] |
| rm [containerId] | 刪除容器 | docker rm [containerId] |
| start [containerId] | 啟動(dòng)已經(jīng)生成、已經(jīng)停止運(yùn)行的容器文件 | docker start [containerId] |
| stop [containerId] | 終止容器運(yùn)行 (發(fā)送 SIGTERM ) | docker stop [containerId] |
| logs [containerId] | 查看 docker 容器的輸出 | docker logs [containerId] |
| exec [containerId] | 進(jìn)入一個(gè)正在運(yùn)行的 docker 容器執(zhí)行命令 | docker container exec -it [containerID] /bin/bash |
| cp [containerId] | 從正在運(yùn)行的 Docker 容器里面,將文件拷貝到本機(jī) | docker container cp [containID]:app/package.json . |
| commit [containerId] | 創(chuàng)建一個(gè)新鏡像來(lái)自一個(gè)容器 | docker commit -a "wp" -m "mysql" a404c6c174a2 mynginx:v1 |
docker 數(shù)據(jù)盤操作
- volume
#創(chuàng)建數(shù)據(jù)盤
docker volume create nginx-vol docker volume ls docker volume inspect nginx-vol #把nginx-vol數(shù)據(jù)卷掛載到/usr/share/nginx/html,掛載后容器內(nèi)的文件會(huì)同步到數(shù)據(jù)卷中
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 #刪除數(shù)據(jù)卷
docker container stop nginx1 #停止容器
docker container rm nginx1 #刪除容器
docker volume rm nginx-vol #刪除數(shù)據(jù)庫(kù)
- Bind mounts
#此方式與Linux系統(tǒng)的mount方式很相似,即是會(huì)覆蓋容器內(nèi)已存在的目錄或文件,但并不會(huì)改變?nèi)萜鲀?nèi)原有的文件,當(dāng)umount后容器內(nèi)原有的文件就會(huì)還原
#創(chuàng)建容器的時(shí)候我們可以通過(guò)-v或--volumn給它指定一下數(shù)據(jù)盤
#bind mounts 可以存儲(chǔ)在宿主機(jī)系統(tǒng)的任意位置
#如果源文件/目錄不存在,不會(huì)自動(dòng)創(chuàng)建,會(huì)拋出一個(gè)錯(cuò)誤
#如果掛載目標(biāo)在容器中非空目錄,則該目錄現(xiàn)有內(nèi)容將被隱藏
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",
}
]
# 指定數(shù)據(jù)盤容器
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 網(wǎng)絡(luò)
安裝Docker時(shí),它會(huì)自動(dòng)創(chuàng)建三個(gè)網(wǎng)絡(luò),bridge(創(chuàng)建容器默認(rèn)連接到此網(wǎng)絡(luò))、 none 、host
- None:該模式關(guān)閉了容器的網(wǎng)絡(luò)功能,對(duì)外界完全隔離
- host:容器將不會(huì)虛擬出自己的網(wǎng)卡,配置自己的IP等,而是使用宿主機(jī)的IP和端口。
- bridge 橋接網(wǎng)絡(luò),此模式會(huì)為每一個(gè)容器分配IP
可以使用該--network標(biāo)志來(lái)指定容器應(yīng)連接到哪些網(wǎng)絡(luò)
#bridge模式使用 --net=bridge 指定,默認(rèn)設(shè)置
docker network ls #列出當(dāng)前的網(wǎng)絡(luò)
docker inspect bridge #查看當(dāng)前的橋連網(wǎng)絡(luò)
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 指定無(wú)網(wǎng)絡(luò)
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
端口映射
# 查看鏡像里暴露出的端口號(hào)
docker image inspect nginx
"ExposedPorts": {"80/tcp": {}}
# 讓宿主機(jī)的8080端口映射到docker容器的80端口
docker run -d --name port_nginx -p 8080:80 nginx # 查看主機(jī)綁定的端口
docker container port port_nginx
#指向主機(jī)的隨機(jī)端口
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
#創(chuàng)建自定義網(wǎng)絡(luò)
docker network create --driver bridge myweb
# 查看自定義網(wǎng)絡(luò)中的主機(jī)
docker network inspect myweb
# 創(chuàng)建容器的時(shí)候指定網(wǎng)絡(luò) 指定同一個(gè)網(wǎng)絡(luò)的容器是可以互相通信的
docker run -d --name mynginx1 --net myweb nginx docker run -d --name mynginx2 --net myweb nginx docker exec -it mynginx2 bash
ping mynginx1
# 連接到指定網(wǎng)絡(luò)
docker run -d --name mynginx3 nginx docker network connect myweb mynginx3
docker network disconnect myweb mynginx3
# 移除網(wǎng)絡(luò)
docker network rm myweb
compose 暫時(shí)先不說(shuō),暫時(shí)用到的不多,主要做編排使用,基本上都在使用jekins做編排
部署環(huán)境
node環(huán)境部署
安裝完docker環(huán)境 繼續(xù)安裝node環(huán)境
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 安裝最新的穩(wěn)定版本
nvm use stable
nrm:# 切換node鏡像,修改源為淘寶鏡像
npm i -g nrm
nrm use taobao
安裝pm2 部署線上 node服務(wù)
npm i -g pm2
cd /root/webhook
pm2 start webhook.js --name webhook --watch
pm2 list | pm2 ls
集成項(xiàng)目搭建
回想起以前的前端部署都是前端打個(gè)目標(biāo)文件,壓縮成壓縮包或者rpm安裝包去發(fā)布,如果有多個(gè)環(huán)境還需要一步步的去連服務(wù)器去手動(dòng)發(fā)布
為了解決這種耗人力的工作,這邊推出了一款簡(jiǎn)易的docker發(fā)布項(xiàng)目
我們可以把其中一臺(tái)服務(wù)器配置成發(fā)布服務(wù)器,用來(lái)編譯新鏡像發(fā)布新鏡像,然后直接拷貝鏡像到別的服務(wù)器直接啟動(dòng)
現(xiàn)在我們見一個(gè)node項(xiàng)目docker-hook
此項(xiàng)目的核心是通過(guò)用戶點(diǎn)擊頁(yè)面上的觸發(fā)去動(dòng)態(tài)調(diào)用sh去處理我們的腳本
中間一版本我們是通過(guò)接口調(diào)用觸發(fā),發(fā)現(xiàn)不是很好用,就做一個(gè)可視化平臺(tái)去使用
也可以通過(guò)gitHub的webhook去動(dòng)態(tài)觸發(fā)CI/CD,提交即部署,這邊我就不貼代碼了
這邊主要講思路,貼上部分代碼,如果有需要優(yōu)化的部分麻煩指正
// 本項(xiàng)目使用的是通過(guò)node的child_process spawn開啟一個(gè)子進(jìn)程去處理sh命令
// console log日志是通過(guò)morgan 自定義輸出
// 每個(gè)模塊的sh腳本都會(huì)通過(guò)winston把實(shí)時(shí)日志存儲(chǔ)到對(duì)應(yīng)的模塊日志文件中,文件大問(wèn)題,我們就按天生成一個(gè)文件日志
/** 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');
/**
* 統(tǒng)一處理shell腳本執(zhí)行
*/
function handleShellFile(projectName, shellPath, res, req) {
return resolveFile(shellPath).then(data => {
// 判斷當(dāng)前是否是成功
if(!data.success) {
errorHandle(res);
}
let child = spawn('sh', [data.filePath])
let buffers = [];
child.stdout.on('data', (buffer) => {
buffers.push(buffer);
console.log('實(shí)時(shí)日志:', buffer.toString());
logger[projectName] && logger[projectName].log("info", `實(shí)時(shí)日志:${buffer.toString()}`);
})
child.stdout.on('end',function(buffer){
let logs = Buffer.concat(buffers, buffer).toString();
console.log('執(zhí)行完畢');
logger[projectName] && logger[projectName].log("info", '執(zhí)行完畢');
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ok: true}))
});
child.on('close', (code) => {
if (code !== 0) {
console.log(`子進(jìn)程退出,退出碼 ${code}`);
}
});
}, error => {
// 錯(cuò)誤處理顯示返回
errorHandle(res);
})
}
shell文件介紹:
├─ docker-hook
│ ├─ sh // shell腳本文件
│ │ ├─ Archer-front-image.sh // 前端版本復(fù)制鏡像
│ │ ├─ Archer-front-remote.sh // 前端版本遠(yuǎn)程打包
│ │ ├─ Archer-front.sh // 前端版本本地打包編譯發(fā)布(本地使用)
│ │ ├─ ar-mock-image.sh // armock項(xiàng)目復(fù)制鏡像
│ │ ├─ ar-mock-remote.sh // armock項(xiàng)目遠(yuǎn)程打包
│ │ ├─ ar-mock.sh // armock項(xiàng)目本地打包編譯發(fā)布(本地使用)
│ │ ├─ env-init.sh // 環(huán)境初始化
│ │ └─ project-init.sh // git項(xiàng)目初始化,幫忙建目錄
env-init.sh可以拷貝到服務(wù)器 一鍵去部署環(huán)境
#!/bin/bash
echo 'docker 環(huán)境初始化'
function docker_install()
{
echo "檢查Docker......"
docker -v
if [ $? -eq 0 ]; then
echo "檢查到Docker已安裝!"
else
echo "安裝docker環(huán)境..."
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 #要每日構(gòu)建版本的 Docker CE
yum-config-manager --enable docker-ce-test
yum install -y docker-ce docker-ce-cli containerd.io
echo '啟動(dòng)docker'
systemctl start docker
echo '查看docker'
docker version
echo "安裝docker環(huán)境...安裝完成!"
fi
}
# 執(zhí)行函數(shù)
docker_install
# nrm 是否安裝
function nvm_install()
{
nvm --version
if [ $? -eq 0 ]; then
echo "檢查到nvm已安裝!"
nvm install v13.14.0 #安裝最新的穩(wěn)定版本
nvm use v13.14.0
echo "安裝node環(huán)境...安裝完成!"
else
source /root/.bashrc
echo "安裝nvm失敗..."
fi
}
# node 是否安裝
function node_install()
{
echo "檢查node......"
node -v
if [ $? -eq 0 ]; then
echo "檢查到Node已安裝!"
else
echo "安裝nvm環(huán)境..."
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.1/install.sh | bash
source /root/.bashrc
nvm_install
fi
}
# node_module庫(kù) 安裝監(jiān)測(cè)
function node_module_install()
{
node --version
if [ $? -eq 0 ]; then
echo "安裝nrm源和pm2庫(kù)"
nrm_install
pm2_install
else
echo "node環(huán)境未安裝成功"
fi
}
# nrm 安裝監(jiān)測(cè)
function nrm_install() {
echo "監(jiān)測(cè)nrm源..."
nrm --version
if [ $? -eq 0 ]; then
echo "已安裝nrm源"
else
npm i -g nrm
nrm use taobao
echo "安裝nrm源成功"
fi
}
# pm2 安裝監(jiān)測(cè)
function pm2_install() {
echo "監(jiān)測(cè)pm2庫(kù)..."
pm2 --version
if [ $? -eq 0 ]; then
echo "已安裝pm2庫(kù)"
else
npm i -g pm2
echo "安裝pm2庫(kù)成功"
fi
}
# 執(zhí)行函數(shù)
echo '安裝node環(huán)境'
node_install
node_module_install
如果已經(jīng)安裝過(guò)node,確認(rèn)下是否更新過(guò)~/.bash_profile,沒(méi)有則添加,也可以安裝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文件并為后續(xù)的部署做準(zhǔn)備
Archer-front.sh拉代碼部署,鏡像生成,容器部署一個(gè)文件搞定
#!/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 "開始執(zhí)行構(gòu)建"
docker build -f ./docker/Dockerfile -t archer-front:1.0 .
echo "停止舊的容器并刪除容器"
docker stop archer-front-container
docker rm archer-front-container
echo "啟動(dòng)新容器"
docker run -p 11001:11001 -v /etc/hosts:/etc/hosts --name archer-front-container -itd archer-front:1.0
那么多節(jié)點(diǎn)部署怎么辦呢?
我們可以考慮把當(dāng)前的這個(gè)鏡像導(dǎo)出并導(dǎo)入加載
Archer-front-image.sh
#!/bin/bash
echo "進(jìn)入目錄/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 "拷貝前端鏡像完成"
其他節(jié)點(diǎn)怎么來(lái)拿呢?可以通過(guò)scp來(lái)拷貝這邊打包出來(lái)的鏡像去使用啊,這就是Archer-front-remote.sh里面的實(shí)現(xiàn)
查看日志功能主要是通過(guò)定時(shí)刷新調(diào)用接口去實(shí)現(xiàn)的,有些low,自己使用不會(huì)有那么大的量,所以沒(méi)走實(shí)時(shí)刷新。
我們?cè)賮?lái)看看效果,是不是很香。

一次發(fā)布,終身好用
最后送大家一波福利
在這里特地講我自己這兩個(gè)月整理的相關(guān)面試題分享給大家,免費(fèi)獲取哦~


獲取方式:
一、搜索QQ群,前端學(xué)習(xí)交流群:954854084
三、QQ掃描下方二維碼!
