NextJS学习-视频会议项目-virtual-event-starter-kit项目运行环境配置,以及打包成Docker镜像

420 阅读14分钟

文章目的

通过探究virtual-event-starter-kit项目,学习NextJS

部署环境

在本地跑代码的时候需要预先配置好一些东西,如下。

配置github授权

GitHub OAuth 第三方登录示例教程

如下图所示位置,可以获取到到ClientID、ClientSecret分别赋值给.env.local中的NEXT_PUBLIC_GITHUB_OAUTH_CLIENT_ID、GITHUB_OAUTH_CLIENT_SECRET变量。

部署到Vercel

项目中使用了100ms、DATOCMS,在运行项目前需要先配置好他们,按照项目github中的readme把项目部署到Vercel中即可。
无脑部署,在部署过程中可能会要求设置100ms,按照要求设置即可(在这个过程中需要登录100ms、DATOCMS,用自己的github账号注册、登录即可)。 image.png 可用参考: 部署到vercel一个后端工程师对前端云 Vercel 的体验和探索

获取配置值: 项目成功部署到Vercel后,从下图为之获取环境变量值,在.env.local文件中设置相同环境变量值。 image.png

image.png

DATOCMS

(我对DATOCMS的印象,可用来管理数据,有点类似MySQL,只不过访问方式是http请求。)
前面成功部署到Vercel后,就可以在DATOCMS中看到对应工程配置,如下图,里面存有项目需要的数据。其中Read-only API token就对应着项目的.env.local中的DATOCMS_READ_ONLY_API_TOKEN变量值,也是前面所述的部署Vercel成功后的获取配置值中的一个。
在DATOCMS中,可以从Obsah中看到项目所需的数据。
关于DATOCMS的教程可参考:Next.js+Headless CMS + GraphQL - Next.js tutorial for beginners(part2)-YouTub

与100ms的连接

浏览器输入http://localhost:3000/stage/a,Join后没有反应。

可以从项目关于HMS的readme出发得到解决策略。

(1)Token值的正确获取。
当用户输入/stage/a时,会去100ms获取token值(见components/hms/lib/getToken.ts),之后与100ms的交互要带着该token。
从源码来看,请求token的URL对应.env.local中的变量NEXT_PUBLIC_HMS_TOKEN_ENDPOINT(也是前面所述的部署Vercel成功后的获取配置值中的一个),该URL对应着平台上的Token Endpoint如下图

(2)设置正确的Template和Roles。
开发者可以按照Templates and RolesHMS的readme中的指引设置正确的Template和Roles,项目中需要正确设置的Roles有viewer对应/stage/astage对应/stage/a?role=stagebackstage对应/stage/a?role=backstage见项目原代码components/hms/Room.tsxcomponents/hms/lib/getToken.ts,不同的Roles对应不同的使用者权限,其设置如下图:
在设置Roles之后,需要基于template需要创建Room,如下图所示,创建的Room中有对应的Roles有:

(3)在DateCMS中设置正确的ROOM ID
平台设置好ROOM后,可以获取正确的ROOM ID如653f760ddfa3214e11aff413f4,这时就要将该ROOM ID设置在DateCMS下 Stage => Live Stage => room id,如下图:

本地安装Redies

Redies的配置见本项目的Database Providers
按照Redies官网,无脑安装即可,见install-redis,在安装过程中可能会询问你哪些ip可以访问该redis,我设置成仅本地即127.0.0.1,安装成功后。
安装成功后,需要配置项目.env.local中的变量 。

redies安装、本地启动后默认端口是6379,.env.local中的REDIS_URL设置成localhost并不起作用,需要设置成127.0.0.1,设置后运行yarn dev重启项目,在首页注册后,起一个终端运行redis-cli,输入keys *可以看到有如下图值。

配置standalone

Next.js带有standalone配置,可以使用Next.js的“输出文件跟踪”功能减少部署项目时的规模(大小),,见如何使用“输出文件跟踪”功能将NextJs (SSR)部署到Azure?,但要求next.js版本是12.2.x以上。当前使用的是Next.js的版本是12.0.10,需要进行升级yarn add next@12, 之后运行yarn build报错Can't resolve 'react-dom/client...,本项目解决策略是升级react、react-dom,yarn add react@18 react-dom@18,最终next由12.0.10升级到12.3.4,react由18.0.0-rc.0升级到18.2.0,react-dom由18.0.0-rc.0升级到18.2.0
升完级后,next.config.js中添加standalone,如下所示:

