身体的勤奋,掩盖不了思想的懒惰

大家好,我是柒八九。一个专注于前端开发技术/RustAI应用知识分享Coder

前言

不知道大家春节过的如何。反正我是嗨翻了,导致过年期间一天电脑都没打开过。过年就应该有过年的样子。哈哈。

既然,年都过完了,也应该收收心了。毕竟只有脚踏实地才是实现诗和远方。然后,是时候打开自己新年flag,准备勇攀高峰了。

最近在搞利用gitlab发布npm包的东西,在处理过程中,发现有很多关于CI/CD的环境变量,同时我们在之前的f_cli项目中也使用.env来涉猎相关的概念。所以,今天我们来聊聊关于环境变量的故事。

当然,我们后期也会单独出一篇如何在gitlab发布npm包的文章。

好了,天不早了,干点正事哇。

我们能所学到的知识点

  1. 环境变量是什么
  2. 环境变量的类型
  3. 为什么要使用环境变量?
  4. 环境变量的例子
  5. 如何存储环境变量
  6. 如何使用环境变量
  7. 如何保护环境变量文件的安全性

0. 环境变量是什么

环境变量(Environment Variables)是在程序运行时动态可用的变量。这些变量的值可以来自各种来源,如文本文件、第三方密钥管理器、调用脚本等。

这里重要的是

这些环境变量的值不会硬编码在程序中。它们是真正动态的,可以根据程序运行的环境进行更改。

1. 环境变量的类型

Windows系统中的环境变量

在基于Windows的计算机系统中,有三种典型的环境变量类型。

1. 系统环境变量

系统环境变量位于系统的最上级根目录,对系统中所有进程和所有用户配置文件都是相同的。我们的操作系统/系统管理员通常会设置这些变量,我们很少需要调整它们。

系统环境变量最常见的用途之一是设置一个PATH变量指向一个全局软件包/库,以供系统中的所有用户使用。

2. 用户环境变量

用户环境变量Windows系统中本地用户配置文件的变量。这些变量用于存储特定用户的信息,如本地安装的库的路径,这些库不对所有用户开放,仅针对特定用户安装的程序的特定值等。

我们无需系统管理员权限就可更改这些变量;作为用户,我们可以自己更改。

3. 运行时/进程环境变量

运行时环境变量只针对它们关联的运行时或进程。这些变量通常由创建新进程的父进程设置,并伴随系统和用户环境变量。

我们可以使用终端脚本来动态创建和存储这些变量。 运行时变量通常不是永久的,除非通过脚本设置,否则在启动新进程时需要对其进行定义。


Unix系列系统中的环境变量

Windows系统不同,Unix系列系统没有三层环境变量类型。在这样的系统中,所有内容都存储在var对象下,运行的程序可以访问/修改它。

如果我们需要在系统上运行的每个程序启动时都将一些环境变量默认加载,则需要在诸如.~/bashrc~/.profile之类的文件中定义它们,这些文件会在系统启动时加载

环境变量与伪环境变量

Windows和基于DOS的系统中有一种单独的动态环境变量,称为伪环境变量。这些不是静态分配的键值对,而是在查询时返回各种值的动态引用。

虽然我们可以使用SET命令或其等效命令手动为环境变量赋值,但是我们不能为伪环境变量赋值。这样的变量有一个固定列表,我们可以在代码中使用它们以使工作更轻松。 一些流行的用例包括

  • %CD%用于当前目录
  • %TIME%用于当前时间

2. 为什么要使用环境变量?

现在我们已经理解了什么是环境变量以及其各种类型,是时候了解为什么在应用程序中大量使用它了。

关注分离

在应用程序中使用环境变量的最重要原因之一是遵循一个设计原则——关注分离(separation of concern)。 该设计原则规定计算机程序应该分为不同的部分来有效地管理它们。 每个部分都应基于程序的主要关注点之一,并且这些部分之间的耦合应尽量少。

我们可以将应用程序配置视为这样的关注点之一;因此,它需要与主程序分离。 实现它的最佳方式之一是将其存储在外部文件中,并按需注入。 环境变量帮助我们使用env文件隔离关键的应用程序配置数据。 这样,我们的开发人员只能访问他们需要的信息。

在不同环境中维护独立的配置集

除了将应用程序配置与源代码隔离外,我们还需要能够轻松地在配置数据集之间切换。 如果我们选择在源代码中硬编码应用程序配置值,则根据外部条件(如部署环境)替换这些值几乎是不可能的。

