跳到主要内容

· 阅读需 6 分钟
Mixbo

GeoIP2 module 的作用

GeoIP2 module 可以通过将 IP 地址与 MaxMind 的 GeoIP2 数据库进行匹配,为 Nginx 提供有关请求者的地理位置信息。这可以用于实现一些基于地理位置的功能,例如:

  • 根据用户所在的国家/地区为其提供不同的网站内容
  • 基于 IP 地址的访问控制,例如阻止某些国家/地区的访问
  • 基于用户地理位置的广告定向

使用 Nginx 官方 Docker image 添加 GeoIP2 模块

使用官方源 build 镜像是一种简单和可靠的方法来获取最新版本的 Nginx,同时也可以确保使用的镜像是安全的和经过官方认证的。

要使用 GeoIP2 module,需要将它添加到 Nginx 的模块列表中。可以通过构建一个自定义镜像,在其中包括 GeoIP2 module 或者通过使用预构建的镜像并添加模块作为插件进行安装。

执行以下命令从 Nginx 官网镜像 repo 重新打包 Nginx 包 参考

先在本地启动 Docker

$> cd ~/dev
$> git clone https://github.com/nginxinc/docker-nginx.git
$> cd docker-nginx/modules
# 在 modules 中通过 Dockerfile.alpine 重新打包 Nginx 并开启 geoip2 模块
$> docker build -f Dockerfile.alpine --build-arg ENABLED_MODULES="geoip2" -t nginx-with-geoip2 .

准备源数据

在配置 geoip2 之前需要到 MaxMind 下载数据,在 Nginx 中更新 GeoIP2 数据库的方法通常有以下方法:

  • 手动下载最新的 GeoIP2 数据库文件,替换旧的数据库文件即可。(需要注意的是,如果您使用的是收费版的 GeoIP2 数据库,则需要获取相应的授权以下载最新的数据库文件)
  • 除了手动下载和更新数据库文件外,还可以使用一些第三方工具自动更新 GeoIP2 数据库。可以使用 geoipupdate 工具自动从 MaxMind 网站下载并更新数据库文件。
  • 此外还有一些第三方软件或服务可以提供自动更新 GeoIP2 数据库的功能

具体操作参考官方链接

下载后的文件需要放到 /etc/nginx/modules/geoip2 目录,以方便以下配置能被正确使用

配置 geoip2 指令

在 Nginx 配置文件中,可以使用 geoip2 指令来配置 GeoIP2 module,geoip2 指令可以指定 GeoIP2 数据库文件的位置以及要查询的 IP 地址的变量名。

geoip2 modules/geoip2/GeoLite2-Country.mmdb {
auto_reload 5m;
$geoip2_metadata_country_build metadata build_epoch;
# 这里需要注意 source 位置只能在 $country_code 之后
$country_code source=$source_ip country iso_code;
$country_name country names en;
}
geoip2 modules/geoip2/GeoLite2-City.mmdb {
$geoip2_data_city_name city names en;
}

这段 Nginx 配置代码是使用 GeoIP2 module 实现 IP 地址到国家/地区和城市名称的映射,具体说明如下:

  • 定义了一个 auto_reload 指令,表示该模块将自动重新加载数据库文件并更新相关变量的值时间间隔为 5 分钟。
  • 第一个模块将请求的 IP 地址与名为 "GeoLite2-Country.mmdb" 的 MaxMind GeoIP2 国家数据库进行匹配,并指定了一个自定义的变量 $country_code$country_name。其中 $country_code 变量将返回请求者的 国家/地区 ISO3166-1 代码 ,而 $country_name 变量将返回请求者国家/地区的英文名称。 此外 $geoip2_metadata_country_build 变量将返回国家数据库的构建元数据信息即构建时间戳。
  • 第二个模块将请求的 IP 地址与名为 "GeoLite2-City.mmdb" 的 MaxMind GeoIP2 城市数据库进行匹配,并指定了一个自定义的变量 $geoip2_data_city_name, 此变量将返回请求者所在城市的英文名称。
  • 需要注意的是 $source_ip 变量没有被定义在这个 Nginx 配置中,需要在其他地方定义该变量并将其传递给这个配置块。(以下是一个获取 $source_ip 的例子,因为要走 CDN 所以要从 $proxy_add_x_forwarded_for 变量中解析获取。)
    # 从 CDN 过来的 X_FORWARDED_FOR 信息中取第一个 ip 地址当成请求的源 ip。
    # "Amazon CloudFront" "111.3.1.136, 132.116.93.159, 172.22.214.128, 172.21.201.146" -> 11.19.1.131
    # result: $source_ip = 11.19.1.131
    if ($proxy_add_x_forwarded_for ~ ^([^,]*)) {
    set $source_ip '$1';
    }
    综上,这个 Nginx 配置使用了 GeoIP2 module 和 MaxMind 的 GeoIP2 数据库,实现了 IP 地址到国家/地区和城市名称的映射,并在配置中定义了自定义变量和自动重新加载的指令。