module.exports = {
  output: 'standalone',
  images: {
    domains: [
      'www.datocms-assets.com',
      'a.storyblok.com',
      'images.ctfassets.net',
      'images.prismic.io',
      'cdn.aglty.io',
      'localhost' // For Strapi
    ],
    imageSizes: [24, 64, 300]
  },
};

Next.js官网关于standalone的介绍:Automatically Copying Traced Files

部署项目到阿里云

云主机购买与配置

购买阿里云:阿里云主机购买及配置Linux服务器(centos7.5)详细步骤(建议买成最低的配置、按量付费的)
配置安全组:阿里云配置安全组(如果不知道该配置哪些端口,入方向、出方向都可以配置成全部的,但为了安全仅在调试代码时才开机)

image.png

安装环境:CentOS 7.2 下安装配置Node.js和Yarn

注意:在阿里云中启动项目,可能会遇到next.js的SWC无法编译的问题,这时需要切换到babel,在项目根目录下创建一个.babelrc文件,文件内容{ "presets": ["next/babel"] }。 见参考Unsupported FeaturesOpted out of SWC feedback #30174

更改github授权

github授权整体流程见本文前面的章节。这里由于部署到了阿里云,因此要更改Authorization callback URL,由localhost:3000更改到阿里云服务器ECS的公共IP地址。
云服务器ECS的公共IP: image.png

允许网站打开摄像头和麦克风

问题:由于浏览器限制只能由localhost或https才可以访问摄像头见navigator.mediaDevices is undefined
解决策略:

  • 策略1:手动解禁浏览器的限制。
  • 策略2:给网站配置https域名。

由于申请到域名后要进行IPC备案,本文最终采用手动解禁浏览器的限制的策略。

手动解禁浏览器的限制

解禁策略见:如何让Chrome浏览器允许http网站打开摄像头和麦克风

注意在chrome://flags/中更改完配置后,需要点击Relaunch按钮更新下才能生效。

本文为了每次访问时不再输入端口号,在阿里云中安装了nginx,在文件/etc/nginx/nginx.conf中把80端口重定向到了3000端口,配置见下图(项目启动端口默认是3000,注意阿里云的安全组要配置入方向80端口可以访问)。

image.png

配置https域名

不行呀,所有的都配置好后,访问网站时,提示要进行IPC备案才能访问网站,备案需要很多证书,而且审核时间要好几天,就没有动力再研究下去了。

这里只记录到IPC备案。

配置阿里云ECS的https域名,流程如下:
(1)首先要有域名,这里推荐在阿里云里申请一个(找最便宜的一个就可以),一定要实名认证否则配置不了DNS,具体流程可以在网上搜一下,教程有很多。

域名申请成功后一定要同步DNS如下图,否则域名解析的时候会一直是域名的DNS信息未查询到的状态,如下图。

也有免费申请域名的,但感觉不太靠谱:免费域名与阿里云服务器绑定

(2)配置DNS。
完成DNS配置后,用户就可以通过域名来进行访问了(切记是http+域名请求)。
配置DNS的流程如下图所示,点击解析按钮跳转到 云解析DNS/域名解析/解析设置,添加ipv4的记录:



(3)参考阿里云域名配置HTTPS阿里云免费SSL证书申请入口及流程(白嫖指南)教程配置HTTPS。
补充:

(4)阿里云的IPC备案。
进行备案前要准备好材料,而且备案审核时间有些长,建议开发者在上线之前就申请好IPC备案。
阿里云怎么备案的可以见:IPC备案

ICP备案前准备概述来看ECS实例备案还有门槛,如下图。

VSCode远程连接阿里云主机

远程连接后,开发很爽。。。。。
可以先把项目代码放在阿里云主机上,在云主机上配置好运行环境,之后使用本地VSCode远程连接云主机,本地VSCode可以直接修改云主机上的代码,本地VSCode也是利用云主机环境运行代码。
可参考:使用VS Code连接远程服务器真香!使用VSCode远程开发调试
连接成功后,如下图,可以看到VSCode打开的文件、控制台的环境都是远程主机的,此时修改文件可直接保存到远程主机上。