使用环境变量可以将配置数据与代码解耦,并向应用程序提供此信息的方式,从而可以根据需要动态修改/交换所提供的信息。

保护密钥

我们的应用程序密钥属于敏感信息。如果心术不正人获取了它们,他们就可以访问我们的应用程序内部架构和第三方资源。未经授权访问这些密钥可能会导致资金和应用程序数据流失。

因此,保护这些密钥很重要。在代码中随意留下它们可能会导致所有开发人员都可以访问它们。如果不遵循适当的代码混淆方法,可以通过反向工程检索代码中的密钥。通过环境变量隔离这些密钥可以防止发生这种情况。


3. 环境变量的例子

现在我们已经清楚地了解了环境变量的工作方式以及如何有效地使用它们,下面是可以使用环境变量的一些常见场景:

  • 环境类型:环境变量通常用于存储应用程序当前运行的环境的名称(如DEV/PROD等)。应用程序逻辑可以使用此值来访问正确的资源集或启用/禁用应用程序的某些功能或部分。
  • 域名:应用程序的域名可以根据其环境而有所不同。隔离它还可以帮助我们轻松地修改应用程序的域名,而无需在整个代码库中搜索其出现的位置。
  • API URL:我们的应用程序的每个环境也可以在不同的环境中部署API。

4. 如何存储环境变量

现在我们已经理解了环境变量的重要性,是时候看看如何在应用程序中存储和访问它们了。 下面讨论了在应用程序中管理环境变量的三种不同且流行的方式。

使用 .env 文件

.env 文件无疑是管理环境变量的最简单和最流行的方式。

这里的思想很简单——在项目的根目录下创建一个名为 .env 的文件,将环境变量存储在其中。应用程序查询此文件中的变量,并在运行时加载以供使用。

下面是一个典型的 .env 文件示例:

VAR_A=front789
VAR_B=rust

.env 文件还使我们可以定义环境变量集,并根据应用程序的运行时环境或其他因素访问它们。 我们可以创建多个文件,将它们保存为 .env.dev.env.prod,而不是将文件简单地保存为 .env。 在这些文件中,我们可以根据环境定义相同的变量集,但具有不同的值。

优点

以下是我们应该考虑使用 .env 文件管理应用程序中的环境变量的一些原因。

简单易用

在众多环境变量管理技术中,此方法是最简单的。我们所要做的就是创建一个包含密钥的纯文本文件,并将其存储在项目的根目录中。

切换环境就像改变 env 文件本身一样简单。 我们可以通过名称 .env.dev、.env.prod 等存储多个文件,并配置源代码以根据其运行的环境访问这些文件。

本地访问

我们可以轻松地在本地开发环境中设置 .env 文件。

  • 原生平台变量管理器不同,我们不需要部署应用程序来利用环境变量功能。
  • 密码管理器相比,.env 文件更易于本地设置,访问应用程序密钥时没有网络依赖性。
开源支持

有许多开源包可以帮助我们从 env 文件加载和管理应用程序密钥。 我们不需要依赖付费服务,在应用密钥管理方面,我们的选择也不受限制。 有大量第三方开源库可以帮助我们管理 env 文件。

一些流行/有用的例子是

缺点

在项目中使用它们之前,我们应该了解 env 文件可能存在的一些缺点。

格式

.env 文件以键值对的形式存储应用程序密钥。 在 .env 文件中存储环境变量的常用格式是:

Key1=Value1

为了使应用程序能够成功读取应用程序密钥,我们需要严格遵守此格式。 如果在数十或数百行环境变量之间的某个地方出了小差错,整个文件都可能无法解析,我们的程序将在整个过程中抛出无关的错误。 .env 文件存在解析错误的事实可能甚至不会被突出显示。 这就是为什么在使用 .env 文件时我们需要小心的原因。

共享/存储时可能意外泄露密钥

由于 .env 文件是纯文本文件,因此在共享硬盘上存储或通过不安全的网络发送时很容易意外暴露。 因此,在使用 .env 文件存储密钥时,我们需要特别注意不要泄露应用程序密钥。


使用原生平台变量存储

存储环境变量的另一种流行选项是依赖于部署平台的变量存储。大多数部署环境都提供了一个空间,供用户上传密钥,这些密钥随后会注入到应用程序的运行时中。我们可以查看部署平台的文档,了解它们是否支持此功能以及如何开始使用它。

