Эта статья участвовала в мероприятии Haowen Convocation Order, щелкните, чтобы просмотреть:Двойные заявки на внутреннюю и внешнюю стороны, призовой фонд в 20 000 юаней ждет вас, чтобы бросить вызов!
Это 108-я оригинальная статья без воды.Если вы хотите получить больше оригинальных статей, выполните поиск в общедоступном аккаунте и подпишитесь на нас~ Эта статья была впервые опубликована в блоге Zhengcaiyun:Как создать платформу сборки и развертывания, подходящую для вашей команды
Существующие решения для сборки и развертывания в индустрии переднего плана должны широко использоваться, такие как Jenkins, Docker и GitHub Actions.Поскольку наша компания теперь сосуществует с первыми двумя решениями.Поскольку у нас уже есть стабильная конструкция и развертывание Метод, почему мы должны делать это сами? Вы хотите построить свою собственную платформу для интерфейса? Конечно не ради забавы, позвольте мне медленно разобрать причины.
Вы можете столкнуться с различными проблемами при использовании внешнего интерфейса, такими как:
- Eslint пропускает верификацию — front-end проекты в компании, во времени и на разных стадиях, проекты, созданные через старые и новые скаффолдинги, могут иметь разные стили, и правила верификации могут не обязательно быть едиными, хотя сам проект может иметь различные Eslint, Stylelint и другой перехват проверки, но не может помешать разработчикам пропустить эту проверку кода.
- Несовместимые обновления версии npm. Для некоторых проверок совместимости, необходимых для зависимых версий npm, если некоторые плагины npm внезапно обновляют некоторые несовместимые версии, об ошибках будет сообщено после того, как код будет подключен к сети, обычно совместимы все виды IE.
- Вы не можете свободно добавлять нужные вам функции — вы хотите оптимизировать процесс построения интерфейса или оптимизировать функции, удобные для использования интерфейса, но поскольку вы полагаетесь на платформу эксплуатации и обслуживания для создания приложений, если вы хотите добавить свои собственные функции, вам нужно подождать, пока другие запланируют.
И эти проблемы, если у нас будет своя строительная площадка, это не будет проблемой, так что сейчас - Юньчан.
Почему она называется «Юньчан»?Конечно, я надеюсь, что эта платформа может быть похожа на «Гуань Юньчан». Какие возможности может предоставить нам Юньчан?
способность длины облака
Сборка и развертывание
Это, конечно, необходимая базовая способность.Лидер облака обеспечивает возможность построения различных типов интерфейсных проектов компании, таких как Pampas, React, Vue, Uniapp и т. д. Весь процесс на самом деле не сложный, после начала сборки сервер облачного лидера, после получения имени проекта, ветки, среды для развертывания и другой информации для сборки, начинает обновлять код проекта, устанавливать зависимости , а затем упаковать код.Наконец, сгенерированный код перепаковывается в файл изображения, а затем изображение загружается в хранилище изображений, а некоторые статические файлы ресурсов проекта могут быть загружены в CDN, что удобно для После внешнего интерфейса вызывается служба развертывания образов K8S.
Подключаемый процесс строительства
Если вы используете чужую платформу сборки, многие функции внешнего интерфейса, которые вы хотите добавить, зависят от служб других людей для достижения, но если вы переходите в облако, вы можете предоставить открытый интерфейс, чтобы внешний интерфейс мог свободно настраивать собственные подключаемые сервисы.
Например, в процессе онлайн-конструкции и упаковки можно решить некоторые из проблем и болевых точек, упомянутых выше, например:
- Различные Eslint, Tslint и прочие проверки кода на соответствие больше не боятся быть пропущенными людьми.
- Вы также можете проверить версию пакета npm перед сборкой проекта, чтобы предотвратить ошибки совместимости после выхода кода в сеть и т. д.
- После того, как код упакован, он также может выполнять некоторую глобальную инъекцию внешних ресурсов, таких как скрытые точки, мониторинг ошибок, отправка сообщений и т. д.
Проверка процесса выпуска
Существующее управление и контроль процесса выпуска платформы компании зависит от ведения списка операций и обслуживания.Каждый проект будет управлять списком релизеров, поэтому выпуск базовых проектов требует, чтобы релизер следил за выпуском в ту ночь.Чтобы решить эту проблему проблема , которая обеспечивает концепцию потока аудита.
То есть, когда проект тестируется в среде предварительного выпуска, разработчик кода может отправить форму заявки на выпуск в режиме реального времени, а затем лицо, имеющее право на публикацию проекта, получит форму заявки, которую необходимо просмотреть через DingTalk, которая можно получить через веб-страницу. Или напрямую управлять сообщением DingTalk, чтобы согласиться или отклонить заявку на выпуск. После того, как приложение будет одобрено, разработчик кода может развернуть реальную строку выпуска проекта самостоятельно после времени выпуска. После того, как реальная строка будет выпущен, он будет создан для этого проекта позже.Запрос на слияние кода для облегчения архивирования последующих кодов.
Преимущество этого заключается в том, что, с одной стороны, внешний интерфейс может контролировать разрешения на создание и выпуск проекта, чтобы можно было собирать разрешения на публикацию, с другой стороны, он также может освободить издателя проекта, так что разработчики могут более легко запускать код, и выпуск проекта снова открыт.
Возможность экспорта
Yunchang может экспортировать некоторые возможности сборки и обновления во внешний мир, что позволяет сторонним плагинам получать доступ к процессу сборки.Мы предоставляем разработчикам плагин VsCode, так что вы можете свободно обновлять код во время Процесс разработки. Время открытия веб-страницы для построения — это сборка и обновление кода в редакторе, не выходя из дома. В общей среде предусмотрен ярлык для обновления в один клик, что еще больше экономит время работы в середине. время, напишите еще две строки. Разве код не счастливее.
Наш плагин VsCode не только предоставляет некоторые возможности построения Yunchang, но и функции создания небольших программ, поиска маршрутов и др. Если вы хотите поделиться этим плагином, ждите наших последующих статей.
облачная длинная архитектура
Как упоминалось выше, процесс построения облачного лидера основан на возможности развертывания образа, предоставляемого K8S.Клиент и сервер облачного лидера являются службами, работающими в Docker, поэтому облачный лидер использует Docker In Docker. схема, то есть упаковка образа Docker сервисами в Docker.
Для построения кода серверная часть облачного лидера вводит обработку пула процессов.Каждый проект, созданный в облачном лидере, является независимым экземпляром в пуле процессов и имеет независимый процесс упаковки, а также ход процесса упаковки. Далее следует, что он опирается на запланированный запрос задачи Redis, который реализует архитектуру многоэкземплярного параллельного построения Cloud Changer.
Связь интерфейса между длинным облачным клиентом и сервером представляет собой обычный HTTP-запрос и запрос Websocket.После того, как клиент инициирует запрос, сервер сохраняет некоторое приложение, пользователя, информацию о конструкции и другие данные через данные MySQL.
Для взаимодействия с внешними ресурсами некоторые статические ресурсы и упакованные образы будут загружены в CDN и зеркальный репозиторий в процессе сборки, а затем будет вызван интерфейс развертывания K8S для развертывания проекта.
0-1 для интерфейсных сборок
Я видел некоторые функциональные представления «Юньчан» и архитектурный дизайн «Юньчан» выше. Я полагаю, что многие друзья также хотят создать интерфейсную платформу для строительства и публикации, аналогичную «Юньчану». Что вам нужно сделать? me Давайте взглянем на конструктивные идеи основных модулей фронтальной строительной платформы.
процесс сборки
Основным основным модулем фронтальной строительной платформы должны быть сборка и упаковка.Процесс сборки и развертывания можно разделить на следующие этапы:
- После запуска каждой сборки необходимо сохранить некоторые информационные данные этой сборки, поэтому необходимо создать запись о выпуске сборки.В записи о выпуске будет храниться информация о выпуске этого выпуска, такая как имя проекта выпуска, ветка, commitId , информация о коммите, данные оператора, среда релиза, которую необходимо обновить и т. д. В это время нам понадобится таблица записи релиза сборки, и если вам нужны некоторые данные проекта и оператора, вам понадобится таблица приложения и пользовательская таблица для хранения связанных данных для ассоциации.
- После создания записи о выпуске сборки запускается процесс внешней сборки.
pipeline
процесс, процесс может относиться к следующему примеру
// 构建的流程
async run() {
const app = this.app;
const processData = {};
const pipeline = [{
handler: context => app.fetchUpdate(context), // Git 更新代码
name: 'codeUpdate',
progress: 10 // 这里是当前构建的进度
}, {
handler: context => app.installDependency(context), // npm install 安装依赖
name: 'dependency',
progress: 30
}, {
handler: context => app.check(context), // 构建的前置校验(非必须):代码检测,eslint,package.json 版本等
name: 'check',
progress: 40
}, {
handler: context => app.pack(context), // npm run build 的打包逻辑,如果有其他的项目类型,例如 gulp 之类,也可以在这一步进行处理
name: 'pack',
progress: 70
}, {
handler: context => app.injectScript(context), // 构建的后置步骤(非必须):打包后的资源注入
name: 'injectRes',
progress: 80
}, { // docker image build
handler: context => app.buildImage(context), // 生成 docker 镜像文件,镜像上传仓库,以及之后调用 K8S 能力进行部署
name: 'buildImage',
progress: 90
}];
// 循环执行每一步构建流程
for (let i = 0; i < pipeline.length; i++) {
const task = pipeline[i];
const [ err, response ] = await to(this.execProcess({
...task,
step: i
}));
if (response) {
processData[task.name] = response;
}
}
return Promise.resolve(processData);
}
// 执行构建中的 handler 操作
async execProcess(task) {
this.step(task.name, { status: 'start' });
const result = await task.handler(this.buildContext);
this.progress(task.progress);
this.step(task.name, { status: 'end', taskMeta: result });
return result;
}
- Этапы построения, некоторые из вышеперечисленных процессов построения, по сравнению со всеми, кто хочет знать, как запускать некоторые скрипты в процессе построения на стороне сервера, на самом деле идея состоит в том, чтобы пройти
node
изchild_process
Модуль выполняет сценарии оболочки, вот несколько примеров кода:
import { spawn } from 'child_process';
// git clone
execCmd(`git clone ${url} ${dir}`, {
cwd: this.root,
verbose: this.verbose
});
// npm run build
const cmd = ['npm run build', cmdOption].filter(Boolean).join(' ');
execCmd(cmd, options);
// 执行 shell 命令
function execCmd(cmd: string, options:any = {}): Promise<any> {
const [ shell, ...args ] = cmd.split(' ').filter(Boolean);
const { verbose, ...others } = options;
return new Promise((resolve, reject) => {
let child: any = spawn(shell, args, others);
let stdout = '';
let stderr = '';
child.stdout && child.stdout.on('data', (buf: Buffer) => {
stdout = `${stdout}${buf}`;
if (verbose) {
logger.info(`${buf}`);
}
});
child.stderr && child.stderr.on('data', (buf: Buffer) => {
stderr = `${stderr}${buf}`;
if (verbose) {
logger.error(`${buf}`);
}
});
child.on('exit', (code: number) => {
if (code !== 0) {
const reason = stderr || 'some unknown error';
reject(`exited with code ${code} due to ${reason}`);
} else {
resolve({stdout, stderr});
}
child.kill();
child = null;
});
child.on('error', err => {
reject(err.message);
child.kill();
child = null;
});
});
};
- И например мы хотим добавить перед сборкой
Eslint
Операция проверки также может быть добавлена к процессу построения, то есть проверка типа перехвата может быть добавлена к процессу построения онлайн для контроля качества кода построения онлайн.
import { CLIEngine } from 'eslint';
export function lintOnFiles(context) {
const { root } = context;
const [ err ] = createPluginSymLink(root);
if (err) {
return [ err ];
}
const linter = new CLIEngine({
envs: [ 'browser' ],
useEslintrc: true,
cwd: root,
configFile: path.join(__dirname, 'LintConfig.js'),
ignorePattern: ['**/router-config.js']
});
let report = linter.executeOnFiles(['src']);
const errorReport = CLIEngine.getErrorResults(report.results);
const errorList = errorReport.map(item => {
const file = path.relative(root, item.filePath);
return {
file,
errorCount: item.errorCount,
warningCount: item.warningCount,
messages: item.messages
};
});
const result = {
errorList,
errorCount: report.errorCount,
warningCount: report.warningCount
}
return [ null, result ];
};
- После завершения строительства и развертывания информация о статусе обновления этой записи строительства может быть обновлена в соответствии с ситуацией строительства.
Docker
Зеркало, после загрузки зеркального склада, в нем также нужны информационные записи, чтобы ранее построенное зеркало можно было обновить или откатить позже, поэтому необходимо добавить зеркальную таблицу, нижеследующееDocker
Некоторый пример кода, сгенерированного изображением.
import Docker = require('dockerode');
// 保证服务端中有一个基本的 dockerfile 镜像文件
const docker = new Docker({ socketPath: '/var/run/docker.sock' });
const image = '镜像打包名称'
let buildStream;
[ err, buildStream ] = await to(
docker.buildImage({
context: outputDir
}, { t: image })
);
let pushStream;
// authconfig 镜像仓库的一些验证信息
const authconfig = {
serveraddress: "镜像仓库地址"
};
// 向远端私有仓库推送镜像
const dockerImage = docker.getImage(image);
[ err, pushStream ] = await to(dockerImage.push({
authconfig,
tag
}));
// 3s 打印一次进度信息
const progressLog = _.throttle((msg) => logger.info(msg), 3000);
const pushPromise = new Promise((resolve, reject) => {
docker.modem.followProgress(pushStream, (err, res) => {
err ? reject(err) : resolve(res);
}, e => {
if (e.error) {
reject(e.error);
} else {
const { id, status, progressDetail } = e;
if (progressDetail && !_.isEmpty(progressDetail)) {
const { current, total } = progressDetail;
const percent = Math.floor(current / total * 100);
progressLog(`${id} : pushing progress ${percent}%`);
if (percent === 100) { // 进度完成
progressLog.flush();
}
} else if (id && status) {
logger.info(`${id} : ${status}`);
}
}
});
});
await to(pushPromise);
- Каждая сборка должна сохранять некоторый прогресс сборки, журналы и другую информацию, вы можете добавить таблицу журналов для сохранения журнала.
Запуск нескольких экземпляров сборки
На этом этапе процесс сборки проекта успешно завершен, но платформа сборки должна не только собирать и обновлять один проект за раз, поэтому вы можете ввести пул процессов в это время, чтобы ваша платформа сборки могла выполнять сборку. несколько проектов одновременно. .
Node
Это однопоточная модель, когда необходимо выполнить несколько независимых и трудоемких задач, только черезchild_process
Для распределения задач и повышения скорости обработки также необходимо реализовать пул процессов для управления работой нескольких процессов строительства.Идея пула процессов заключается в том, что основной процесс создает очередь задач и контролирует количество подчиненных -процессы Когда подпроцесс завершает задачу, процесс Для продолжения добавления новых дочерних процессов для управления выполнением параллельных процессов процесс реализуется следующим образом.
ProcessPool.ts Ниже приведена часть кода пула процессов, которая в основном показывает идею.
import * as child_process from 'child_process';
import { cpus } from 'os';
import { EventEmitter } from 'events';
import TaskQueue from './TaskQueue';
import TaskMap from './TaskMap';
import { to } from '../util/tool';
export default class ProcessPool extends EventEmitter {
private jobQueue: TaskQueue;
private depth: number;
private processorFile: string;
private workerPath: string;
private runningJobMap: TaskMap;
private idlePool: Array<number>;
private workPool: Map<any, any>;
constructor(options: any = {}) {
super();
this.jobQueue = new TaskQueue('fap_pack_task_queue');
this.runningJobMap = new TaskMap('fap_running_pack_task');
this.depth = options.depth || cpus().length; // 最大的实例进程数量
this.workerPath = options.workerPath;
this.idlePool = []; // 工作进程 pid 数组
this.workPool = new Map(); // 工作实例进程池
this.init();
}
/**
* @func init 初始化进程,
*/
init() {
while (this.workPool.size < this.depth) {
this.forkProcess();
}
}
/**
* @func forkProcess fork 子进程,创建任务实例
*/
forkProcess() {
let worker: any = child_process.fork(this.workerPath);
const pid = worker.pid;
this.workPool.set(pid, worker);
worker.on('message', async (data) => {
const { cmd } = data;
// 根据 cmd 状态 返回日志状态或者结束后清理掉任务队列
if (cmd === 'log') {
}
if (cmd === 'finish' || cmd === 'fail') {
this.killProcess();//结束后清除任务
}
});
worker.on('exit', () => {
// 结束后,清理实例队列,开启下一个任务
this.workPool.delete(pid);
worker = null;
this.forkProcess();
this.startNextJob();
});
return worker;
}
// 根据任务队列,获取下一个要进行的实例,开始任务
async startNextJob() {
this.run();
}
/**
* @func add 添加构建任务
* @param task 运行的构建程序
*/
async add(task) {
const inJobQueue = await this.jobQueue.isInQueue(task.appId); // 任务队列
const isRunningTask = await this.runningJobMap.has(task.appId); // 正在运行的任务
const existed = inJobQueue || isRunningTask;
if (!existed) {
const len = await this.jobQueue.enqueue(task, task.appId);
// 执行任务
const [err] = await to(this.run());
if (err) {
return Promise.reject(err);
}
} else {
return Promise.reject(new Error('DuplicateTask'));
}
}
/**
* @func initChild 开始构建任务
* @param child 子进程引用
* @param processFile 运行的构建程序文件
*/
initChild(child, processFile) {
return new Promise(resolve => {
child.send({ cmd: 'init', value: processFile }, resolve);
});
}
/**
* @func startChild 开始构建任务
* @param child 子进程引用
* @param task 构建任务
*/
startChild(child, task) {
child.send({ cmd: 'start', task });
}
/**
* @func run 开始队列任务运行
*/
async run() {
const jobQueue = this.jobQueue;
const isEmpty = await jobQueue.isEmpty();
// 有空闲资源并且任务队列不为空
if (this.idlePool.length > 0 && !isEmpty) {
// 获取空闲构建子进程实例
const taskProcess = this.getFreeProcess();
await this.initChild(taskProcess, this.processorFile);
const task = await jobQueue.dequeue();
if (task) {
await this.runningJobMap.set(task.appId, task);
this.startChild(taskProcess, task);
return task;
}
} else {
return Promise.reject(new Error('NoIdleResource'));
}
}
/**
* @func getFreeProcess 获取空闲构建子进程
*/
getFreeProcess() {
if (this.idlePool.length) {
const pid = this.idlePool.shift();
return this.workPool.get(pid);
}
return null;
}
/**
* @func killProcess 杀死某个子进程,原因:释放构建运行时占用的内存
* @param pid 进程 pid
*/
killProcess(pid) {
let child = this.workPool.get(pid);
child.disconnect();
child && child.kill();
this.workPool.delete(pid);
child = null;
}
}
Build.ts
import ProcessPool from './ProcessPool';
import TaskMap from './TaskMap';
import * as path from 'path';
// 日志存储
const runningPackTaskLog = new TaskMap('fap_running_pack_task_log');
//初始化进程池
const packQueue = new ProcessPool({
workerPath: path.join(__dirname, '../../task/func/worker'),
depth: 3
});
// 初始化构建文件
packQueue.process(path.join(__dirname, '../../task/func/server-build'));
let key: string;
packQueue.on('message', async data => {
// 根据项目 id,部署记录 id,以及用户 id 来设定 redis 缓存的 key 值,之后进行日志存储
key = `${appId}_${deployId}_${deployer.userId}`;
const { cmd, value } = data;
if(cmd === 'log') { // 构建任务日志
runningPackTaskLog.set(key,value);
} else if (cmd === 'finish') { // 构建完成
runningPackTaskLog.delete(key);
// 后续日志可以进行数据库存储
} else if (cmd === 'fail') { // 构建失败
runningPackTaskLog.delete(key);
// 后续日志可以进行数据库存储
}
// 可以通过 websocket 将进度同步给前台展示
});
//添加新的构建任务
let [ err ] = await to(packQueue.add({
...appAttrs, // 构建所需信息
}));
После обработки многопроцессного строительства с помощью пула процессов, как записывать ход строительства каждого процесса?Я предпочитаю использовать базу данных Redis для кэширования состояния хода строительства и синхронизировать отображение хода выполнения на переднем плане через Websocket. завершено, перейдите к разделу Локальное хранение журналов. Вышеприведенный код кратко представляет реализацию и использование пула процессов. Конечно, конкретное приложение зависит от ваших собственных дизайнерских идей. С помощью пула процессов остальные идеи фактически реализуются конкретными кодами.
Будущее фасадного строительства
И, наконец, давайте поговорим о некоторых наших идеях относительно будущего начального проектирования. Прежде всего, начальное проектирование должно обеспечивать более стабильную конструкцию. При условии стабильности мы можем добиться более быстрого построения. Для направления CI/CD , такие как более полная конструкция Smooth, после обновления сгенерированной онлайн-среды автоматически обрабатывается архивация кода, последний мастер-код повторно объединяется в каждую ветку разработки после архивации, а затем обновляются все тестовые среды и так далее. на.
Что касается производительности сервера, мы рассмотрели, можно ли полагаться на способность облачного построения полагаться на каждый компьютер разработки для достижения локального построения, оффшорного облачного построения облачного развертывания и распределения нагрузки на сервер на соответствующие компьютеры. , так что это также может снизить нагрузку на серверную конструкцию, и серверу нужно только выполнить окончательное развертывание.
Например, наши одноклассники развития хотят, чтобы проект упаковывал пакет по измерению группы. В выпущенной версии выберите филиал Project и Version, чтобы обновить релиз вместе, единое обновление выпуска.
резюме
Таким образом, с вашей собственной платформой для создания и выпуска вы можете управлять всеми функциями, которые вам нужны, и вы можете выполнять все виды функций, которые вы хотите, на интерфейсе. Думаю многим студентам может быть интересен сделанный нами плагин VsCode.Помимо построения проектов, конечно есть и некоторые другие функции, такие как управление тестовыми аккаунтами компании, быстрое построение небольших программ и другие вспомогательные функции разработки , вы хотите? Чтобы узнать больше о функциях этого плагина, пожалуйста, с нетерпением ждите нашего обмена в будущем.
Справочная документация
документация узла child_process
Глубокое понимание процессов и потоков Node.js
Анализ узлового процесса и потока
Рекомендуемое чтение
Самая знакомая незнакомка rc-форма
Возможности Vite и частичный анализ исходного кода
Serverless Custom (Container) Runtime
работы с открытым исходным кодом
- Zhengcaiyun интерфейсный таблоид
адрес с открытым исходным кодомwww.zoo.team/openweekly/(На главной странице официального сайта таблоида есть группа обмена WeChat)
Карьера
ZooTeam, молодая, увлеченная и творческая команда, связанная с отделом исследований и разработок продукции Zhengcaiyun, базируется в живописном Ханчжоу. В настоящее время в команде более 40 фронтенд-партнеров, средний возраст которых составляет 27 лет, и почти 30% из них — инженеры полного стека, настоящая молодежная штурмовая группа. В состав членов входят «ветераны» солдат из Ali и NetEase, а также первокурсники из Чжэцзянского университета, Университета науки и технологий Китая, Университета Хандянь и других школ. В дополнение к ежедневным деловым связям, команда также проводит технические исследования и фактические боевые действия в области системы материалов, инженерной платформы, строительной платформы, производительности, облачных приложений, анализа и визуализации данных, а также продвигает и внедряет ряд внутренних технологий. Откройте для себя новые горизонты передовых технологических систем.
Если вы хотите измениться, вас забросали вещами, и вы надеетесь начать их бросать; если вы хотите измениться, вам сказали, что вам нужно больше идей, но вы не можете сломать игру; если вы хотите изменить , у вас есть возможность добиться этого результата, но вы не нужны; если вы хотите изменить то, чего хотите достичь, вам нужна команда для поддержки, но вам некуда вести людей; если вы хотите изменить установившийся ритм, это будет "5 лет рабочего времени и 3 года опыта работы"; если вы хотите изменить исходный Понимание хорошее, но всегда есть размытие того слоя оконной бумаги.. , Если вы верите в силу веры, верьте, что обычные люди могут достичь необыкновенных вещей, и верьте, что они могут встретить лучшего себя. Если вы хотите участвовать в процессе становления бизнеса и лично способствовать росту фронтенд-команды с глубоким пониманием бизнеса, надежной технической системой, технологиями, создающими ценность, и побочным влиянием, я думаю, что мы должны говорить. В любое время, ожидая, пока вы что-нибудь напишете, отправьте это наZooTeam@cai-inc.com