配置Docker

Docker安装与阿里云官方镜像加速

参考阿里云教程即可完成:

Dockerfile与镜像

next.js官网教程:Deploying-Docker Image
Dockerfile知识补充:使用Dockerfile定制镜像Dockerfile指令详解

(1) Dockerfile
next.js官网所给的Dockerfile示例见examples/with-docker/Dockerfile,本项目参考该Dockerfile示例进行修改,最终使用的Dockerfile如下:

FROM node:14.21.3-alpine AS base

# Install dependencies only when needed
FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app

# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
  if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
  elif [ -f package-lock.json ]; then npm ci; \
  elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
  else echo "Lockfile not found." && exit 1; \
  fi

# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1

RUN yarn build

# If using npm comment out above and use below instead
# RUN npm run build

# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app

ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next

# Copy the scripts which run to set process.env.* and etc before the server.js
COPY --from=builder --chown=nextjs:nodejs /app/entrypoint.sh ./entrypoint.sh
COPY --from=builder --chown=nextjs:nodejs /app/scripts ./scripts
COPY --from=builder --chown=nextjs:nodejs /app/.env.local ./.env.local

# set the entrypoint.sh can be exec for its owner, here is nextjs can exec it.
RUN chmod 766 ./entrypoint.sh

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT 3000
# set hostname to localhost
ENV HOSTNAME "0.0.0.0"

ENTRYPOINT ["./entrypoint.sh"]

CMD ["node", "server.js"]

对于本Dockerfile,相关的有:

  • 可以看出其将编译环境和运行环境进行了分离,减少了最终构建出的镜像的体积,见:Dockerfile多From用法(非常值得一看)

  • 针对DockerFile中From的基础镜像,可以从DockerHub查询,如本项目中使用的是node:14.21.3-alpine。考虑到不同node版本体积的大小(如下图),这里采用alpine版本,不同node镜像的区别可见nodejs/docker-node,同时To add the missing shared libraries to your image, adding the libc6-compat package in your Dockerfile is recommended: RUN apk add --no-cache libc6-compat

  • Dockerfile在runner阶段,使用命令RUN adduser --system --uid 1001 nextjs创建了用户nextjs,该用户属于系统用户,其无法登录、权限较小,最为安全,可参考Linux系统中的超级用户,普通用户,特殊用户(特殊用户)3种类型

  • 建议在构建镜像前,弄清楚Context的概念,可参考镜像构建上下文(Context)

  • 本文使用Next.js的“输出文件跟踪”功能减少部署项目时的规模,即standalone,但在运行生成的镜像时,发现.env.local中配置的环境变量并没有生效,因此最终决定自己写脚本来配置环境变量。参考的有Next.js+Docker+Github Actions实现自动部署next.js/discussions…

  1. 脚本entrypoint.sh如下,执行./scripts/envConfig.js,其作为Dockerfile的ENTRYPOINT,在ENTRYPOINT之前,还需要给系统用户nextj赋权RUN chmod 766 ./entrypoint.sh,使nextjs有权限执行entrypoint.sh文件。同时需要注意,在Dockerfile中当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令,见ENTRYPOINT入口点。本文采用Execute a script before CMD中的策略,通过在entrypoint.sh的最后一行加入exec "$@"使ENTRYPOINT后的CMD生效。
#!/bin/sh
# entrypoint.sh文件
# 设置环境变量
node ./scripts/envConfig.js

# Hand off to the CMD
# cf https://stackoverflow.com/questions/42857897/execute-a-script-before-cmd
exec "$@"
  1. scripts/envConfig.js文件内容如下,其使用了dotenv包,本项目已经安装了dotenv@8.6.0,可以直接用。如果项目中没有安装dotenv,可以在制作Dockerfile镜像前,在项目中运行yarn add dotenv,在package.json、yarn.lock文件中添加对应内容,之后在Dockerfile中最后的运行阶段,添加COPY --from=builder --chown=nextjs:nodejs /app/node_modules/dotenv ./node_modules/dotenv,来把已下载安装好的dotenv复制到对应位置。