就拿我们最熟悉的gitlab CI/CD来说

优点

以下是我们应该考虑使用平台原生变量存储解决方案的原因。

高度安全

由于此选项完全由部署平台管理,因此它比在纯文本文件中存储密钥更安全。我们可以控制谁可以访问变量管理器,并可以放心密钥永远不会意外推送到我们的版本控制系统

易于更新

独立存储环境变量时更新它们更简单——我们不需要编辑源代码并为此进行新版本发布。我们可以简单地在平台中更改值并重建项目。它在下次启动时将获取新值。

格式问题也随之消失,因为大多数特定于平台的部署管理器在我们输入键时会对其进行语法检查。

支持协作

由于部署平台可以由我们的整个团队访问,因此我们可以轻松地与正确的人共享密钥,而无需通过互联网发送文本文件。 我们可以控制谁可以访问变量管理器(在大多数情况下),并将其用作应用程序密钥的中央存储库。

缺点

虽然平台原生变量管理器似乎是我们所需要的解决方案,但在选择它们之前,我们应该记住一些问题。

依赖平台

顾名思义,它们高度特定于我们使用的平台。 在某些情况下,我们的部署平台甚至可能不提供此类服务。 更改部署平台以获取此类服务的访问权限似乎不是最佳决策。

不统一

由于它们完全由部署平台提供和管理,这些服务可能高度不统一。 在平台之间移动变量可能会有冲突。 我们甚至无法假设每个部署平台都可以提供导入/导出环境变量的选项。 虽然大多数平台都支持,但它完全取决于平台。 如果我们有大量的环境变量,很有可能会遇到小范围的供应商锁定。

无本地支持

虽然这些服务非常适合在应用部署中访问环境变量,但在本地开发应用程序时几乎没有机会使用它们。 在大多数情况下,我们必须使用管理本地.env文件的方法。 虽然它能满足目的,但会不必要地使整个设置复杂化。


使用密码管理器

当前仍处于开发初期阶段的第三种选择是使用专用的密码管理器。密码管理器是第三方服务,使我们可以完全隔离应用程序密钥与源代码/部署,并在需要时通过安全的网络连接获取它们。

优势

以下是与其他密钥管理方法相比密码管理器提供的一些优势。

高度安全

由于我们的密钥存储在一个完全隔离的服务中,我们可以放心,在与同事共享或通过版本控制提交时,我们很可能不会意外泄露它们。第三方平台负责保护我们的密钥,而且在数据安全方面,它们通常有非常严格的SLAService Level Agreement:一种服务级别协议)。

即使从应用程序内部访问密钥,大多数密码管理器也会提供自己的客户端代码,可以安全获取并在需要的任何位置允许访问密钥。

跨环境统一

由于密钥现在独立于代码库和部署环境,我们现在可以在环境之间实现统一性。 我们不需要为引入新开发人员或在将应用程序推送到生产环境之前采取特殊步骤——大多数这些方面都被我们的密钥管理器简化或处理了。

缺点

虽然密码管理器似乎是管理环境变量的最佳解决方案,但它们也有自己的注意事项。

成本

环境变量管理长期以来一直是项目内部的活动。 即使大多数部署平台也免费提供此功能,因为它们不会为此产生任何额外成本。

但是,由于密码管理器是完全独立的服务,它们有自己的运营成本。 因此,用户在使用这些服务时必须承担这些费用。

技术早期阶段

由于该技术相当新,我们永远无法确定它在未来几天将如何被行业采用。 尽管从安全性和管理便利性来看,密码管理器展示出巨大的前景,但成本因素和数据处理问题可能会导致技术采用相对缓慢。


5. 如何使用环境变量

现在我们已经理解了环境变量的概念以及在应用程序中实现它们的可用方式,让我们看看如何通过终端和基于Node.js的应用程序使用它们。

终端中的环境变量

由于环境变量特定于进程,因此我们可以通过终端设置和删除它们,以便将它们传递给终端生成的进程。

查找变量值

要查看终端中环境变量的列表,可以运行特定于操作系统的以下命令。

  • Windows上:

    • set
  • LinuxMacOS上:

    • env

这将打印可用环境变量的列表到运行的终端。

设置新变量

要通过终端设置新变量,可以运行以下命令。

  • Windows上:

    • set "VAR_NAME=var_value"
  • LinuxMacOS上:

    • VAR_NAME=var_value

删除变量

