Практика облегченного развертывания переднего плана

JavaScript

задний план

Традиционный процесс ручного развертывания внешнего кода выглядит следующим образом:

手工部署流程

Традиционное ручное развертывание должно пройти через:

  • 1. Упаковать, запустить локальноnpm run buildПакет для создания папки dist.
  • 2. Подключаемся к серверу по SSH и переключаем путь к соответствующему каталогу в сети.
  • 3. Загрузите код в веб-каталог, обычно через xshell или xftp.

Традиционное ручное развертывание имеет следующие недостатки:

  • 1. Каждый раз вам нужно открывать программное обеспечение xshell, чтобы установить соединение с сервером.
  • 2. Когда вы отвечаете за несколько проектов и каждый проект имеет тестовую среду и онлайн-среду, легко вызвать ошибки развертывания.
    (Раньше я столкнулся с очень трагической встречей, потому что я отвечал за четыре проекта и восемь сред одновременно. Я мог модифицировать несколько проектов одновременно за один день, и у меня кружилась голова. Я развернул код тестовой среды к онлайн-среде, так и хотелось плакать без слез)

Полностью автоматизированное развертывание может быть реализовано с помощью Jenkins. Jenkins может автоматически упаковывать код в веб-каталог в соответствии с событиями gitlab push или merge. Вы можете обратиться к:

Jenkins+Docker автоматизирует развертывание проектов vue

Использование jenkins для развертывания очень удобно, но есть и недостатки, такие как хлопотная установка и настройка, упаковка и занятие ресурсов сервера.

Поскольку наш сервер работает под высокой нагрузкой круглый год, возникла ситуация, когда jenkeins упаковали сервер так, чтобы он сломался, поэтому мы можем только заставить блоггеров принять легкое решение для развертывания для достижения автоматического развертывания (конечно, технические решения вытесняются , хаха).

1. Программное исследование

считать:
Можете ли вы запустить что-то вродеnpm run deployСкрипт для упаковки и развертывания нашего кода непосредственно в веб-каталоге на сервере?

После некоторых исследований: нашелnode-ssh,archiverможет удовлетворить наши потребности.

1.1.node-ssh

node-ssh — это облегченный пакет npm, основанный на ssh2, который в основном используется для ssh для подключения к серверу, загрузки файлов и выполнения команд.

руководство пользователя:

const node_ssh = require('node-ssh')
const ssh = new node_ssh()

Используемые API:

  • 1.ssh.connect: подключиться к серверу
ssh.connect({
  host: 'localhost',
  username: 'steel',
  privateKey: '/home/steel/.ssh/id_rsa'
})
  • 2.ssh.putFile: загрузить файл
 ssh.putFile('/home/steel/Lab/localPath', '/home/steel/Lab/remotePath').then(function() {
    console.log("The File thing is done")
  }, function(error) {
    console.log("Something's wrong")
    console.log(error)
  })
  • 3.ssh.execCommand: выполнять команды удаленного сервера
 ssh.execCommand('hh_client --json', { cwd:'/var/www' }).then(function(result) {
    console.log('STDOUT: ' + result.stdout)
    console.log('STDERR: ' + result.stderr)
  })

1.2.archiver

архиватор — это пакет npm для создания архивов, в основном используемый для упаковки и создания zip, rar и т. д.

руководство пользователя:

  const archiver = require('archiver');

  // 设置压缩类型及级别
  const archive = archiver('zip', {
    zlib: { level: 9 },
  }).on('error', err => {
    throw err;
  });
  
  // 创建文件输出流
  const output = fs.createWriteStream(__dirname + '/dist.zip');
  
  // 通过管道方法将输出流存档到文件
  archive.pipe(output);
  
  // 从subdir子目录追加内容并重命名
  archive.directory('subdir/', 'new-subdir');
  
  // 完成打包归档
  archive.finalize();

1.3. План развертывания

Схема развертывания разработана следующим образом:

脚本方案

