Внедрение автоматического развертывания микрофронтенда qiankun+docker+nginx с помощью gitlab-ci/cd

Архитектура внешний интерфейс

Рекомендуемые статьи в прошлом

В этой статье вы узнаете, как создать веб-сайт для автоматического развертывания с разделением интерфейсной и серверной части (gitlab-cicd+docker+vue+django), полный галантереи!

Для начала работы с Docker достаточно этой статьи

Введение в стек технологий

  • микро интерфейс
  • qiankun
  • dockerЕсли вы этого не понимаете, пожалуйста, сначала прочитайте мое предыдущее введение, и вы поймете с первого взгляда.
  • gitlab-ci/cdВот знание автоматизированного развертывания, вы можете узнать об этом
  • nginx

Рекомендуется к сотрудничествуВидео объяснениепонимать быстрее

Что такое микрофронтенд

Микрофронтенд — это технические средства и стратегия методов, позволяющая нескольким командам совместно создавать современные веб-приложения путем независимой публикации функций.

Архитектура микроинтерфейса имеет следующие основные ценности:

  • Независимость от стека технологий Основной фреймворк не ограничивает технологический стек приложения доступа, а микроприложение имеет полную автономию.

  • Независимая разработка, независимое развертывание Хранилище микроприложений является независимым, а интерфейс и серверная часть могут разрабатываться независимо.После завершения развертывания основной фреймворк автоматически завершает обновление синхронизации.

  • Инкрементное обновление

    Перед лицом различных сложных сценариев нам обычно сложно полностью обновить или рефакторить существующую систему, а микро-фронтенд — очень хорошее средство и стратегия для реализации инкрементного рефакторинга.

  • При самостоятельном беге Изоляция состояния между каждым микроприложением, состояние выполнения не передается

что такое цянькунь

qiankun – это готовый к работе фреймворк микроинтерфейса. Он основан на single-spa и обладает возможностями, необходимыми для систем микроинтерфейса, такими как песочница js, изоляция стилей, загрузчик HTML и предварительная загрузка. qiankun можно использовать в любой среде js, а доступ к микроприложениям так же прост, как встраивание системы iframe.

основная философия дизайна qiankun

Справочный адрес:qiankun.umijs.org/zh/guide

  • Простой

    Поскольку микроприложение основного приложения может быть независимым от стека технологий, qiankun — это просто jQuery-подобная библиотека для пользователей.Вам необходимо вызвать несколько API-интерфейсов qiankun, чтобы завершить преобразование микроинтерфейса приложения. В то же время, благодаря дизайну HTML-записи и песочницы qiankun, доступ к микроприложениям так же прост, как с помощью iframe.

  • Независимость от развязки / технического стека

    Основная цель микроинтерфейса — разобрать монолитное приложение на несколько слабосвязанных микроприложений, которые могут быть автономными, и многие проекты qiankun придерживаются этого принципа, например, запись HTML, песочница и взаимодействие между приложениями. Только таким образом микроприложения могут действительно развиваться и работать независимо.

Почему бы не использовать iframe

Справочный адрес:Woohoo.Yuque.com/Теряя его О, да/Го Кэйин 7…

Если не учитывать проблемы с опытом, iframes — почти идеальное решение для микроинтерфейса.

Самая большая особенность iframe заключается в том, чтобы обеспечить собственное решение жесткой изоляции браузера, будь то изоляция стиля, изоляция js и другие проблемы, которые могут быть идеально решены. Но его самая большая проблема заключается в том, что его изоляция не может быть нарушена, что приводит к невозможности совместного использования контекста между приложениями и, как следствие, к опыту разработки и опыту работы с продуктом.

  1. URL-адрес не синхронизирован. Состояние URL-адреса обновления браузера iframe теряется, кнопки «Назад» и «Вперед» использовать нельзя.
  2. Пользовательский интерфейс не синхронизирован, структура DOM не используется совместно. Представьте всплывающее окно со слоем маски в iframe в правом нижнем углу экрана, при этом мы требуем, чтобы всплывающее окно отображалось в центре браузера и автоматически центрировалось при открытии браузера. изменен размер.
  3. Глобальный контекст полностью изолирован, а переменные памяти не используются совместно. Для обеспечения связи и синхронизации данных внутренней и внешней систем iframe файл cookie основного приложения должен быть прозрачно передан в подприложения с разными корневыми доменными именами для достижения эффекта отсутствия входа в систему.
  4. медленный. Каждая запись подприложения — это процесс перестройки контекста браузера и перезагрузки ресурсов.