要删除环境变量,需要运行以下命令。

  • Windows上:

    • set "VAR_NAME="
  • LinuxMacOS上:

    • unset VAR_NAME

请注意,上述方法仅用于为终端的当前会话创建/删除环境变量。

Node.js中的环境变量

JavaScript是当前最流行的编程语言之一。它广泛用于构建后端和前端应用程序,这使它成为最通用的编程语言之一。

Node.js是用于构建后端应用程序的最广泛使用的JS框架之一。让我们看看如何在基于Node.js的应用程序中轻松处理环境变量。

假设有如下的index.js

console.log(process.env);

直接访问环境变量

Node.js通过process.env为我们提供对当前进程环境变量的引用。我们可以通过将此对象打印到控制台来查看可用的环境变量。

此对象将包含运行中的Node进程可用的变量。我们可以通过在运行应用程序之前声明它们来向其中添加新变量,类似于:

VAR_FRONT=789 node index.js

新变量被添加到我们的process.env对象中。但是,我们无法通过此方法访问在.env文件中定义的任何变量。为此,我们需要使用像dotenv这样的包在运行时加载.env文件。

使用dotenv包访问.env文件

dotenv包可以帮助我们加载存储在项目根目录中的.env文件中的环境变量。使用非常简单,我们需要通过运行以下命令进行安装:

npm i dotenv

接下来,我们需要在应用程序代码开头添加以下代码行来初始化包:

require('dotenv').config()

就是这样!现在,我们在项目根目录的.env文件中存储的密钥将在应用程序启动时加载到我们的process.env对象中。


6. 环境变量教程

理解技术概念的最佳方式是观察它在实际应用中的运行。

让我们通过一个小例子来,帮助我们开始使用环境变量并详细了解其用法。

在以下指南中,我们将演示如何通过配置.env文件来讲解,针对其他两种不过介绍。

首先:创建一个Node.js项目

首先,确保我们的系统上安装了Node.js。 一旦在系统上设置好,我们就可以访问npmnpm通过命令行从全局npm注册表安装node软件包,这对安装与测试项目相关的软件包非常有用。

接下来,打开终端并创建一个新目录。 在其中初始化一个新的Node项目:

mkdir env-app
cd env-app 
npm init

通过按enter接受默认选项来跳过接下来的问题。完成后,我们的终端看起来会像这样:

终端输出显示导致初始化一个新的node应用程序的步骤。

使用我们称手的IDE打开项目。

在项目文件夹的根目录中创建一个新文件,并将其保存为index.js。 这将是应用程序的起点。 接下来,安装Express以快速创建和测试REST服务器:

npm i express

安装Express后,在index.js文件中粘贴以下代码:

const express = require("express")

const app = express() 

app.get("/", (req, res) => {
    res.send("Hello front789!")  
})

app.listen(8080);

这是一个使用Node.jsExpress的基本Hello World端点的入门代码段。 在命令行上运行以下命令:

node index.js

这将启动我们的node + express应用程序。 如果在网页浏览器中导航到http://localhost:8080,我们将收到类似的输出:

这表明我们已经正确设置了应用程序!下一步是更新应用程序以使用环境变量。更新index.js中的代码以匹配以下代码段:

const express = require("express")

const app = express()

app.get("/", (req, res) => {

  // responseMessage对象从环境变量中提取其值
  // 如果未找到值,则改为存储字符串“未找到”
  const responseMessage = {
    environment: process.env.environment || "未找到",
    apiBaseUrl: process.env.apiBaseUrl || "未找到"
  }

  res.send(responseMessage)
})

app.listen(8080);

我们不再在响应中发送Hello front789!消息,而是发送一个包含两条信息的JSON对象:

  • environment: 表示应用程序当前部署的环境
  • apiBaseUrl: 包含假想API的基本URL。我们将根据应用程序部署的环境更改此URL的值。

如果无法访问环境变量,则上述两个键的值将包含为对象中的未找到。在继续之前,请重新运行node index.js命令,我们将收到以下输出:

这表明我们的应用程序当前无法访问环境变量。 不仅如此,我们甚至还没有为这些变量定义值。

类型1:通过env文件添加环境变量

让我们从添加环境变量的最基本方法开始——env文件。在项目根目录中,创建一个名为.env的新文件,并在其中存储以下代码:

environment=DEV
apiBaseUrl=http://dev.fron789.com:8080/v1

接下来,通过再次运行node index.js命令重新启动应用程序并检查输出:

打印env变量的值