Процесс выглядит следующим образом:

  • 1. Прочтите файл конфигурации, включая хост сервера, порт, веб-каталог и локальный каталог и другую информацию.
  • 2. Местная упаковка,npm run buildСоздать дистрибутив
  • 3. Запаковать в zip, с помощью архиватора запаковать dist в dist.zip
  • 4. Подключиться к серверу,node-sshчтение конфигурации сервера подключения
  • 5. Загрузите zip, используйтеssh.putFileзагрузить dist.zip
  • 6. Разархивируйте zip, используяssh.execCommandразархивировать dist.zip
  • 7. Удалите локальный dist.zip, используйтеfs.unlinkудалить локальный dist.zip

Конкретный код:

// deploy.js

const path = require('path');
const fs = require('fs');
const childProcess = require('child_process');
const node_ssh = require('node-ssh');
const archiver = require('archiver');
const { successLog, errorLog, underlineLog } = require('../utils/index');
const projectDir = process.cwd();

let ssh = new node_ssh(); // 生成ssh实例

// 部署流程入口
function deploy(config) {
  const { script } = config;
  try {
    console.log(`\n(1)${script}`);
    childProcess.execSync(`${script}`);
    successLog('  打包成功');
    startZip(config);
  } catch (err) {
    errorLog(err);
    process.exit(1);
  }
}

// 开始打包
function startZip(config) {
  let { distPath, host } = config;
  distPath = path.resolve(projectDir, distPath);
  console.log('(2)打包成zip');
  const archive = archiver('zip', {
    zlib: { level: 9 },
  }).on('error', err => {
    throw err;
  });
  const output = fs.createWriteStream(`${projectDir}/dist.zip`).on('close', err => {
    if (err) {
      console.log('  关闭archiver异常:', err);
      return;
    }
    successLog('  zip打包成功');
    console.log(`(3)连接${underlineLog(host)}`);
    uploadFile(config);
  });
  archive.pipe(output);
  archive.directory(distPath, '/');
  archive.finalize();
}

// 上传文件
function uploadFile(config) {
  const { host, port, username, password, privateKey, passphrase, } = config;
  const sshConfig = {
    host,
    port,
    username,
    password,
    privateKey,
    passphrase
  };
  ssh.connect(sshConfig)
    .then(() => {
      successLog(`  SSH连接成功`);
      console.log(`(4)上传zip至目录${underlineLog(config.webDir)}`);
      ssh.putFile(`${projectDir}/dist.zip`, `${config.webDir}/dist.zip`)
        .then(() => {
          successLog(`  zip包上传成功`);
          console.log('(5)解压zip包');
          statrRemoteShell(config);
        })
        .catch(err => {
          errorLog('  文件传输异常', err);
          process.exit(0);
        });
    })
    .catch(err => {
      errorLog('  连接失败', err);
      process.exit(0);
    });
}

// 执行Linux命令
function runCommand(command, webDir) {
  return new Promise((resolve, reject) => {
    ssh.execCommand(command, { cwd: webDir })
      .then(result => {
        resolve();
        // if (result.stdout) {
        //   successLog(result.stdout);
        // }
        if (result.stderr) {
          errorLog(result.stderr);
          process.exit(1);
        }
      })
      .catch(err => {
        reject(err);
      });
  });
}

// 开始执行远程命令
function statrRemoteShell(config) {
  const { webDir } = config;
  const commands = [`cd ${webDir}`, 'pwd', 'unzip -o dist.zip && rm -f dist.zip'];
  const promises = [];
  for (let i = 0; i < commands.length; i += 1) {
    promises.push(runCommand(commands[i], webDir));
  }
  Promise.all(promises)
    .then(() => {
      successLog('  解压成功');
      console.log('(6)开始删除本地dist.zip');
      deleteLocalZip(config);
    })
    .catch(err => {
      errorLog('  文件解压失败', err);
      process.exit(0);
    });
}

// 删除本地dist.zip包
function deleteLocalZip(config) {
  const { projectName, name } = config;
  fs.unlink(`${projectDir}/dist.zip`, err => {
    if (err) {
      errorLog('  本地dist.zip删除失败', err);
    }
    successLog('  本地dist.zip删除成功\n');
    successLog(`\n 恭喜您,${underlineLog(projectName)}项目${underlineLog(name)}部署成功了^_^\n`);
    process.exit(0);
  });
}

module.exports = deploy;

2. Практика строительных лесов