Некоторые проблемы решить легко (Задача 1), на некоторые проблемы мы можем закрыть глаза (Задача 4), а некоторые проблемы нам решить сложно (Задача 3) или даже невозможно решить (Задача 2). и эти неразрешимые проблемы принесут очень серьезные проблемы с опытом работы с продуктом, что в конечном итоге привело к тому, что мы отказались от решения iframe.

Основная ценность микрофронтенда

Woohoo.Yuque.com/Теряя его О, да/Го Кэйин 7…

идея проекта

Прежде чем говорить о конкретной технической реализации, давайте посмотрим, чего мы хотим достичь.

Схема микроинтерфейса

image-20210705141310079.pngОсновное приложение отвечает за управление статусом входа и отображением навигации.

Дочерние приложения загружаются динамически на основе кликов по навигации основного приложения.

Логика развертывания

Есть много идей для развертывания, я расскажу о том, как пробовал здесь:

  • Используйте только один контейнер nginx, разверните несколько приложений, прослушивая разные порты, а затем добавьте соответствующий прокси-сервер маршрутизации в подприложение в порт основного приложения.

    Этот метод является самым простым, но не подходит для автоматического развертывания gitlab-ci/cd, поэтому я просто изначально протестировал реализацию микрофронтенда развертывания nginx.

  • Nginx использует множество контейнеров, каждый контейнер подвергается порту, затем соответствующий агент маршрутизации добавляется к подприложению главным приложением.

    Этот метод может быть реализован, но он будет открывать несколько портов на сервере, безопасность будет снижена, а внешние также могут напрямую обращаться к подприложению через порт.

  • Используйте несколько контейнеров nginx, открывайте только порт основного приложения, основное приложение подключается к подприложению, а затем получает доступ через прокси-сервер nginx.

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

image-20210705142948593.png

qiankun

установить цянькунь

$ yarn add qiankun # 或者 npm i qiankun -S

Зарегистрируйте микроприложение в основном приложении

import { registerMicroApps, addGlobalUncaughtErrorHandler, start } from 'qiankun';

const apps = [
  {
    name: 'ManageMicroApp',
    entry: '/system/', // 本地开发的时候使用 //localhost:子应用端口
    container: '#frame',
    activeRule: '/manage',
  },
]

/**
 * 注册微应用
 * 第一个参数 - 微应用的注册信息
 * 第二个参数 - 全局生命周期钩子
 */
registerMicroApps(apps,{
  // qiankun 生命周期钩子 - 微应用加载前
  beforeLoad: (app: any) => {
    console.log("before load", app.name);
    return Promise.resolve();
  },
  // qiankun 生命周期钩子 - 微应用挂载后
  afterMount: (app: any) => {
    console.log("after mount", app.name);
    return Promise.resolve();
  },
});

/**
 * 添加全局的未捕获异常处理器
 */
addGlobalUncaughtErrorHandler((event: Event | string) => {
  console.error(event);
  const { message: msg } = event as any;
  // 加载失败时提示
  if (msg && msg.includes("died in status LOADING_SOURCE_CODE")) {
    console.error("微应用加载失败,请检查应用是否可运行");
  }
});

start();

После регистрации информации о микроприложении, как только URL-адрес браузера изменится, будет автоматически запущена логика сопоставления qiankun, и все микроприложения, соответствующие правилу activeRule, будут вставлены в указанный контейнер, а микро- приложения будут вызываться по очереди для раскрытия ловушек жизненного цикла.

Если микроприложение не связано напрямую с маршрутом, вы также можете вручную загрузить микроприложение:

import { loadMicroApp } from 'qiankun';


loadMicroApp({
  name: 'app',
  entry: '//localhost:7100',
  container: '#yourContainer',
});

Микро приложение

Микроприложения могут получить доступ к основному приложению qiankun без установки каких-либо дополнительных зависимостей.

1. Экспортируйте соответствующие хуки жизненного цикла

Микро-приложения необходимо экспортировать в свою собственную запись js (обычно запись js вашего настроенного веб-пакета)bootstrap,mount,unmountТри хука жизненного цикла для вызова основного приложения в нужное время.

import Vue from 'vue';
import VueRouter from 'vue-router';

import './public-path';
import App from './App.vue';
import routes from './routes';
import SharedModule from '@/shared'; 