剩下的就是正常使用 nginx-with-geoip2 这个 Nginx 镜像了

· 阅读需 2 分钟
Mixbo

在 Docker 中 Debug Node.js 服务

本文介绍如何在 Docker 容器中进行 Node.js 应用的 debug,让你快速定位并解决应用问题。

为什么需要在 Docker 中进行 Node.js 的 Debug?

在开发 Node.js 应用时,我们经常会使用 node --inspect-brk 命令来开启调试模式。这种方式通常在本地开发环境下使用,但是在一些环境中,我们一般不会直接运行 Node.js 应用,而是通过容器技术,比如 Docker 来部署应用。这时候,我们就需要在 Docker 容器中对 Node.js 应用进行调试了。

如何在 Docker 中进行 Node.js 的 Debug?

在 Docker 容器中启动应用时,需要加上参数 NODE_OPTIONS=--inspect-brk=0.0.0.0,以开启 Debug 模式。具体的操作是在 docker run 命令中增加以下代码:

docker run -p 3000:3000 -p 9229:9229 -e NODE_OPTIONS=--inspect-brk=0.0.0.0 <image-name>

上述命令中,-p 参数将容器中的端口映射到主机上,-e 参数用于设置环境变量,其中 NODE_OPTIONS=--inspect-brk=0.0.0.0 用于启动 Debug 模式。

开始调试

  • 在 Chrome 浏览器中打开 DevTools(开发者工具),进入调试页面。具体的操作是在地址栏输入 chrome://inspect/#devices,然后点击 Open dedicated DevTools for Node。
  • 在 DevTools 中选择要 Debug 的 Node.js 应用,并点击 Inspect 按钮

· 阅读需 3 分钟
Mixbo

Umi 是一个很棒的 React 框架用于快速搭建项目。目前 Umi 4 已经发布,此版本自带了社区中比较常用的特性(包括:tailwindcss、monorepo、MFSU V3、React Router 6 等)。 其中 monorepo 是期待很久的特性,遗憾的是 3.x 版本现在以及未来都不持 monorepo。问题是实际的项目中或多或少会有多个项目依赖相同代码和组件的需求,我喜欢用 monorepo 来共享组件、工具方法等。

当然发布成 npm 包也是一种方案,只是我偏好用 monorepo

因为老项目都跑在 Umi 3.x 版本,怎么在不支持 monorepo 的项目中实现模块共用就是我要分享的内容。

目录结构

apps
--packages
--app1 # umi 项目 1
--app2 # umi 项目 2
--service # 网络请求
--utils # 工具方法
--package.json

上面的目录结构可以看出 app1 & app2 想要共享 service & utils 包中的代码,在 Umi 3.x 版本中 debug 源代码发现只要在 building time 时把 service & utils 的目录路径添加到 webpack 的“查找目录列表”即可实现类似于 monorepo 的效果。

具体实现

  • 在 service 目录中创建 plugin.js 文件 利用 Umi 的 plugin 机制可以将 service 的目录路径注入到 webpack 的各个阶段
  // service/plugin.js
export default (api)=>{
api.chainWebpack(webpackConfig => {
webpackConfig.resolve.alias.set('@service', path.resolve(__dirname));

// 将 service 目录中的代码用 ts-in-node_modules rules 解析,以解决 loader 找不到的问题
webpackConfig.module.rule('ts-in-node_modules').include.add(path.resolve(__dirname)).end();
return webpackConfig;
});
}
  • 在 app1 中配置 .umirc.ts 文件 plugin 和 chainWebpack 选项
  // app1/.umirc.ts
import { resolve } from 'path'
export default defineConfig({
// 启用 service 插件
plugins: [resolve(__dirname, './../service/plugin.js')],

// umi build 时将 app1/node_modules 添加到 webpack modules 源路径中,
// 以解决当引用 service 时引用到的第三方包时报找不到的问题
chainWebpack(config) {
config.resolve.modules.add(`${resolve(__dirname)}/node_modules`).end()
}
})
  • 在 app1 代码中引用 service 代码即可
  // app1/pages/products/index.tsx

// 从 service 包中引用 `fetchProducts` 方法
// 注意这里要用 @services alias
import { fetchProducts } from "@service/product"

const ProductsPage = (props) => {
const [products, setProducts]=useState([])

useEffect(()=>{
fetchProducts().then((data)=>{
setProducts(data)
})
}, [])

return (
<pre>
{JSON.stringify(products)}
</pre>
)
}
export default ProductsPage
  • 在 app1 目录中配置 tsconfig.json 以解决编辑器能索引到文件
  // app1/tsconfig.json
{
{
"compilerOptions": {
// ....
"paths": {
"@service/*": ["./../service/*"],
//....
}
}
}

经过以上的代码调整基本上能满足在 Umi 3.x 中实现 monorepo 级别的代码共用。