проблема:
Вышеупомянутое решение уже может завершить автоматическое развертывание проекта, но если к автоматическому развертыванию необходимо подключить новый проект, очень сложно скопировать весь файл?

Таким образом, автоматизированное развертывание можно превратить в скаффолд.fe-deploy-cli, который поддерживает создание шаблонов конфигурации развертывания и развертывание сценариев, которые можно развернуть в соответствующей среде с помощью всего одной команды.

Пакеты npm, связанные со скаффолдингом:

  • командир: полное решение для интерфейса командной строки node.js
  • download-git-repo: загрузка кода репозитория git
  • ora: показать эффекты загрузки
  • inquirer: инструмент для взаимодействия пользователей с командами
  • child_process: встроенный модуль npm для выполненияpackage.jsonскрипт упаковки в

2.1. Инициализация

Для инициализации необходимо создать новое хранилище конфигурации развертывания git на github, выполнитьdeploy initпройти черезdownload-git-repoВытащите шаблон конфигурации из git.

// init.js

#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const download = require('download-git-repo');
const ora = require('ora');
const { successLog, infoLog, errorLog } = require('../utils/index');
let tmp = 'deploy';
const deployPath = path.join(process.cwd(), './deploy');
const deployConfigPath = `${deployPath}/deploy.config.js`;
const deployGit = 'dadaiwei/fe-deploy-cli-template';

// 检查部署目录及部署配置文件是否存在
const checkDeployExists = () => {
    if (fs.existsSync(deployPath) && fs.existsSync(deployConfigPath)) {
        infoLog('deploy目录下的deploy.config.js配置文件已经存在,请勿重新下载');
        process.exit(1);
        return;
    }
    downloadAndGenerate(deployGit);
};

// 下载部署脚本配置
const downloadAndGenerate = templateUrl => {
    const spinner = ora('开始生成部署模板');
    spinner.start();
    download(templateUrl, tmp, { clone: false }, err => {
        if (err) {
            console.log();
            errorLog(err);
            process.exit(1);
        }
        spinner.stop();
        successLog('模板下载成功,模板位置:deploy/deploy.config.js');
        infoLog('请配置deploy目录下的deploy.config.js配置文件');
        process.exit(0);
    });
};

module.exports = () => {
    checkDeployExists();
};

2.2 Настройка конфигурации

путем измененияdeploy.config.js, установите конфигурацию dev (тестовая среда) и prod (онлайн-среда).

// deploy.config.js

module.exports = {
  privateKey: '', // 本地私钥地址,位置一般在C:/Users/xxx/.ssh/id_rsa,非必填,有私钥则配置
  passphrase: '', // 本地私钥密码,非必填,有私钥则配置
  projectName: '', // 项目名称
  dev: { // 测试环境
    name: '测试环境',
    script: "npm run build", // 测试环境打包脚本
    host: '', // 测试服务器地址
    port: 22, // ssh port,一般默认22
    username: '', // 登录服务器用户名
    password: '', // 登录服务器密码
    distPath: 'dist',  // 本地打包dist目录
    webDir: '',  // // 测试环境服务器地址
  },
  prod: {  // 线上环境
    name: '线上环境',
    script: "npm run build", // 线上环境打包脚本
    host: '', // 线上服务器地址
    port: 22, // ssh port,一般默认22
    username: '', // 登录服务器用户名
    password: '', // 登录服务器密码
    distPath: 'dist',  // 本地打包dist目录
    webDir: '' // 线上环境web目录
  }
  // 再还有多余的环境按照这个格式写即可
}

2.3 Регистрация команд развертывания

Команда развертывания реестра взята изdeploy.config.jsПрочитайте конфигурацию dev и prod в , а затем передайтеprogram.commandЗарегистрируйте команду dev и prod, запуститеdeploy devилиdeploy prodТо есть войти в процесс развертывания в разделе 1.3.