Vue.config.productionTip = false;

let instance = null;
let router = null;
// 如果子应用独立运行则直接执行render
if (!window.__POWERED_BY_QIANKUN__) {
  render();
}

/**
 * 渲染函数
 * 主应用生命周期钩子中运行/子应用单独启动时运行
 */
function render(props = {}) {
  // SharedModule用于主应用于子应用的通讯
  // 当传入的 shared 为空时,使用子应用自身的 shared
  // 当传入的 shared 不为空时,主应用传入的 shared 将会重载子应用的 shared
  const { shared = SharedModule.getShared() } = props;
  SharedModule.overloadShared(shared);

  router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? '/manage/' : '/',
    mode: 'history',
    routes
  });

  // 挂载应用
  instance = new Vue({
    router,
    render: (h) => h(App)
  }).$mount('#app');
}

/**
 * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
 * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
 */
export async function bootstrap() {
  console.log('vue app bootstraped');
}
/**
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
export async function mount(props) {
  console.log('vue mount', props);
  render(props);
}
/**
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async function unmount() {
  console.log('vue unmount');
  instance.$destroy();
  instance = null;
  router = null;
}
/**
 * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
 */
export async function update(props) {
  console.log('update props', props);
}

Приведенный выше код также ссылается наpublic-pathдокумент:

if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

В основном это решает проблему некорректных адресов, таких как скрипты, стили и изображения, динамически загружаемые микроприложениями.

2. Настройте инструмент упаковки для микроприложения.

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

веб-пакет:

const packageName = require('./package.json').name;


module.exports = {
  publicPath: '/system/', //这里打包地址都要基于主应用的中注册的entry值
  output: {
    library: 'ManageMicroApp', // 库名,与主应用注册的微应用的name一致
    libraryTarget: 'umd', // 这个选项会尝试把库暴露给前使用的模块定义系统,这使其和CommonJS、AMD兼容或者暴露为全局变量。
    jsonpFunction: `webpackJsonp_${packageName}`,
  },
};

Резюме ключевых моментов

  • Конфигурация при регистрации основного приложения

    const apps = [
      {
        name: 'ManageMicroApp',
        entry: '/system/',  // http://localhost/system/ 这里会通过nginx代理指向对应的子应用地址
        container: '#frame',
        activeRule: '/manage',
      },
    ]
    

    Когда основное приложение регистрирует микроприложение,entryможет быть относительным путем,activeRuleне может иentryТо же самое (иначе главная страница приложения обновляется и оно становится микроприложением)

  • основа маршрутизации vue

    router = new VueRouter({
      base: window.__POWERED_BY_QIANKUN__ ? '/manage/' : '/',
      mode: 'history',
      routes
    });
    

    Если он вызывается основным приложением, база маршрута/manage/

  • конфигурация упаковки webpack

    module.exports = {
      publicPath: '/system/',
    };
    

    дляwebpackвстроенные микроприложения, микроприложенияwebpackупакованныйpublicPathнеобходимо настроить как/system/, иначе микроприложениеindex.htmlможет запросить правильно, но микроприложенияindex.htmlвнутриjs/cssпуть не пройдет/system/.

На этом мы завершили настройку микрофронтенда, и следующим шагом будет настройка nginx.

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