我们会发现输出仍保持不变。这是因为即使我们已经定义了环境变量的值,我们也并没有真正指示应用程序在哪里可以找到它们。这就是像dotenv这样的包派上用场的地方。

运行以下命令安装dotenv:

npm i dotenv

要在代码中开始使用dotenv,请在index.js的第2行添加以下代码:

require('dotenv').config()

接下来,重新启动应用程序并检查输出:

如我们所见,环境变量的值已经从我们的.env文件加载!

现在,为了模拟更真实的场景,让我们将.env文件重命名为.env.dev,并创建一个名为.env.prod的新文件。 将以下代码段粘贴到新文件中:

environment=PROD
apiBaseUrl=http://prod.front789.com:3000/v1

完成后,用以下代码替换index.js文件的第2行:

require('dotenv').config({
  path: "PROD" === process.env.NODE_ENV?.toUpperCase() ? 
  './.env.prod' :
  './.env.dev' 
})

console.log(process.env.NODE_ENV)

这里的变化是我们现在指示dotenv包根据另一个名为NODE_ENV的环境变量从两个可用文件中获取环境变量的内容。 这个环境变量的值从哪里来? 我们的终端。

要测试此设置,请使用以下命令运行应用程序:

NODE_ENV=DEV node index.js

如果我们现在转到localhost:8080,我们会注意到以下响应:

现在,关闭运行中的应用程序,并使用以下命令再次运行它:

NODE_ENV=PROD node index.js

现在转到localhost:8080将导致我们收到以下响应:

这就是如何使用.env文件根据外部条件访问不同变量集的方式。 这里的外部条件是外部环境变量NODE_ENV,本质上它是一个用户环境变量,而environmentapiBaseUrl是运行时环境变量。 调用用户配置文件提供NODE_ENV变量的值,应用程序利用它进行内部决策。

如果我们使用Windows,在运行前两个命令时可能会遇到困难。 Windows终端可能不允许我们使用KEY=VALUE语法即时分配用户环境变量(除非是Windows上的Bash)。

那种情况下的一个快速解决方案是使用脚本和cross-env

通过运行以下命令安装cross-env:

npm i --save-dev cross-env

接下来,转到package.json文件并更新脚本以匹配此内容:

{
  "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",  
      "start-dev": "cross-env NODE_ENV=DEV node index.js",
      "start-prod": "cross-env NODE_ENV=PROD node index.js"
  },
}

现在,我们可以运行以下命令使用两个不同的环境变量集运行应用程序:

npm run start-dev 
npm run start-prod 

这是一个通用的解决方案,我们也可以在bash/zsh上使用这些脚本。


6. 如何保护环境变量文件的安全性

虽然环境变量在现代 DevOps 实践中非常有用,但你需要意识到它们可能带来的安全隐患。

将 env 文件排除在版本控制之外

处理任何密钥信息时最重要的事情之一是将它们排除在版本控制之外。

版本控制仅用于跟踪应用程序源代码的更改。进入版本控制系统的所有内容都将留在其中,直到被明确删除,并且我们团队中的大多数人都可以访问这些历史数据以供参考。

如果你将用于应用程序中的付费 API 服务的密钥存储在 env 文件中,除非有必要让整个开发团队都能访问它,否则你不希望与他们分享。如果你的项目在像 GitHub 这样的平台上是开源的,将 env 文件添加到你的版本控制系统中可能意味着与整个世界分享!Env 文件应该存储在本地。我们可以通过专门的方法为每个部署环境提供相关的 env 文件。

始终将 env 文件添加到我们的 .gitignore 文件中。我们可以考虑向我们的版本控制系统添加一个模板 env 文件,以便其他团队成员可以将其用作参考,在本地创建他们自己的 env 文件。

在安装之前检查软件包名称

由于在使用 Node.js 应用程序时通常会从 NPM 安装大多数软件包,因此在执行此操作时应格外小心。众所周知,任何人都可以创建和部署一个 NPM 包。而且也不足为奇的是,人们在输入想要安装的软件包名称时经常会出错。

已经注意到多次出现了这样的情况,即由于输入错误,用户不小心安装了与一些流行软件包名称相似的恶意软件包。这些软件包旨在获取我们应用程序的环境变量,并通过互联网将它们发送给其创建者。

唯一的解决方法是在从互联网安装新软件包时保持警惕。


后记

分享是一种态度

全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。

本文由mdnice多平台发布


前端柒八九
18 声望3 粉丝