// 部署流程
function deploy() {
    // 检测部署配置是否合理
    const deployConfigs = checkDeployConfig(deployConfigPath);
    if (!deployConfigs) {
        process.exit(1);
    }

    // 注册部署命令,注册后支持deploy dev和deploy prod
    deployConfigs.forEach(config => {
        const { command, projectName, name } = config;
        program
            .command(`${command}`)
            .description(`${underlineLog(projectName)}项目${underlineLog(name)}部署`)
            .action(() => {
                inquirer.prompt([
                    {
                        type: 'confirm',
                        message: `${underlineLog(projectName)}项目是否部署到${underlineLog(name)}?`,
                        name: 'sure'
                    }
                ]).then(answers => {
                    const { sure } = answers;
                    if (!sure) {
                        process.exit(1);
                    }
                    if (sure) {
                        const deploy = require('../lib/deploy');
                        deploy(config);
                    }
                });

            });
    });
}

3. Руководство пользователя

Предварительное условие: вы можете подключиться к серверу через ssh.

Применимые объекты: Небольшие группы или отдельные проекты, которые все еще используют ручное развертывание и рассчитывают быстро реализовать упрощенное развертывание.В конце концов, у крупных компаний, таких как Alibaba, есть полная платформа для внешнего развертывания.

руководство пользователя:GitHub.com/sack for/share…

3.1. Установка

npm i fe-deploy-cli -g

Проверяем версию, установка прошла успешно

版本

3.2. Инициализируйте шаблон развертывания

deploy init

初始化部署模板

Генерируется в рамках текущего проектаdeploy.config.js

生成deploy.config.js

3.3. Изменить конфигурацию развертывания

Файл конфигурации развертывания находится в папке развертывания.deploy.config.js, обычно содержатdev(тестовая среда) иprod(Онлайн-среда) Две конфигурации, и есть избыточные конфигурации среды в аналогичной форме, только одна среда может удалить другую избыточную конфигурацию (например, толькоprodОнлайн-среда, пожалуйста, удалитеdevконфигурация тестовой среды).

Для получения конкретной информации о конфигурации см. комментарии к файлу конфигурации:

module.exports = {
  privateKey: '', // 本地私钥地址,位置一般在C:/Users/xxx/.ssh/id_rsa,非必填,有私钥则配置
  passphrase: '', // 本地私钥密码,非必填,有私钥则配置
  projectName: 'hivue', // 项目名称
  dev: { // 测试环境
    name: '测试环境',
    script: "npm run build-dev", // 测试环境打包脚本
    host: '10.240.176.99', // 测试服务器地址
    port: 22, // ssh port,一般默认22
    username: 'root', // 登录服务器用户名
    password: '123456', // 登录服务器密码
    distPath: 'dist',  // 本地打包dist目录
    webDir: '/var/www/html/dev/hivue',  // // 测试环境服务器地址
  },
  prod: {  // 线上环境
    name: '线上环境',
    script: "npm run build", // 线上环境打包脚本
    host: '10.240.176.99', // 线上服务器地址
    port: 22, // ssh port,一般默认22
    username: 'root', // 登录服务器用户名
    password: '123456', // 登录服务器密码
    distPath: 'dist',  // 本地打包dist目录
    webDir: '/var/www/html/prod/hivue' // 线上环境web目录
  }
  // 再还有多余的环境按照这个格式写即可
}

3.4 Просмотр команды развертывания (этот шаг можно пропустить)

Настроеноdeploy.config.js,бегать

deploy --help

Посмотреть команды развертывания

查看部署命令

3.5 Развертывание тестовой среды

Развертывание тестовой среды используетdevКонфигурация

deploy dev

Сначала идет подтверждение, а затем начинается процесс развертывания.После того, как скрипт автоматически завершит 6-шаговую операцию, поздравляем, развертывание прошло успешно! ! !

测试环境部署

3.5. Развертывание онлайн-среды

В развертывании онлайн-среды используютсяprodКонфигурация

deploy prod

Процесс развертывания аналогичен развертыванию тестовой среды:

线上环境部署

4. Оптимизация (Дополнительно)

Вышеупомянутое реализовало автоматическое развертывание скаффолдинга и развернуло основной код.deploy.jsиспользоватьasync awaitзаменятьPromiseОптимизировано.

// deploy.js

const path = require('path');
const fs = require('fs');
const childProcess = require('child_process');
const ora = require('ora');
const node_ssh = require('node-ssh');
const archiver = require('archiver');
const { successLog, errorLog, underlineLog } = require('../utils/index');
const projectDir = process.cwd();