Сначала повесьте конфигурацию nginx основного приложения.

    server {
        listen       80;
        listen       [::]:80 default_server;
        server_name  localhost;
        root         /usr/share/nginx/html;

        location / {
            try_files $uri $uri/ /index.html;
            index index.html;
        }
				# 前面我们配置的子应用entry是/system/,所以会触发这里的代理,代理到对应的子应用
        location /system/ {
    				 # -e表示只要filename存在,则为真,不管filename是什么类型,当然这里加了!就取反
             if (!-e $request_filename) {
                proxy_pass http://192.168.1.2; # 这里的ip是子应用docker容器的ip
             }
    				 # -f filename 如果 filename为常规文件,则为真
             if (!-f $request_filename) {
                proxy_pass http://192.168.1.2;
             }
             # docker运行的nginx不识别localhost的 所以这种写法会报502
             # proxy_pass  http://localhost:10200/;
             proxy_set_header Host $host;
         }

        location /api/ {
            proxy_pass http://后台地址IP/;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header REMOTE-HOST $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }

Взгляните на под-приложение

server {
    listen       80;
    listen       [::]:80 default_server;
    server_name  _2;
    root         /usr/share/nginx/html;

    # 这里必须加上允许跨域,否则主应用无法访问
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
    add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';

    location / {
        try_files $uri $uri/ /index.html;
        index index.html;
    }

    location /api/ {
        proxy_pass http://后台地址IP/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header REMOTE-HOST $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    error_page 404 /404.html;
        location = /40x.html {
    }

    error_page 500 502 503 504 /50x.html;
        location = /50x.html {
    }
}

конфигурация докерфайла

Вот посмотрите на под-приложение

# 直接使用nginx镜像
FROM nginx
# 把上面配置的conf文件替换一下默认的
COPY nginx.conf /etc/nginx/nginx.conf
# nginx默认目录下需要能看见index.html文件
COPY dist/index.html /usr/share/nginx/html/index.html
# 再回头看一下部署逻辑图和qiankun注意点,必须要把所有的资源文件放到system文件下index.html才能正确加载
COPY dist /usr/share/nginx/html/system

Посмотрите еще раз на основное приложение

# 这里主应用没有直接使用nginx,因为nginx反向代理的/api/会出现404的问题,原因未知!
FROM centos
# 安装nginx
RUN yum install -y nginx
# 跳转到/etc/nginx
WORKDIR /etc/nginx
# 替换配置文件
COPY nginx.conf nginx.conf
# 跳转到/usr/share/nginx/html
WORKDIR /usr/share/nginx/html
# 主应用正常打包,所以直接把包放进去就行
COPY dist .
# 暴露80端口
EXPOSE 80
# 运行nginx
CMD nginx -g "daemon off;"

конфигурация gitlab-ci/cd

Давайте сначала посмотрим на приложение,только суть

image: node

stages:
  - install
  - build
  - deploy
  - clear

cache:
  key: modules-cache
  paths:
    - node_modules
    - dist

安装环境:
  stage: install
  tags:
    - vue
  script:
    - npm install yarn
    - yarn install

打包项目:
  stage: build
  tags:
    - vue
  script:
    - yarn build

部署项目:
  stage: deploy
  image: docker
  tags:
    - vue
  script:
  	# 通过dockerfile构建项目的镜像
    - docker build -t rainbow-system .
    # 如果存在之前创建的容器先删除
    - if [ $(docker ps -aq --filter name=rainbow-admin-system) ];then docker rm -f rainbow-admin-system;fi
    # 通过刚刚的镜像创建一个容器 给容器指定一个网卡rainbow-net,这个网卡是我们自定义,创建方式后面会说,然后给定一个ip
    - docker run -d --net rainbow-net --ip 192.168.1.2 --name rainbow-admin-system rainbow-system

清理docker:
  stage: clear
  image: docker
  tags:
    - vue
  script:
    - if [ $(docker ps -aq | grep "Exited" | awk '{print $1 }') ]; then docker stop $(docker ps -a | grep "Exited" | awk '{print $1 }');fi
    - if [ $(docker ps -aq | grep "Exited" | awk '{print $1 }') ]; then docker rm $(docker ps -a | grep "Exited" | awk '{print $1 }');fi
    - if [ $(docker images | grep "none" | awk '{print $3}') ]; then docker rmi $(docker images | grep "none" | awk '{print $3}');fi

Снова посмотрите на основное приложение, исключите повторение и сосредоточьтесь непосредственно на ключевых моментах.

部署项目:
  stage: deploy
  image: docker
  tags:
    - vue3
  script:
    - docker build -t rainbow-admin .
    - if [ $(docker ps -aq --filter name=rainbow-admin-main) ];then docker rm -f rainbow-admin-main;fi
    # 给容器指定一个网卡rainbow-net,然后给定一个ip,然后通过--link与之前创建的子应用连通,重点!
    - docker run -d -p 80:80 --net rainbow-net --ip 192.168.1.1 --link 192.168.1.2 --name rainbow-admin-main rainbow-admin

Вышеупомянутая пользовательская сетевая карта докера, сгенерированная команда выглядит следующим образом:

$ docker network create --driver bridge --subnet 192.168.0.0/16 --gateway 192.168.0.1 rainbow-net

Суммировать

До сих пор мы реализовали автоматическое развертывание qiankun+docker и gitlab-ci/cd.Мы столкнулись со многими ямами, а затем нашли относительно разумное решение.Если у вас есть какие-либо вопросы, добро пожаловать в обсуждение.