// scripts/envConfig.js文件
const dotenv = require('dotenv');
const path = require('path');

const envFileAbsolutePath = path.join(__dirname, '../.env.local');

dotenv.config({path: envFileAbsolutePath});

(2) 构建镜像
首先确认已经在项目中使用了standalone,具体可见本文的前面的配置standalone章节。
构建镜像时遇到的问题和构建流程如下:

  • 注册时的Cookie问题。
    pages/api/register.ts中注册时,尝试通过http请求头Set-Cookie设置Cookie,但在浏览器中没有生效,原因在于http请求头中Secure属性在product环境下置为true了,而我们发起的时http请求而非https请求。 2b230783d9deac1abf4ee5621735fd5d.png
文件pages/api/register.ts
修改前:
// Save `key` in a httpOnly cookie
res.setHeader(
  'Set-Cookie',
  cookie.serialize(COOKIE, id, {
    ...
    secure: process.env.NODE_ENV === 'production',
    ...
  })
);

修改后:
res.setHeader(
  'Set-Cookie',
  cookie.serialize(COOKIE, id, {
    ...
    secure: false,
    ...
  })
);

  • Dockerfile构建镜像
    Dockerfile写好后,放在项目的根目录下,运行docker build -t name:version .命令来构建镜像,如本项目的命令docker build -t virtual-event:beta.1 .注意其中的.不能丢,原因见镜像构建上下文(Context)
  • 打包结束后,可以运行docker image ls命令查看本地镜像,如下图,可以看到使用standalone+node:14.21.3-alpine大小由979MB减少到186MB,很好。 image.png

(3)本地镜像上传、下载到阿里云
按照阿里云镜像仓库:拉取和推送Docker镜像的教程,创建命名空间、镜像仓库,之后进入镜像仓库可以看到阿里云官网,关于推拉docker镜像的命令,按照提示可以将打包好的镜像PUSH到刚创建的镜像空间。

$ docker login --username=*** registry.cn-beijing.aliyuncs.com
$ docker tag [ImageId] registry.cn-beijing.aliyuncs.com/***/***:[镜像版本号]
$ docker push registry.cn-beijing.aliyuncs.com/***/***:[镜像版本号`

可以运行以下命令拉取镜像到本地:

docker pull registry.cn-beijing.aliyuncs.com/***/***:***

可以运行如下命令本地运行镜像为容器:

docker run -p 3000:3000 imageName:tag

有用的链接

目前处于写作中,文章简要记录笔记。

React Aria

感觉类似Ant Design那样的UI库,但可以在服务端使用。

React AriaSSRProviderServer Side Rendering(重点理解SSRProvider添加id的目的)

在pages/_app.tsx中,使用到了React Aria中的SSRProvider、OverlayProvider可在VSCode中点击跳转到其定义处看注释。

100ms

介绍:According to the website, 100ms is an infrastructure for live apps that
enables you to build powerful live apps in hours. It provides you with a pre-built template to build virtual events, audio rooms, and classrooms with a few lines of code. The 100ms infrastructure handles all audio-video edge cases, so you don't have to.

Video and Audio conferencing in few minutes with React

项目中使用的是"@100mslive/hms-video-store@0.2.89""@100mslive/hms-video@0.1.43""@100mslive/react-sdk@0.0.6""@100mslive/react-icons@0.0.6",这些包都是比较老的,yarn install下载到的都是打包后的代码。
如果想要阅读其源码,可以运行yarn add @100mslive/react-sdk会把最新版本的代码下载下来,会安装hms-videohms-video-storereact-sdk三个包,内部都有src文件夹,可以阅读原码。

通过全局搜索navigator可以看到其底层是使用WebRTC那套东西来实现的,关于WebRTC可以看WebRTC-面向网络的实时通信(非常值得一看)进行了解。

其他

SkipNavContent:provide a shortcut for keyboard and screen reader users to skip to the content.(个人感觉是将页面的『焦点』放在<SkipNavContent/>的位置,如点击Tab键可以直接跳转到SkipNavContent标签的位置,在该项目中就是页面内容位置)(后期验证下)

NProgress.js: 进度条