let ssh = new node_ssh(); // 生成ssh实例

// 部署流程入口
async function deploy(config) {
  const { script, webDir, distPath, projectName, name } = config;
  try {
    execBuild(script);
    await startZip(distPath);
    await connectSSH(config);
    await uploadFile(webDir);
    await unzipFile(webDir);
    await deleteLocalZip();
    successLog(`\n 恭喜您,${underlineLog(projectName)}项目${underlineLog(name)}部署成功了^_^\n`);
    process.exit(0);
  } catch (err) {
    errorLog(`  部署失败 ${err}`);
    process.exit(1);
  }
}

// 第一步,执行打包脚本
function execBuild(script) {
  try {
    console.log(`\n(1)${script}`);
    const spinner = ora('正在打包中');
    spinner.start();
    console.log();
    childProcess.execSync(script, { cwd: projectDir });
    spinner.stop();
    successLog('  打包成功');
  } catch (err) {
    errorLog(err);
    process.exit(1);
  }
}

// 第二部,打包zip
function startZip(distPath) {
  return new Promise((resolve, reject) => {
    distPath = path.resolve(projectDir, distPath);
    console.log('(2)打包成zip');
    const archive = archiver('zip', {
      zlib: { level: 9 },
    }).on('error', err => {
      throw err;
    });
    const output = fs.createWriteStream(`${projectDir}/dist.zip`);
    output.on('close', err => {
      if (err) {
        errorLog(`  关闭archiver异常 ${err}`);
        reject(err);
        process.exit(1);
      }
      successLog('  zip打包成功');
      resolve();
    });
    archive.pipe(output);
    archive.directory(distPath, '/');
    archive.finalize();
  });
}

// 第三步,连接SSH
async function connectSSH(config) {
  const { host, port, username, password, privateKey, passphrase, distPath } = config;
  const sshConfig = {
    host,
    port,
    username,
    password,
    privateKey,
    passphrase
  };
  try {
    console.log(`(3)连接${underlineLog(host)}`);
    await ssh.connect(sshConfig);
    successLog('  SSH连接成功');
  } catch (err) {
    errorLog(`  连接失败 ${err}`);
    process.exit(1);
  }
}

// 第四部,上传zip包
async function uploadFile(webDir) {
  try {
    console.log(`(4)上传zip至目录${underlineLog(webDir)}`);
    await ssh.putFile(`${projectDir}/dist.zip`, `${webDir}/dist.zip`);
    successLog('  zip包上传成功');
  } catch (err) {
    errorLog(`  zip包上传失败 ${err}`);
    process.exit(1);
  }
}


// 运行命令
async function runCommand(command, webDir) {
  await ssh.execCommand(command, { cwd: webDir });
}

// 第五步,解压zip包
async function unzipFile(webDir) {
  try {
    console.log('(5)开始解压zip包');
    await runCommand(`cd ${webDir}`, webDir);
    await runCommand('unzip -o dist.zip && rm -f dist.zip', webDir);
    successLog('  zip包解压成功');
  } catch (err) {
    errorLog(`  zip包解压失败 ${err}`);
    process.exit(1);
  }
}

// 第六步,删除本地dist.zip包
async function deleteLocalZip() {
  return new Promise((resolve, reject) => {
    console.log('(6)开始删除本地zip包');
    fs.unlink(`${projectDir}/dist.zip`, err => {
      if (err) {
        errorLog(`  本地zip包删除失败 ${err}`, err);
        reject(err);
        process.exit(1);
      }
      successLog('  本地dist.zip删除成功\n');
      resolve();
    });
  });
}


module.exports = deploy;

Инициализация скаффолда, параметры конфигурации, команды развертывания при регистрации и рекомендации по использованию соответствуют предыдущим версиям.

Эпилог

Выше приведена небольшая практика блоггеров по поводу front-end облегченного развертывания скаффолдинга.Если вы чувствуете, что что-то наработали, то можете обратить на это внимание, поставить лайк, скачать и пользоваться. Кодировать слова непросто, большое спасибо.

гитхаб-адрес:GitHub.com/sack for/share…(добро пожаловать, звезда, спасибо, спасибо)