Full Stack for Front-End Engineers, v3

了解成为全栈工程师意味着什么。动手搭建属于你自己的服务器,从零开始构建和部署Web应用程序。你将深入研究服务器,使用命令行,理解网络和安全知识,设置持续集成和部署流程,管理数据库,并构建容器。成为一名全面发展的工程师,获得能够构建任何类型应用程序的信心!

0-introduction

  • 讲师介绍
    • Jem Young,Netflix 的软件工程经理,在 Netflix 工作约七年。
    • 管理 netflix.com 的平台团队,技术栈主要是 Node 和 React。
    • 播客 Front End Happy Hour 的联合主持人。
    • 在 Frontend Masters 上有其他课程:《WebAssembly 入门》和《前端工程师面试》。
  • 课程先决条件
    • 对编程有一定熟悉度。
    • 可以使用终端(Terminal),最好是 OS X 环境。
    • 需要网络连接。
    • 需要信用卡(可选,用于购买域名)。
    • 需要有冒险精神,因为课程将手动完成从终端到服务器再到网页的全过程。
  • 现代计算的组成
    • 课程将其分解为三个主要部分:
      1. UI (用户界面):前端部分。
        • 存在于浏览器、移动设备、汽车、桌面、电视甚至智能家电中。
      2. 服务器 (Server):后端部分。
        • 提供 API、日志记录 (Logging)、身份验证 (Authentication)。
        • 也是一个开发平台(本地或远程)。
      3. 数据库 (Database):后端部分。
        • 用于结构化数据存储、数据分析、AI 和商业智能。
  • 前端 vs. 后端
    • 前端 (Frontend):通常指 UI。前端工程师对自己 title 的定义非常细致(如 UX、可访问性等)。
    • 后端 (Backend):指服务器和数据库等所有非前端的部分。后端工程师的 title 则比较笼统。
  • 什么是全栈工程师 (Full Stack Engineer)?
    • Jem 认为,许多自称全栈的工程师只是使用了别人的工具(如 Firebase, Vercel)来快速搭建前后端,但并不真正理解底层原理。
    • 真正的全栈工程师应该理解并能操作更底层的东西,例如:
      • 调整 IP 配置表。
      • 配置防火墙。
      • 为公司选择合适的操作系统。
    • 技能差距示例
      • 写一个 React 组件(多数前端工程师可以)。
      • 用 Node 从零写一个服务器(较少人可以)。
      • 写一个 SQL 查询(更少人了解)。
  • 课程目标
    • 覆盖 UI 之外的“其他一切”。
    • 深入了解服务器、网络、数据库概览以及互联网的工作原理。
    • 让学生了解所有系统是如何关联在一起的,从而获得竞争优势。
    • 课程内容是渐进的,需要跟上进度,犯错是学习过程的一部分。

1-what-is-a-full-stack-engineer

  • 全栈工程师意味着什么?
    • 社区观点
      • 对数据输入、处理和呈现给用户负有全部责任。
      • 通才 (Generalist),能做所有事情。
      • “万事通,无一精通 (Jack of all trades, Master of None)”。
      • 能构建一个完整的、可工作的应用程序。
      • “瑞士军刀般的程序员 (A Swiss Army coder)”。
      • 能理解全局。
      • (开玩笑)几乎无法拼凑出半个能用的软件的人。
    • 讲师的观点
      • 他最喜欢的术语是 通才 (Generalist)
      • 精通所有领域几乎是不可能的,因为每个主题都非常复杂。
      • 关键在于理解所有系统如何协同工作,并将它们组合在一起。
  • 什么是技术栈 (Stack)?
    • 一个典型的技术栈包含:
      1. 用户界面 (User Interface)
      2. Web 服务器 (Web Server)
      3. 数据库 (Database)
      4. 操作系统 (Operating System) - 经常被忽略但至关重要。
      5. 应用服务器 (Application Server) - 视情况而定。
  • 流行的技术栈示例
    • LAMP Stack:Linux, Apache, MySQL, PHP。
      • 被认为是 WordPress 的技术栈,是互联网上最流行的建站方式。
    • MEAN Stack:Mongo, Express, Angular, Node.js。
    • MERN Stack:Mongo, Express, React, Node.js。
    • 重要观点:像 MEAN 和 MERN 这样的酷炫名称主要是市场营销的结果。技术栈的选择是任意的,只要你和团队熟悉并能用它构建应用即可。
    • 其他组合示例
      • React, Node.js, Redis
      • Angular, Tomcat (Java 应用服务器), MySQL
      • Vue, Apache, Postgres
  • 最终定义
    • 全栈工程师:能够管理应用程序的前端和后端,并理解这些系统如何连接和协同工作的工程师。

2-command-line-exercise

  • 终端和命令行 (Terminal and Command Line)
    • 核心理念:与计算机交互的最高效、最底层的方式。
    • 优点
      • 快速 (Fast):直接与操作系统对话,没有延迟。
      • 一致 (Consistent):命令在本地机器和远程服务器上通用。
      • 可移植 (Portable)
      • 低开销 (Low Overhead):非常直接和功能化,没有多余的东西。
    • 终端应用推荐iTerm(macOS),因其分屏等便捷功能。系统自带的 Terminal 也完全可用。
  • 常用命令
    • ls:列出当前目录中的文件和文件夹。
    • cd:改变目录 (Change Directory)。
    • mkdir:创建新目录 (Make Directory)。
    • rmdir:删除空目录。
    • cat:显示文件内容。
    • man:显示命令的手册页 (Manual),非常重要。
    • less:分页显示文件内容。
    • touch:创建一个空文件(技术上是更新文件的“最后修改时间”,如果文件不存在则会创建它)。
    • rm:删除文件。常用 rm -rf 来递归强制删除目录及其内容。
    • echo:重复并打印输入的内容。
  • 练习任务
    1. 导航到你的主目录 (home directory)。
    2. 创建一个名为 temp 的目录。
    3. 进入 temp 目录。
    4. 列出目录内容(此时应为空)。
    5. 创建一个名为 hello 的文件。
    6. 再次列出目录内容。
    7. 移出 temp 目录。
    8. 删除 temp 目录。
  • 补充说明
    • Windows 用户:可以通过安装 Windows Subsystem for Linux (WSL) 来运行这些命令。安装过程可能需要一些时间并重启电脑。

3-command-line-solution

  • 练习解答
    1. 导航到主目录:
      • cd ~ (波浪号 ~ 是主目录的快捷方式)。
    2. 确认当前位置:
      • pwd (Print Working Directory) 显示当前完整路径。
    3. 创建目录:
      • mkdir temp
    4. 进入目录:
      • cd temp
      • Tab 自动补全 是一个非常有用的功能,可以提高效率。
    5. 创建文件:
      • touch hello
    6. 列出内容:
      • ls (会看到 hello 文件)。
  • 手册页 (man page) 的使用
    • 查看手册: man ls
    • 在手册中搜索: 输入 / 后跟搜索词,例如 /-a 来查找 a 选项。
    • 退出手册: 按 q 键。
  • ls 的常用选项 (flags)
    • ls -la 是一个非常常用的组合。
      • l: 以长格式 (long format) 显示,包含权限、所有者、大小、修改日期等详细信息。
      • a: 显示所有 (all) 文件,包括以 . 开头的隐藏文件(如 .gitignore)。
  • 目录导航
    • cd ..: 返回上一级目录。
    • /: 文件系统的根目录 (root)。除非非常清楚自己在做什么,否则不要修改根目录下的任何东西。
  • 删除目录
    • rmdir temp: 如果 temp 目录不为空,此命令会失败。
    • rm -rf temp: 这是删除目录及其所有内容的常用方法。
      • rm: remove 命令。
      • r: 递归 (recursive),删除目录下的所有文件和子目录。
      • f: 强制 (force),忽略不存在的文件并无需确认。
      • 警告: rm -rf 是一个非常危险的命令,使用时需格外小心。

4-vim-exercise

  • 实用命令(以防迷路)
    • cd ..:移出当前目录。
    • pwd:显示当前工作目录路径(“我在哪里?”)。
    • clear:清空终端屏幕。
    • Ctrl+C:终止几乎所有正在运行的程序。
    • cd ~:返回主目录。
  • Vim 简介
    • Vim 是一个命令行文本编辑器,几乎在所有服务器环境中都可用,因此学习它非常重要。
    • 核心优势:一旦熟练,编辑文件的速度极快,因为它完全脱离鼠标操作。
  • Vim 的三种主要模式
    1. Normal Mode (普通模式)
      • 打开 Vim 后的默认模式。
      • 用于导航和执行命令。
      • ESC 键可随时返回此模式。
    2. Insert Mode (插入模式)
      • 用于输入和编辑文本。
      • 在普通模式下按 i 键进入。
    3. Command Mode (命令模式)
      • 用于执行保存、退出、搜索等操作。
      • 在普通模式下按 : 键进入。
  • 练习任务
    1. 导航到你的主目录。
    2. 创建一个名为 temp 的目录。
    3. 使用 Vim 打开一个名为 test 的文件。
      • 命令:vim test (如果文件不存在,Vim 会创建它)。
    4. 写入几行文字。
      • 进入插入模式 (i) -> 输入文字。
    5. 保存并退出。
      • 返回普通模式 (ESC) -> 进入命令模式 (:) -> 输入 wq 并按回车。

5-vim-solution

  • 练习解答
    1. 创建嵌套目录 (高级技巧):
      • mkdir -p temp/foo/barp 选项可以一次性创建尚不存在的父目录。
    2. 打开 Vim:
      • vim test
    3. 编辑文件:
      • i 进入 插入模式 (Insert Mode),屏幕左下角会显示 - INSERT --
      • 输入文本,例如 "hello"。
    4. 保存并退出:
      • ESC 返回 普通模式 (Normal Mode)
      • 输入 :wq 并按回车。
        • : 进入命令模式。
        • w 代表写入 (write)。
        • q 代表退出 (quit)。
  • 确认文件内容
    • cat test:该命令会在终端中打印出 test 文件的内容。
  • Vim 实用技巧
    • Undo (撤销): 在普通模式下,按 u 可以撤销上一步操作。
    • Quit without saving (不保存退出):
      • 输入 :q,如果文件有未保存的修改,Vim 会发出警告。
      • 输入 :q!,感叹号 ! 表示强制执行。这会放弃所有未保存的修改并退出。当你搞砸了文件又不想保存时非常有用。
  • 深入学习 Vim
    • Vim 功能非常强大,但学习曲线陡峭。
    • 本课程只介绍基础操作,足够完成后续任务。
    • 推荐 Frontend Masters 上由 The Primagen 主讲的 Vim Fundamentals 课程(时长 4 小时),适合希望深度掌握 Vim 的学习者。
    • 熟练使用 Vim 的人效率极高,但这需要长时间的练习和投入。

6-shell-exercise

  • Shell 简介
    • 什么是 Shell? Shell 是在终端(Terminal)内部运行的应用程序。它负责解释你输入的命令,并将其传递给操作系统执行。
    • 常见的 Shell
      • bash (Bourne Again Shell):大多数 Unix/Linux 系统的传统默认 Shell。
      • zsh (Z Shell):现代 macOS 的默认 Shell,功能比 bash 更强大,扩展性更好。
      • 在本课程中,bashzsh 的基本命令是通用的。
  • Shell 配置
    • 查看当前 Shell
      • echo $0echo $SHELL
    • 配置文件
      • Shell 的行为可以通过修改其配置文件来定制。这些文件通常位于你的主目录 (~)下。
      • zsh 的配置文件是: ~/.zshrc
      • bash 的配置文件是: ~/.bash_profile~/.bashrc
    • 应用配置
      • 修改配置文件后,必须重新加载 Shell 才能让更改生效。
      • 方法一:运行 source ~/.zshrc (替换为你的配置文件路径)。
      • 方法二(更简单):关闭当前终端窗口/标签,然后重新打开一个。
  • 练习任务
    1. 打开你的 Shell 配置文件(根据你正在使用的 Shell 决定是 ~/.zshrc 还是 ~/.bash_profile)。
    2. 添加一行命令,让你的 Shell 在每次启动时都向你问好。
    • 提示
      • 使用 echo 命令来打印文本。
      • 可以尝试使用环境变量,如 echo "Good morning, $USER",来让问候更具个性化。

7-shell-solution

  • 练习解答
    • 目标:让终端在每次启动时自动运行一个命令(例如,打印问候语)。
  • 步骤分解
    1. 确定你正在使用的 Shell:
      • 在终端输入 echo $0
      • 本例中,输出为 zsh
    2. 找到对应的配置文件:
      • 对于 zsh,配置文件是 ~/.zshrc
    3. 编辑配置文件:
      • 使用 Vim 打开文件:vim ~/.zshrc
      • 你的配置文件内容可能与讲师的不同,这取决于你之前安装过的工具。
    4. 添加自定义命令:
      • 在文件的任意位置(通常是末尾)添加新的一行:
      • echo "Good morning, $USER"
      • $USER 是一个环境变量,它会自动被替换为当前登录的用户名。
    5. 保存并退出 Vim:
      • ESC,然后输入 :wq 并回车。
    6. 测试效果:
      • 打开一个新的终端标签页或窗口。
      • 你应该能看到 "Good morning, [你的用户名]" 这条消息。
  • 核心概念
    • 通过编辑 Shell 配置文件,你可以实现自动化。每次打开终端时,都可以让它自动执行你想要的任何任务,例如检查邮件、查看天气、获取 Git Pull Requests 状态等。
  • 深入学习和工具推荐
    • Frontend Masters 课程:
      • Scott Moss 的 "Bash, Vim, Regex" 课程。
      • Brian Holt 的相关课程。
    • Oh My Zsh:
      • 一个非常流行的 zsh 配置管理框架。
      • 它提供了大量漂亮的主题、实用的插件和非常好的默认设置,强烈推荐使用。

8-servers

  • 本节主题

    • 服务器 (Server)
    • 云计算 (Cloud Computing)
    • 虚拟专用服务器 (VPS)
    • 操作系统 (Operating System)
    • SSH
    • 哈希 (Hashing)
  • 什么是服务器 (Server)?

    • 本质:一个响应请求 (serves requests) 的程序或计算机。这更多是一个概念,而非特指某种实体。
    • 任何计算机都可以成为服务器(例如,你的手机、笔记本电脑)。
    • 专用服务器 (Dedicated Hardware) 与普通电脑的区别:
      • 芯片组不同:服务器芯片为虚拟化等场景优化,通常更高效,也更昂贵。
      • 高可用性:需要 100% 持续运行。而我们的笔记本电脑会关机或重启,这会导致服务中断,因此不适合做生产环境的服务器。
  • 练习: 创建一个简单的 Node.js 服务器

    • 目标:创建一个名为 simpleServer.js 的文件,它将监听 3000 端口。

    • 代码框架

      const http = require("http");
      const fs = require("fs");
      const PORT = 3000;
      
      const server = http.createServer((req, res) => {
        // ... 处理请求和响应的逻辑
      });
      
      server.listen(PORT, () => {
        console.log(`Server is running on port ${PORT}`);
      });
      
    • 这个练习将在接下来的部分逐步完成。


9-create-a-simple-node-js-server

  • 练习步骤:创建一个完整的 Node.js 服务器
    • 我们将编写服务器代码、安装所需软件、并最终运行它。
  • 编写 Node.js 服务器代码 (simpleServer.js)
    • temp 目录中,使用 Vim 创建 simpleServer.js 文件。
    • 代码详解
      • const http = require('http'): 引入 Node.js 内置的 HTTP 模块,用于创建服务器和处理 HTTP 请求。
      • const fs = require('fs'): 引入文件系统 (File System) 模块,用于读取文件。
      • const PORT = 3000: 定义端口号。3000 是开发中常用的、通常未被占用的端口。
        • 常见保留端口22 (SSH), 80 (HTTP), 443 (HTTPS)。
      • http.createServer((req, res) => { ... }): 创建服务器实例。这个函数在每次有新请求时被调用。
        • req (Request) 和 res (Response) 对象是所有 Web 服务器的核心。req 包含了请求信息,res 用于发送响应。
      • res.writeHead(200, {'Content-Type': 'text/html'}): 写入响应头 (Header)。200 是 HTTP 状态码,表示“OK”;Content-Type 告诉浏览器我们发送的是 HTML 内容。
      • fs.createReadStream('index.html').pipe(res): 这是最关键的一步。
        • createReadStream 创建一个可读流,它会像水流一样一点一点地读取 index.html 文件。
        • .pipe() 将文件流“管道”到响应流 res 中。
        • 流 (Streams) 的优势:对于大文件,流不需要一次性将整个文件加载到内存中,因此效率更高、内存占用更少。
  • 准备并运行环境
    1. 安装 Homebrew (macOS): Homebrew 是 macOS 的“事实标准”包管理器,可以非常方便地安装各种开发工具。
    2. 安装 Node.js: 使用 brew install node 命令进行安装。
      • (可选) 讲师提到了 nvm (Node Version Manager),这是一个管理多个 Node.js 版本的强大工具。
    3. 创建 index.html 文件:
      • vim index.html,内容可以极其简单,例如只写一行 hello world。浏览器有很强的容错性,即使是不完整的 HTML 也能正常显示。
    4. 运行服务器:
      • 在终端中运行 node simpleServer.js
  • 在浏览器中访问
    • 打开浏览器,访问 http://localhost:3000
    • localhost 是一个特殊的主机名,等同于 IP 地址 127.0.0.1,它总是指向你自己的本地计算机。
    • 其他类似的保留 IP 地址,如 192.168.0.1192.168.1.1,通常是你家庭网络中路由器的地址。

10-server-management

  • 为什么本地服务器不够用?
    • 可用性 (Availability): 你的笔记本电脑会关机、重启、更新系统,无法保证服务 100% 在线。服务器的核心原则之一就是持续可用。
    • 协作 (Collaboration): 如果团队成员都基于自己的本地服务器开发,代码同步和环境统一会成为噩梦。
    • 扩展性 (Scalability): 当用户增多时,你不能通过“买更多笔记本电脑”来扩展服务。
  • 服务器管理 (Server Management)
    • 生产环境中的服务器通常不止一台,需要考虑冗余 (redundancy)故障转移 (failover)、负载均衡、滚动更新等复杂策略。
    • 这背后是一个庞大的专业领域,通常称为 DevOps (开发运维)。
  • 物理服务器 vs. 云计算
    • 传统方式 (Server Farm): 过去,公司需要在自己的机房(甚至地下室)部署一排排的物理服务器。这需要专门的场地、冷却系统和运维人员。
    • 现代方式: 云计算 (Cloud Computing)
      • 核心概念:不再自己购买和维护物理硬件,而是从像 AWS、DigitalOcean 这样的专业公司那里租用计算资源和时间
      • 核心技术:虚拟化 (Virtualization)
        • 它允许将一台强大的物理服务器的资源(如 32 个 CPU 核心)分割成多个独立的、互不干扰的虚拟计算机
        • 这使得小开发者可以按需购买少量资源(比如,只买一个 CPU 核心),而不是被迫租用一整台昂贵的物理机。
      • 虚拟专用服务器 (Virtual Private Server - VPS):
        • 这是我们将要购买的服务类型。它是在大型数据中心里通过虚拟化技术创建出来的一台“虚拟”服务器,但你拥有对它的完全控制权,就像一台真实的私人服务器一样。
        • 几乎所有现代 Web 应用(从小型网站到 Netflix)都构建在 VPS 之上。
  • 课程下一步
    • 我们将购买一个 VPS,选择操作系统,并学习如何通过 SSH 安全地登录和管理它。

11-buying-a-vps

  • 购买一个 VPS (Virtual Private Server)
    • VPS 就好比是你在互联网上拥有的一块“数字地产”,一个空白画布,可以随心所欲地在上面构建应用。
  • 平台选择: DigitalOcean
    • 为什么选择 DigitalOcean 而非 AWS?
      • 界面更友好:对于初学者来说,操作简单直观,不易出错。
      • 功能更聚焦:AWS 功能极其强大但同样非常复杂。对于本课程的需求,DigitalOcean 完全足够。
      • 优秀的教程:DigitalOcean 提供了大量高质量的入门教程。
    • (如果你想深入学习 AWS,Frontend Masters 上有专门的课程)。
  • 创建 Droplet (DigitalOcean 对 VPS 的称呼)
    • 登录 DigitalOcean 后,点击 "Create" -> "Droplets"。
    • 1. 选择区域 (Region):
      • 原则:选择离你的目标用户最近的数据中心,这样可以显著减少网络延迟,提升用户体验。
      • 如果是开发服务器,则选择离开发者团队最近的区域。
      • 大型应用为了服务全球用户,会在世界各地的多个区域部署服务器。
      • 本次练习:选择离你物理位置最近的即可(例如,美国西海岸选 San Francisco,东海岸选 New York)。
    • 2. 选择操作系统 (Operating System):
      • 这是一个至关重要且几乎不可逆的决定。
      • 选择: Ubuntu (LTS 版本)
        • Ubuntu: 最常见、最流行的服务器 Linux 发行版,社区支持良好。
        • LTS (Long Term Support): 长期支持版。服务器的首要原则是稳定性和安全性,LTS 版本会提供多年的安全更新和维护,远比追逐最新功能重要。企业级开发总是选择 LTS 版本
        • 选择 64 位版本 (x64)。

12-operating-systems

  • 操作系统 (Operating Systems) 概述
    • 两大类型
      1. Windows 系列: 主要用于服务器的是 Windows Server。
      2. Unix-based (类 Unix) 系列: 种类繁多。
    • Unix-based 系统详解:
      • Linux:
        • 一个非常流行、开源免费的 Unix-like 内核。
        • 拥有众多发行版 (Distributions/Flavors),如 Debian, CentOS, Ubuntu, Fedora, Red Hat 等。
        • Ubuntu 是目前最流行、最常见的服务器操作系统,是本次课程的安全选择。
      • BSD (Berkeley Software Distribution):
        • 另一个主要的 Unix 分支。
        • macOS 的底层就是基于 BSD 的。
      • 共通性: 因为 Linux 和 macOS 都源于 Unix,所以它们共享一套核心的命令行工具(如 ls, cd, mkdir, vi),这就是为什么我们在 Mac 上学习的命令可以在 Ubuntu 服务器上无缝使用的原因。
  • Unix 的核心哲学
    • “一切皆文件 (Everything is a file)”“一切皆进程 (process)”。这是一个非常简洁和强大的设计模型。
    • Unix 系统结构:
      1. 程序/工具 (Programs/Utilities): 我们在命令行中使用的命令,如 ls, mkdir
      2. Shell: 用户与系统交互的命令行界面,它解释用户的命令。
      3. 内核 (Kernel): 操作系统的核心,负责管理硬件资源并执行命令。
  • 继续完成 VPS 创建流程
    • 操作系统: 再次确认选择 UbuntuLTS 版本。
    • Droplet 大小 (Size):
      • 选择最小、最便宜的配置。对于本课程的学习目的,最低配的 Droplet (例如 $4 或 $6/月) 的性能完全足够。
      • 注意成本:记得在课程结束后关闭或销毁你的 Droplet,以免产生不必要的费用。
    • 认证方式 (Authentication):
      • 下一个关键步骤是选择如何登录到这台新服务器。
      • 绝对不要使用密码 (Password)
      • 我们将使用 SSH 密钥 (SSH Key),这是更安全、更专业的行业标准做法。

13-security-hashing

  • 服务器登录的安全考量
    • 密码 (Password) 的巨大风险:
      • 易被猜到/窃取: 常见密码(如 "password", "123456")依然泛滥,极易被猜到。
      • 人类行为的可预测性: 用户倾向于使用有规律、容易记住的密码,比如用 @ 代替 a (p@ssword)。这种模式早已被攻击者掌握。
      • 字典攻击 (Dictionary Attack): 攻击者会使用包含数百万常用词汇、短语和常见替换模式的“字典”来快速破解密码。
      • 算力威胁: 即使是看起来复杂的密码,在专用破解硬件面前也可能在很短时间内被暴力破解。
    • 生物识别 (Biometric): 虽是趋势,但目前在服务器认证领域标准不一,尚未普及。
    • 服务器认证的最佳选择: SSH 密钥 (SSH Key):
      • 极其安全,随机性极高,在可预见的未来内几乎不可能被猜到或破解。
  • 密码管理器:个人安全的基石
    • 强烈推荐: 使用密码管理器,如 1Password 或 KeePass。
    • 核心原则: 不要自己创建和记忆密码。让密码管理器为你生成真正随机、高强度的密码,你只需要记住一个主密码。
    • 特别提醒: 因多次曝出安全问题,不推荐使用 LastPass
  • 哈希 (Hashing):现代安全的基础
    • 什么是哈希?
      • 它是一个单向的数学过程。
      • 任意长度的输入数据(如一个密码、一个文件)通过一个哈希函数,转换成一个固定长度的、看起来随机的字符串(称为哈希值)。
      • 流程: 输入 -> [哈希函数] -> 输出 (哈希值)
    • 哈希函数的演进与问题:
      • MD5:
        • 一个早期的、现已不安全的哈希算法。
        • 致命缺陷: 相同的输入永远产生相同的输出。例如,"password" 这个词的 MD5 哈希值是全球唯一的。
        • 这导致了 彩虹表 (Rainbow Table) 攻击:攻击者可以预先计算好亿万个常用密码的 MD5 哈希值存成一个速查表。一旦获取了数据库中的 MD5 哈希,只需查表就能反推出原始密码。
    • 命令行演示:
      • 使用 openssl md5 [文件名] 可以计算一个文件的 MD5 哈希值。
      • 演示表明,无论谁来计算,只要文件内容是 "password",得到的 MD5 哈希值都是完全一样的。

14-hashing-with-salt

  • 更强的哈希算法
    • SHA1: 比 MD5 生成的哈希值更长,熵更高。但它同样有确定性的问题(相同输入永远产生相同输出),因此也容易受到彩虹表攻击,现已不被认为是安全的。
    • SHA256: 目前加密领域的黄金标准。哈希值非常长(65 个字符),大大增加了破解和制作彩虹表的难度。但其根本的确定性问题依然存在。
  • 终极解决方案: 加盐 (Salting)
    • 什么是盐 (Salt)?
      • 一个为每个用户、每次哈希独立生成的、随机的字符串。它没有任何特殊含义,目的就是引入随机性。
    • 加盐的工作流程:
      • hash_result = HASH_FUNCTION( password + salt )
      • 当用户注册时,系统生成一个随机的 Salt,将它和用户的密码拼接后,再进行 SHA256 哈希计算。
      • 最终,数据库中存储的是哈希结果和这个随机的 Salt
    • 加盐的效果:
      • 即使两个用户设置了完全相同的密码(例如 "123456"),因为他们的 Salt 是不同的,最终存储在数据库中的哈希值也完全不同。
      • 这彻底摧毁了彩虹表攻击的有效性,因为攻击者无法预先计算一个包含所有可能 Salt 组合的彩虹表。
  • 安全准则
    1. 永远不要在生产环境中存储明文密码。
    2. 永远不要使用 MD5 或任何未加盐的哈希算法来存储密码。
    3. 永远不要自己发明加密算法 (Don't roll your own crypto)! 加密学是一个极其复杂的专业领域,自己写的算法几乎 100% 存在漏洞。请始终使用经过行业验证的标准库和算法。
  • 哈希的其他重要应用
    • 文件完整性校验:
      • 开源软件或大文件下载时,网站通常会提供文件的 MD5 或 SHA256 哈希值。下载后,你可以在本地计算文件的哈希值,与官方提供的值进行比对。如果一致,就证明文件在下载过程中没有被损坏或篡改。
    • 加密货币 (Cryptocurrencies):
      • 整个区块链技术都建立在哈希之上。
      • 比特币 (Bitcoin) 的“挖矿”本质上就是在寻找一个符合特定条件的 SHA256 哈希值。
    • 勒索软件 (Ransomware):
      • 恶意软件使用强大的哈希算法(加上只有攻击者知道的 Salt/Key)来加密你的文件,使其变成一堆无意义的数据。然后向你索要赎金,以换取解密的密钥。

15-setup-ssh-keys-for-login

  • SSH 和公钥加密 (Public Key Cryptography)
    • SSH (Secure Socket Shell) 是一种网络协议,用于安全地访问远程计算机,其安全性基于公钥加密体系。
    • SSH 密钥对 (Key Pair) 包含两个部分:
      1. 公钥 (Public Key):
        • 可以安全地分享给任何人或任何服务(如 GitHub, DigitalOcean)。
        • 它的作用是加密数据。
      2. 私钥 (Private Key):
        • 必须像最高机密一样严格保管,绝不能泄露。
        • 它的作用是解密由对应公钥加密的数据。
    • 工作原理:
      • 当你连接服务器时,通信内容被公钥加密。只有持有私钥的你才能解密这些信息。这确保了即使通信被拦截,中间人也无法读懂内容。
  • 创建 SSH 密钥
    • 命令: ssh-keygen
    • 标准实践:
      1. 进入存放 SSH 密钥的目录: cd ~/.ssh
      2. 运行创建命令: ssh-keygen
      3. 为密钥文件命名 (例如:fsfe,代表 Full Stack for Frontend)。这可以避免覆盖默认的 id_rsa 密钥。
      4. 为私钥设置一个密码 (passphrase)。这是一个好习惯,它为你的私钥提供了额外的安全层。如果有人偷走了你的私钥文件,没有这个密码也无法使用它。
    • 命令执行后,会生成两个文件:fsfe (私钥) และ fsfe.pub (公钥)。
  • 在 DigitalOcean 上配置 SSH 密钥
    1. 复制公钥: 使用 cat ~/.ssh/fsfe.pub 命令查看并复制你的公钥的全部内容。
    2. 添加到 DigitalOcean: 在 DigitalOcean 创建 Droplet 的界面中,选择 "SSH Key" 作为认证方式,点击 "New SSH Key",将复制的公钥粘贴进去,并为这个密钥起一个可识别的名称。
    3. 创建 Droplet: 选中刚刚添加的密钥,然后完成 Droplet 的创建。DigitalOcean 会自动将你的公钥配置到新服务器上。
  • 通过 SSH 登录服务器
    1. 从 DigitalOcean 控制台获取你服务器的 IP 地址
    2. 登录命令格式: ssh -i [私钥文件路径] [用户名]@[服务器IP地址]
      • i (identity_file): 用于指定本次连接使用的私钥文件。
      • [用户名]: 所有新创建的 Ubuntu 服务器的默认管理员用户是 root
    3. 完整命令示例: ssh -i ~/.ssh/fsfe [email protected]
    4. 首次连接: 终端会显示一个关于主机真实性的警告,这是正常的。输入 yes 并回车,表示你信任这台服务器。
    5. 如果一切正确,你将成功登录到你的远程服务器,并看到服务器的命令行提示符。

16-ssh-key-recap

  • SSH 密钥创建与使用回顾
    • 本节首先回顾了完整的流程:
      1. 使用 ssh-keygen 创建密钥对 (公钥 .pub 和私钥)。
      2. 使用 cat 查看并复制公钥内容。
      3. 在 DigitalOcean 界面添加公钥。
      4. 使用 ssh -i [私钥路径] root@[IP地址] 命令登录。
  • known_hosts 文件:信任的凭证
    • 作用: 当你首次通过 SSH 连接一个新服务器并输入 yes 确认信任后,该服务器的公钥指纹(一种身份标识)会被记录在本地的 ~/.ssh/known_hosts 文件中。
    • 安全机制:
      • 当你再次连接同一 IP 地址的服务器时,SSH 会用服务器发来的密钥指纹与 known_hosts 文件中的记录进行比对。
      • 如果指纹不匹配,SSH 会立即中断连接并发出严重警告。这通常意味着两种可能:服务器重装了系统(正常情况),或者你正在遭受中间人攻击(危险情况)。
    • 退出服务器: 在服务器的命令行中输入 exit 命令即可断开连接。
  • 简化 SSH 登录流程 (避免每次都输入 i)
    • 目标: 让 SSH 自动识别并使用我们创建的密钥,实现 ssh root@[IP地址] 直接登录。
    • 方法: 使用 SSH Agent 和 Keychain (macOS)
      1. SSH Agent: 一个在后台运行的程序,它可以安全地缓存你的私钥。
      2. 添加私钥到 Agent:
        • 运行命令: ssh-add --apple-use-keychain ~/.ssh/fsfe
        • ssh-add: 将密钥添加到 Agent 的命令。
        • -apple-use-keychain: 这是一个 macOS 特有的选项,它会让你输入的私钥密码(passphrase)安全地存储在系统的钥匙串(Keychain)中,这样你就不需要每次都重新输入密码了。
      3. 效果: 一旦密钥被添加到 Agent,ssh 命令会自动尝试使用 Agent 中缓存的所有密钥进行登录,从而无需再手动指定 i 参数。
    • 其他简化方法 (SSH Config)
      • 通过编辑 ~/.ssh/config 文件,可以为不同的主机创建别名并指定默认的密钥、用户名等,进一步简化登录。
      • 例如,在 config 文件中可以设置:只要连接到某个 IP,就自动使用 fsfe 这个私钥。

17-how-the-internet-works

  • 即将进行的重要步骤
    • 购买域名。
    • 将域名连接到我们刚刚创建的服务器。
    • 深入理解互联网的工作原理。
  • “互联网如何工作?”——一个经典的面试问题
    • 讲师喜欢这个问题,因为它是一个开放式问题,能展示面试者的知识广度、深度、好奇心和思考能力。
    • 它没有唯一正确答案,你可以从技术栈的任何层面(操作系统、网络、服务器、浏览器等)来回答。
  • 互联网的核心精神
    • 合作 (Cooperation) 与规则 (Rules):互联网并非由单一实体设计或控制,而是由无数个人、组织和公司共同遵循一系列标准和协议(如 TCP/IP, SSH)而形成的全球网络。这种自发的协作是互联网最神奇的地方。
  • 一个简化的请求流程图
    • 你的电脑 -> 路由器 (Router) (将内部私有 IP 转换成外部公共 IP) -> ISP (互联网服务提供商) -> 骨干网 (Backbone ISP) (互联网的高速公路) -> 数据中心 (Data Center) -> 负载均衡器 (Load Balancer) -> 你的服务器
    • 这只是一个高度简化的模型,实际过程涉及更多技术(如 BGP、modem、跨国海底光缆等)。
  • 关键术语
    • Internet (互联网): A network of networks.
    • Intranet (内联网): 一个私有网络,通常在公司或组织内部使用。
    • LAN (局域网 Local Area Network): 在小范围内(如一个房间或一栋楼)连接的设备。
    • WAN (广域网 Wide Area Network): 连接多个 LAN 的网络。
  • IP 地址 (Internet Protocol Address)
    • IP (Internet Protocol): 一套定义设备如何在网络上寻址和通信的规则。
    • IP 地址: 分配给每个联网设备的唯一标签。
    • IP 地址的所有权: 互联网上的 IP 地址块实际上是由公司(如 Google, Cloudflare)和政府机构购买和拥有的。
    • IPv4 vs. IPv6:
      • IPv4: 早期的标准,提供约 43 亿个地址。这些地址在很多年前就已耗尽。
      • IPv6: 新的标准,提供了几乎无限的地址数量 (340 undecillion),解决了地址耗尽的问题。

18-network-tools-exercise

  • 网络诊断工具 (Network Tools)
    • 这些工具可以帮助我们“掀开”网络的神秘面纱,了解其底层是如何连接和工作的。
  • ping
    • 作用: 向一个主机(域名或 IP 地址)发送一个小的数据包,并等待其响应,以此来测试该主机是否在线以及网络的延迟情况。
    • 用途: 最快速、最常用的检查网络连通性的方法。例如,如果网站打不开,可以先 ping google.com 来判断是网站本身的问题还是自己的网络问题。
    • 注意: 出于安全或策略考虑,有些服务器(如 frontendmasters.com)可能会被配置为不响应 ping 请求。
  • traceroute
    • 作用: 显示你的计算机到目标主机之间数据包所经过的所有“跳” (hops),即所有的路由器和网络节点。
    • 用途:
      • 直观地展示了互联网的复杂路径。一个请求可能需要经过 20-30 个甚至更多的中间节点才能到达目的地。
      • 可以帮助诊断网络问题到底出在哪一“跳”。通过查看每一跳的延迟时间,可以定位到是本地网络、ISP 还是目标服务器所在的网络出现了瓶颈。
    • 示例解读: traceroute google.com
      • 第一跳通常是你的本地路由器 (如 192.168.x.x)。
      • 接着会经过你的 ISP (互联网服务提供商) 的多个内部路由器。
      • 然后进入互联网的骨干网。
      • 最后进入目标服务(如 Google)的数据中心网络,并最终到达服务器。
  • netstat (Network Statistics)
    • 作用: 显示你电脑上所有网络连接、路由表、接口统计、开放端口等详细信息。
    • 用途:
      • 直接运行 netstat 会输出海量信息,通常需要配合参数和管道使用。
      • netstat -lt | less:
        • l: 显示正在监听 (listening) 的端口。
        • t: 只显示 TCP 连接。
        • | less: 将输出通过管道传给 less 命令,以便分页查看。
      • 可以让你看到操作系统后台正在运行的所有网络相关的进程和它们使用的端口。

19-internet-networking-terminology

  • 网络协议:TCP vs. UDP
    • 网络通信的两种主要协议,它们决定了数据如何被传输。
  • TCP (Transmission Control Protocol)
    • 特点: 可靠、面向连接、有顺序
    • 工作方式:
      • 三次握手 (Three-way Handshake): 在传输数据前,客户端和服务器会进行三次通信 (SYN -> SYN/ACK -> ACK) 来建立一个可靠的连接。
      • 错误校验与重传: TCP 会确保所有数据包都按顺序、无错误地到达。如果发现有数据包丢失或损坏,它会自动请求重传。
    • 适用场景: 绝大多数互联网应用,如网页浏览 (HTTP/HTTPS)、文件下载 (FTP)、邮件发送 (SMTP)。在这些场景中,数据的完整性和正确性至关重要,哪怕一个字节出错都可能导致整个文件或网页损坏。
    • 缺点: 因为要进行握手和错误校验,所以速度相对较慢,开销较大。
  • UDP (User Datagram Protocol)
    • 特点: 不可靠、无连接、“尽力而为” (best-effort)
    • 工作方式:
      • 它只是简单地将数据包“扔”向目标地址,不建立连接,不保证数据包是否到达、是否按顺序到达,也不进行错误校验。就像寄平信,寄出去就不管了。
    • 适用场景: 对实时性要求高,但对少量数据丢失不敏感的场景。最典型的例子是视频直播、在线游戏、VoIP(网络电话)
      • 在视频通话时,如果丢失一两帧画面,视频可能会瞬间模糊一下,但这比为了等待重传而导致整个画面卡住要好得多。
    • 优点: 速度快,开销小。
  • ICMP (Internet Control Message Protocol)
    • 一个用于在网络设备之间传递控制消息的协议。
    • 它不传输用户数据,而是用于报告错误(如“目标主机不可达”)或进行网络诊断。
    • 我们之前使用的 pingtraceroute 命令就是基于 ICMP 工作的。
  • 数据包 (Packet)
    • 网络上传输的最小数据单位。任何大的数据(如一个网页、一个视频文件)在传输前都会被分割成无数个小的数据包,每个数据包都包含了目标地址、源地址等信息。

20-dns-urls

  • DNS (Domain Name System)
    • 核心作用: 互联网的 “电话簿”。它负责将人类容易记忆的域名 (Domain Name)(如 frontendmasters.com)翻译成机器能够理解的 IP 地址(如 104.18.39.229)。
    • Nameservers (域名服务器): 存储这些“域名-IP 地址”映射关系的专用服务器。它们在全球范围内分布,并相互同步信息。
    • 查询流程:
      1. 你在浏览器输入 google.com
      2. 浏览器向 DNS Nameserver 发出查询:“google.com 的 IP 地址是什么?”
      3. Nameserver 返回对应的 IP 地址。
      4. 浏览器使用这个 IP 地址与 Google 的服务器建立连接。
  • DNS 记录类型
    • A 记录: 将域名映射到一个 IPv4 地址。这是最基本、最常用的一种记录。
    • CNAME (Canonical Name) 记录: 将一个域名映射到另一个域名(别名)。例如,可以将 blog.example.com 指向 example.wordpress.com
  • DNS 查询工具
    • nslookup (Name Server Lookup):
      • nslookup frontendmasters.com
      • 一个快速查询域名对应 IP 地址的工具。
    • dig (Domain Information Groper):
      • dig frontendmasters.com
      • 功能更强大、输出信息更详细的 DNS 查询工具。它可以显示 A 记录、CNAME 记录、MX 记录(邮件服务器)等各种信息。
      • 权威性 (Authoritative): dig 的结果会告诉你查询的 Nameserver 是否是该域名的“权威”服务器(即最终记录的持有者)。
  • URL (Uniform Resource Locator) 结构
    • 一个标准的 URL 由多个部分组成,例如:https://dev.example.com/path/to/resource?key=value
      • Scheme/Protocol: https:// (协议)
      • Subdomain: dev (子域名)
      • Domain: example (域名)
      • TLD (Top-Level Domain): .com (顶级域名)
      • Path: /path/to/resource (路径)
      • Query Parameters: ?key=value (查询参数)
  • 域名抢注 (Cybersquatting) 和 TLD
    • ICANN 是负责管理全球域名的主要机构。
    • 公司通常会购买与其品牌相关的所有可能的域名(包括拼写错误的、负面的),以防止恶意使用。
    • 域名争议通常由 ICANN 仲裁,但过程复杂且往往对拥有更多法律资源的大公司有利。

21-buying-a-domain-name

  • 购买域名
    • 推荐注册商 (Registrar): Namecheap
      • 原因:
        • 价格便宜,选择多样。
        • 提供了丰富的功能(如 SSL, 邮件转发),但不会强制推销。
        • 相比之下,Google Domains 价格更高且推销感更强。
    • 域名安全
      • 极其重要: 你的域名注册商账户必须使用强密码并开启双因素认证 (2FA)
      • 如果域名账户被盗,攻击者可以将你的网站指向恶意站点,造成巨大损失。
    • ICANN 费用: 购买或续费任何域名,都包含一笔支付给 ICANN 的小额费用,用于维护全球 DNS 系统的运行。
  • 将域名连接到 DigitalOcean 服务器 (核心步骤)
    • 目标: 让 jemstack.lol 这个域名指向我们在 DigitalOcean 上创建的服务器的 IP 地址。
    • 方法: 将域名的域名服务器 (Nameservers) 从 Namecheap 更改为 DigitalOcean。
      1. 在 Namecheap 中更改 Nameservers:
        • 登录 Namecheap,找到你购买的域名,进入管理页面。
        • 在 "Nameservers" 部分,选择 "Custom DNS"。
        • 填入 DigitalOcean 的三个 Nameserver 地址:
          • ns1.digitalocean.com
          • ns2.digitalocean.com
          • ns3.digitalocean.com
        • 保存更改。
      2. 在 DigitalOcean 中添加域名:
        • 在 DigitalOcean 的控制面板中,找到你的 Droplet,选择 "Add a domain"。
        • 输入你的域名 (jemstack.lol)。
      3. 在 DigitalOcean 中创建 DNS 记录:
        • DigitalOcean 会自动为你跳转到 DNS 管理页面。你需要创建两条 A 记录
          • 记录一: 主机名 (Hostname) 填 @,指向你的 Droplet。
            • @ 符号代表根域名,即 jemstack.lol 本身。
          • 记录二: 主机名填 www,也指向你的 Droplet。
            • 这使得 www.jemstack.lol 也能访问到你的服务器。
  • DNS 传播 (Propagation)
    • 更改 Nameserver 和 DNS 记录后,这些变更需要时间在全球的 DNS 服务器之间同步。这个过程称为传播
    • 传播时间可能从几分钟到几个小时不等,在此期间你的域名可能暂时无法解析。
  • 邮件转发 (Email Forwarding)
    • Namecheap 提供免费的邮件转发服务。
    • 你可以设置一个 "Catch-all" 规则,将发送到 [任何字符]@yourdomain.com 的邮件全部转发到你的个人邮箱(如 Gmail)。
    • 这是一个非常实用的功能,可以为不同的服务使用不同的邮箱地址(如 [email protected], [email protected]),方便追踪垃圾邮件来源。
    • 注意: 邮件转发只能收,不能发。如果想用自定义域名发邮件,需要购买付费的邮件托管服务。

22-update-restart-server

  • 服务器初始配置 (First-time Server Setup)
    • 拿到一台全新的服务器后,首要任务是进行基础的更新和安全设置。
  • 步骤概览
    1. 更新所有已安装的软件包。
    2. 重启服务器以应用更新。
    3. 创建一个新的、拥有管理员权限的普通用户。
    4. 为新用户配置 SSH 登录。
    5. 禁用 root 用户直接登录
  • 实际操作
    1. 登录服务器:
      • 使用 ssh root@[你的服务器IP] 登录。
    2. 更新软件:
      • apt update: 这个命令会从 Ubuntu 的软件源同步最新的软件包列表信息。它会真正安装任何东西,只是刷新本地缓存。
      • apt upgrade: 这个命令会根据 update 获取到的最新列表,将你服务器上所有已安装的软件包升级到最新版本。
      • 在升级过程中,可能会弹出一些确认窗口(例如关于内核更新),通常直接按回车或选择默认选项 "OK" 即可。
    3. 重启服务器:
      • shutdown -r now:
        • shutdown: 关机命令。
        • r: 表示重启 (reboot)。
        • now: 表示立即执行。
      • 执行后,你的 SSH 连接会中断。需要等待一两分钟让服务器重启完成。
    4. 重新登录:
      • 待服务器重启后,再次使用 ssh root@[你的服务器IP] 登录,准备进行下一步的用户创建。
  • 重要概念
    • apt (Advanced Package Tool): Ubuntu 及其衍生版(如 Debian)的默认包管理器,用于安装、更新和卸载软件。

23-create-a-user

  • 为什么不能使用 root 用户?
    • root 是 Linux/Unix 系统中的超级管理员,拥有至高无上的权限。
    • root 用户的操作不会有任何确认或警告。一个错误的命令(如 rm -rf /)可以瞬间摧毁整个系统。
    • 为了安全,日常操作绝对不能直接使用 root 用户。
  • sudo:安全的管理员权限
    • sudo (Super User Do): 一个命令,它允许普通用户临时root 的身份执行单条命令。
    • 核心原则: 默认以权限最小的普通用户身份操作,只在绝对必要时才使用 sudo 来执行需要管理员权限的任务。
  • 创建新用户并赋予 sudo 权限
    1. 创建用户: (当前仍在 root 用户下操作)
      • adduser jem (将 jem 替换为你想要的用户名)
      • 系统会提示你为新用户设置密码,并填写一些可选的个人信息(可以直接按回车跳过)。
    2. 添加到 sudo:
      • usermod -aG sudo jem
      • usermod: 修改用户属性的命令。
      • aG: a 表示追加(append),G 表示指定组(group)。合起来就是将用户追加到某个组。
      • sudo: 在 Ubuntu 中,所有属于 sudo 组的成员都自动拥有 sudo 权限。
    3. 切换到新用户:
      • su jem (su 代表 Switch User)
      • 你会发现命令行提示符从 # (root) 变成了 $ (普通用户),并且你现在位于该用户的主目录(如 /home/jem)。
    4. 测试 sudo 权限:
      • sudo cat /var/log/auth.log
      • 尝试读取一个只有 root 才能访问的系统日志文件。如果系统提示你输入 jem 用户的密码,并且成功显示了文件内容,说明 sudo 配置成功。
  • 为新用户配置 SSH 登录
    • 目标: 让我们能直接用 ssh jem@[IP地址] 登录,而不仅仅是 ssh root@[IP地址]
    1. (当前在 jem 用户下) 创建 .ssh 目录: mkdir ~/.ssh
    2. (当前在 jem 用户下) 创建空的授权密钥文件: touch ~/.ssh/authorized_keys
    3. 将你的公钥添加到 authorized_keys 文件中:
      • 方法: 在本地电脑的终端上,使用 cat ~/.ssh/fsfe.pub 查看并复制你的公钥。
      • 回到服务器的终端(此时是 jem 用户),使用 Vim 打开文件: vim ~/.ssh/authorized_keys
      • 将刚刚复制的公钥粘贴进去,然后保存退出 (:wq)。
    4. 测试新用户登录:
      • 本地电脑的终端上,尝试用新用户登录: ssh jem@[你的服务器IP]
      • 如果一切顺利,你应该能不输密码直接登录成功。
      • 注意: 即使是同一个 SSH 密钥,你也可以用它来登录服务器上的不同用户,只要你将该公钥添加到了对应用户的 authorized_keys 文件中。

24-file-permissions

  • 配置 SSH,增强安全性
    • 目标: 彻底禁用 root 用户通过 SSH 直接登录,并确保我们新用户的 SSH 配置正确无误。
  • 查看系统日志,了解安全威胁
    • 命令: sudo cat /var/log/auth.log
    • 这个日志文件记录了所有尝试登录你服务器的行为。
    • 你会惊人地发现,即使是一台刚上线几分钟的新服务器,也会立刻遭到来自世界各地的自动化脚本的攻击。它们会不停地尝试用常见的用户名(如 root, admin, user)和密码来破解你的服务器。
    • 这直观地展示了为什么禁用密码登录禁用 root 登录是至关重要的安全措施。
  • 设置 authorized_keys 文件权限
    • SSH 服务对密钥文件的权限非常敏感,如果权限设置得过于宽松,它会拒绝使用这些密钥。
    • chmod (Change Mode): 修改文件或目录权限的命令。
    • 正确权限: chmod 644 ~/.ssh/authorized_keys
      • 644 (rw-r--r--): 这是一个八进制数,代表了三组权限:
        • 6: 文件所有者(你自己)拥有读 (r) 和写 (w) 权限。
        • 4: 文件所属组拥有只读 (r) 权限。
        • 4: 其他所有人拥有只读 (r) 权限。
  • 禁用 Root 登录
    • 警告: 这是整个设置过程中最危险的一步。在执行此操作前,必须确保你已经可以用新创建的用户成功通过 SSH 登录。否则,你将把自己永久地锁在服务器之外,只能删除服务器重来。
    1. 编辑 SSH 配置文件:
      • sudo vim /etc/ssh/sshd_config
      • sshd_config 是 SSH 服务(daemon)的配置文件。
    2. 修改配置:
      • 在文件中找到 PermitRootLogin 这一行。
      • 将它的值从 yes (或 prohibit-password) 修改为 no
      • PermitRootLogin no
    3. 保存并退出 (:wq)。
  • 重启 SSH 服务
    • 修改配置文件后,必须重启对应的服务才能让更改生效。
    • 命令: sudo service sshd restart
  • 最终测试
    1. 从服务器 exit 退出登录。
    2. 尝试再次以 root 用户登录: ssh root@[IP地址]。此时,服务器应该会直接拒绝你的连接 (permission denied)。
    3. 尝试以你的新用户登录: ssh jem@[IP地址]。此连接应该仍然可以正常工作。
    4. 至此,服务器的基础安全设置完成。

25-setup-nginx-web-server

  • Web 服务器: Apache vs. Nginx
    • Web 服务器是处理来自互联网的传入请求并将其路由到正确位置(如应用程序、文件)的软件。
    • Nginx (发音为 Engine-X):
      • 一个高性能、轻量级的 Web 服务器、反向代理 (reverse proxy)、负载均衡器等。
      • 用 C 语言编写,速度极快,性能优于 Node.js。
      • 配置灵活,是现代 Web 开发的首选。
    • Apache:
      • 另一个流行的 Web 服务器,功能强大,对 PHP、Java 等支持良好。
    • 为什么需要 Nginx?
      • 虽然 Node.js 自己可以创建服务器直接响应请求,但将 Nginx 放在前面作为网关是最佳实践。
      • 专业分工: Nginx 专门处理网络流量、SSL 加密、压缩、缓存、负载均衡等任务,效率远高于在 Node.js 中自己实现这些功能。
  • 安装和启动 Nginx
    1. 安装:
      • sudo apt install nginx
    2. 启动服务:
      • sudo service nginx start
    3. 验证:
      • 在浏览器中直接访问你的服务器 IP 地址(或已经解析的域名)。
      • 如果一切顺利,你应该能看到 "Welcome to nginx!" 的默认页面。这标志着你的服务器现在正式对外提供 Web 服务了。
  • Nginx 默认配置剖析
    • Nginx 的配置文件位于 /etc/nginx/ 目录下。
    • sites-availablesites-enabled 是 Nginx 用来管理多个网站配置的目录结构。
    • 查看默认配置: less /etc/nginx/sites-available/default
      • root /var/www/html;: 指定了网站文件的根目录。刚刚看到的 "Welcome" 页面就存放在这里。
      • index index.html index.htm ...;: 指定了当访问一个目录时,Nginx 会默认查找的文件名。
      • location / { ... }: 定义了如何处理对根路径 (/) 的请求。
  • 修改默认网页
    1. 进入网站根目录: cd /var/www/html
    2. sudo vim index.html: 创建一个新的 index.html 文件(需要 sudo 权限)。
    3. 输入 "Hello world" 并保存。
    4. 刷新浏览器,你应该能看到自己创建的 "Hello world" 页面。Nginx 会优先加载 index.html
  • 准备 Node.js 环境
    • 目标: 让 Nginx 将请求代理到我们的 Node.js 应用。
    • 安装最新版 Node.js:
      • Ubuntu 的 apt 仓库中的 Node.js 版本通常很旧。我们需要从 nodesource 官方源来安装最新版本。
      • 运行命令: curl -fsSL <https://deb.nodesource.com/setup_19.x> | sudo -E bash - (这个命令会添加新的软件源)
      • 然后安装: sudo apt-get install -y nodejs
      • 验证版本: node --version (应显示 v19.x.x)

26-setup-proxy-pass

  • 安装和配置 Node.js 环境

    • 安装 Node.js (续):
      • 上一步添加了 Node.js 的新软件源,现在通过 sudo apt-get install -y nodejs 正式安装。
      • 使用 node --version 确认已安装最新版本(如 19.x)。
    • 安装 Git: sudo apt install git (通常已预装,但确认一下没坏处)。
  • 创建 Node.js 应用程序目录

    1. 更改目录所有权 (重要):
      • 默认情况下,/var/www 目录由 root 用户拥有,这意味我们每次创建或修改文件都需要 sudo,非常不便。
      • sudo chown -R jem:jem /var/www (将 jem 替换为你的用户名):
        • chown: Change Owner 命令。
        • R: 递归 (Recursive),将 /var/www 及其所有子目录和文件的所有权都更改。
        • jem:jem: 用户名:用户组
      • 执行此操作后,你就可以在该目录下自由地创建和编辑文件,无需 sudo
    2. 创建应用目录:
      • mkdir /var/www/app
    3. 初始化项目:
      • cd /var/www/app
      • git init: 初始化 Git 仓库。
      • npm init: 初始化 Node.js 项目,生成 package.json 文件(一路回车使用默认值即可)。
      • touch app.js: 创建主应用程序文件。
  • 编写 Node.js 服务器

    • vim app.js 打开文件,并写入一个非常简单的服务器代码。

    • 这次,我们直接写入响应,而不读取文件

      const http = require("http");
      
      http
        .createServer((req, res) => {
          res.write("On the way to being a full stack engineer");
          res.end(); // 必须调用 end() 来结束响应
        })
        .listen(3000, () => {
          console.log("Server started on port 3000");
        });
      
  • 配置 Nginx 反向代理

    • 目标: 让所有访问服务器 80 端口的请求,都由 Nginx 转发到我们内部运行在 3000 端口的 Node.js 应用上。
    1. 创建新的 Nginx 配置文件:
      • 我们将创建一个新的配置文件,而不是修改 default,这样更清晰、更易于管理。
      • sudo vim /etc/nginx/sites-enabled/fsfe (文件名可自定,推荐用域名命名)。
    2. 编写虚拟主机配置:
      • 这是一个精简版的配置,只包含我们需要的核心功能。

27-virtual-server-pm2

  • 编写 Nginx 虚拟主机配置 (Virtual Server)

    • /etc/nginx/sites-enabled/fsfe 文件中,写入以下配置:

      server {
          listen 80 default_server;
          listen [::]:80 default_server;
      
          server_name jemstack.lol; // 替换成你的域名
      
          location / {
              proxy_pass <http://127.0.0.1:3000>;
          }
      }
      
      
    • 配置详解:

      • listen 80 default_server;: 监听 IPv4 的 80 端口,并将其设为默认服务器。
      • listen [::]:80 default_server;: 监听 IPv6 的 80 端口。
      • server_name: 指定此配置块处理哪个域名的请求。
      • location / { ... }: 定义如何处理根路径 (/) 的请求。
      • proxy_pass <http://127.0.0.1:3000>;: 这是核心。它告诉 Nginx,将所有收到的请求原封不动地转发 (pass) 到运行在本地 3000 端口的服务(即我们的 Node.js 应用)。
  • 调整 Nginx 主配置

    1. 我们需要告诉 Nginx 加载我们新的配置文件,而不是默认的。
    2. sudo vim /etc/nginx/nginx.conf
    3. 找到 include /etc/nginx/sites-enabled/*; 这一行,并将其修改为明确指向我们的文件:
      • include /etc/nginx/sites-enabled/fsfe;
  • 验证和重启 Nginx

    1. 验证配置语法:
      • sudo nginx -t
      • 这个命令会检查所有 Nginx 配置文件是否有语法错误。如果显示 "syntax is ok" 和 "test is successful",则可以安全地重启。
    2. 重启 Nginx 服务:
      • sudo service nginx restart
  • 启动 Node 应用并测试

    1. 进入应用目录: cd /var/www/app
    2. 启动 Node 服务器: node app.js
    3. 在浏览器中访问你的域名 (例如 http://jemstack.lol)。
    4. 如果一切正常,你应该能看到 Node.js 应用输出的 "On the way to being a full stack engineer"。
  • 使用 PM2 持久化 Node 应用

    • 问题: 直接用 node app.js 启动的应用,一旦你关闭 SSH 连接,它就会被终止。
    • 解决方案: PM2 (Process Manager 2)
      • PM2 是一个 Node.js 的进程管理器,它可以让你的应用在后台持续运行,并在应用崩溃时自动重启。
    1. 全局安装 PM2:
      • sudo npm install -g pm2
    2. 使用 PM2 启动应用:
      • pm2 start app.js --watch
      • -watch: 这个参数会让 PM2 监视文件变化,并在文件被修改时自动重启应用(类似 nodemon)。
    3. 常用 PM2 命令:
      • pm2 list: 查看所有正在由 PM2 管理的应用的状态。
      • pm2 stop [app_name|id]: 停止应用。
      • pm2 restart [app_name|id]: 重启应用。
      • pm2 logs: 查看日志。
    4. 设置开机自启:
      • pm2 save: 保存当前的应用列表。
      • pm2 startup: 生成一个开机自启的脚本命令。复制并运行这个命令。
      • 这样设置后,即使服务器重启,PM2 也会自动启动你保存的应用。

28-git-exercise

  • 回顾与总结
    • 我们已经完成了从购买域名和服务器,到手动配置 Nginx 和 Node.js,再到让应用上线运行的全过程。
    • 至此,你已经掌握了成为一个全栈工程师所需的核心基础设施技能。
  • Git 工作流:连接本地开发与服务器部署
    • 目标: 建立一个工作流,使我们可以在本地舒适地编写代码,然后通过 Git 将代码部署到服务器上,而不是一直在服务器上用 Vim 编辑。
    • 家庭作业任务:
      1. GitHub 上创建一个新的仓库。
      2. 在你的服务器上,为 GitHub 创建一个新的专用 SSH 密钥(例如 gh_key)。
      3. 将这个新密钥的公钥添加到你的 GitHub 账户中。
      4. 在你的服务器上,为之前创建的本地 Git 仓库添加远程地址 (remote),指向你在 GitHub 上创建的仓库。
      5. 配置服务器的 SSH,使其在连接 github.com 时使用我们新创建的 gh_key
      6. 服务器上,将你的代码 push 到 GitHub 仓库。
  • 后续课程展望
    • 接下来的部分将涉及更多实战和高级主题:
      • 创建子域名 (subdomain)。
      • 设置和连接数据库。
      • 搭建一个简单的 CI/CD 管道 (持续集成/持续部署)。
      • 更深入地探讨防火墙和权限。
      • 使用 Cron Job 执行定时任务。

29-version-control-git

  • Git 与哈希

    • Git 的底层就是基于哈希的。每一次提交 (commit) 都会生成一个唯一的 SHA-1 哈希值,这确保了版本历史的完整性和不可篡改性。
    • Git 的发明者是 Linus Torvalds,他也是 Linux 内核的发明者。
  • 完成家庭作业:配置 Git 工作流

    • 本节详细演示了上一节留下的家庭作业,目的是打通从服务器到 GitHub 的代码推送流程。

    • 关键步骤与易错点:

      1. 创建 SSH 密钥:

        • 在服务器上,进入 ~/.ssh 目录。
        • 运行 ssh-keygen 并命名为 gh_key
      2. 在 GitHub 添加公钥:

        • 在 GitHub 的 "Settings" -> "SSH and GPG keys" 中,添加 gh_key.pub 的内容。
      3. 在服务器上添加远程仓库:

        • 进入你的应用目录 /var/www/app
        • 运行 git remote add origin [email protected]:your-username/your-repo.git
      4. 配置 SSH 以使用特定密钥 (最关键的一步):

        • 在服务器上,编辑 ~/.ssh/config 文件 (如果不存在则创建)。

        • 注意: 不要使用 sudo,因为这是用户级别的配置。

        • 添加以下内容:

          Host github.com
              HostName github.com
              User git
              IdentityFile ~/.ssh/gh_key
          
          
        • 这个配置告诉 SSH,当它尝试连接 github.com 时,应该使用 gh_key 这个私钥文件。

      5. 测试与推送:

        • git push origin main (或 master) 推送代码。
  • 疑难解答 (In Case You Get Stuck)

    • 测试 SSH 连接:
      • ssh -T -v [email protected]
      • T: 不分配伪终端,只测试连接。
      • v: 详细模式 (Verbose),会打印出 SSH 连接的每一步,非常适合调试。它可以告诉你 SSH 正在尝试使用哪个密钥文件,以及在哪一步失败了。
    • 在 Vim 中保存只读文件:
      • 如果你忘记用 sudo 打开一个需要权限的文件并进行了编辑,可以用这个命令强制保存::w !sudo tee %
        • :w !...: 将缓冲区内容通过管道传递给外部命令。
        • sudo tee: 以 root 权限运行 tee 命令。
        • %: 在 Vim 中代表当前文件名。
    • 强制杀死进程:
      • pkill node: 杀死所有名为 node 的进程。当你无法通过 Ctrl+C 正常退出一个程序时非常有用。
    • 查看数字权限:
      • stat -c "%a %n" *: 这条命令可以以数字形式(如 644)显示当前目录下所有文件的权限。

30-security

  • 安全:永恒的主题
    • 再次查看服务器的认证日志 sudo cat /var/log/auth.log
    • 即使服务器只上线了 24 小时,日志里也充满了来自世界各地的、持续不断的自动攻击尝试。
    • 这再次强调了安全不是可选项,而是服务器管理的首要任务。
  • 服务器被入侵的严重后果
    • 资源滥用: 运行挖矿程序,消耗你的计算资源和带宽,导致高额账单。
    • 成为攻击跳板: 将你的服务器用作“僵尸网络”的一部分,去攻击其他网站或基础设施。
    • 数据和凭证泄露 (最危险):
      • 攻击者可以利用你在服务器上配置的 SSH 密钥,进一步访问你的 GitHub 账户。
      • 如果 GitHub 账户中有公司代码,这意味着整个公司的核心资产可能因此泄露。
    • 抹除痕迹: 攻击者会删除或修改日志文件,让你甚至不知道自己曾经被入侵。
  • 应对服务器入侵
    • 唯一可靠的策略: 一旦确认服务器被入侵,唯一的做法是立即将其彻底销毁 (wipe),然后从一个已知安全的备份中恢复
    • 为什么不能“修复”: 你无法确定攻击者在系统中留下了多少后门 (rootkit) 或潜伏了多久。任何试图“清理”的尝试都可能失败,导致未来更严重的损失。
  • 核心安全措施总结
    1. 使用 SSH 密钥认证: 禁用密码登录。
    2. 配置防火墙 (Firewall): 只开放绝对必要的端口。
    3. 保持软件更新 (Keep Software Up-to-Date): 及时修复已知的安全漏洞。

31-view-open-ports-with-nmap

  • 安全措施概览
    • 回顾了 SSH、防火墙、软件更新、双因素认证和 VPN 等安全概念。
  • 端口 (Port) 的重要性
    • 定义: 端口是网络通信的端点,用于区分同一台服务器上运行的不同服务或进程。
    • 作用: 它允许一台具有单个 IP 地址的服务器同时提供多种服务(例如,80 端口用于 Web 服务,22 端口用于 SSH 管理)。
    • 查看常用端口:
      • less /etc/services
      • 这个文件列出了许多“众所周知”的端口及其对应的服务,如 21 (FTP), 22 (SSH), 80 (HTTP), 443 (HTTPS) 等。
  • 扫描开放端口 (Open Ports)
    • 核心概念: 开放的端口是潜在的安全风险入口。我们必须确保只开放了绝对需要的端口。
    • 工具: nmap (Network Mapper)
      • 一个非常强大的网络扫描和安全审计工具。
    1. 安装 nmap:
      • 在你的服务器上运行: sudo apt install nmap
    2. 扫描服务器:
      • 从你的本地电脑(或任何其他地方)运行命令:nmap [你的服务器IP地址]
      • nmap 会探测目标服务器,并报告哪些端口是开放的 (state is open)。
    • 扫描结果分析:
      • Port 22 (ssh): 开放是正常的,因为我们需要通过它来管理服务器。
      • Port 80 (http): 开放是正常的,因为我们需要通过它来提供 Web 服务。
      • Port 3000 (ppp?): 这个端口被我们的 Node.js 应用打开了。但它不应该对外部世界开放。因为 Nginx 已经在内部将 80 端口的流量代理到 3000 端口了。直接暴露 3000 端口是不必要的,并且增加了攻击面。
    • 下一步行动: 我们需要使用防火墙来关闭 3000 端口,只允许来自服务器内部的访问。

32-firewall-ufw

  • 防火墙 (Firewall)
    • 作用: 一个网络安全系统,它根据预设的规则来监控和控制传入和传出的网络流量。简单来说,就是决定“谁可以通过,谁不能通过”。
  • ufw (Uncomplicated Firewall)
    • Ubuntu 系统内置的一个非常易于使用的防火墙管理工具。它大大简化了传统的、复杂的防火墙配置(如 iptables)。
    • 核心命令:
      • ufw allow [端口/服务名]: 允许流量通过。
      • ufw deny [端口/服务名]: 阻止流量,向请求方发送任何响应。
      • ufw reject [端口/服务名]: 阻止流量,并向请求方发送一个“连接被拒绝”的响应。
    • deny vs. reject:
      • 通常推荐使用 deny。因为它让你的服务器对不必要的扫描“隐形”,不会泄露任何信息。
      • reject 会明确告诉对方“这里有一个端口,但它不让你进”,这仍然暴露了信息。
  • 配置防火墙
    1. 检查状态:
      • sudo ufw status (初始状态通常是 inactive)
    2. 设置规则 (在启用防火墙之前):
      • sudo ufw allow ssh: 这是最重要的一步! 必须首先允许 SSH 流量,否则启用防火墙后你将立即被锁定在服务器之外。ufw 很智能,它知道 ssh 对应的是 22 端口。
      • sudo ufw allow http: 允许 Web 流量通过 80 端口。
    3. 启用防火墙:
      • sudo ufw enable
      • 系统会给你一个最终警告,确认你已设置好 SSH 规则。输入 y 并回车。
    4. 再次检查状态:
      • sudo ufw status
      • 现在你应该能看到状态是 active,并且 22/tcp80/tcp 端口的动作为 ALLOW
  • 验证效果
    • 此时,如果你再次从本地电脑使用 nmap 扫描你的服务器,你会发现 3000 端口已经不再显示为开放状态了。我们成功地关闭了不必要的端口。

33-permissions-chmod

  • 文件权限 (Permissions)
    • Linux/Unix 系统中一个至关重要的安全概念,它决定了谁可以对文件和目录进行何种操作。
    • ls -la 命令可以详细地显示文件权限。
  • 权限的组成
    • 权限字符串(如 drwxr-xr-x)分为四部分:
      1. 文件类型: 第一个字符。d 代表目录 (directory), 代表普通文件。
      2. 所有者权限 (Owner): 接下来的三个字符。
      3. 组权限 (Group): 再接下来的三个字符。
      4. 其他人权限 (Others): 最后三个字符。
    • 权限类型:
      • r: 读取 (Read)
      • w: 写入 (Write)
      • x: 执行 (Execute)
  • 用数字表示权限 (Octal Notation)
    • 这是 chmod 命令更常用的一种方式,用一个三位数的八进制数来代表权限。
    • 数字映射:
      • r (Read) = 4
      • w (Write) = 2
      • x (Execute) = 1
    • 计算方法: 将所需的权限数字相加即可。
      • rwx = 4 + 2 + 1 = 7
      • rw- = 4 + 2 + 0 = 6
      • r-x = 4 + 0 + 1 = 5
      • r-- = 4 + 0 + 0 = 4
  • 常见权限数字组合的含义
    • chmod 777: rwxrwxrwx极不安全的做法,意味着任何人都可以对该文件进行任何操作。应该极力避免。
    • chmod 644: rw-r--r--。这是一个非常常见的文件权限设置。
      • 所有者可以读写。
      • 组用户和其他人只能读取。
    • chmod 755: rwxr-xr-x。这是一个非常常见的目录或脚本权限设置。
      • 所有者可以读、写、执行。
      • 组用户和其他人可以读取和执行(进入目录)。
    • chmod 600: rw-------。这是一个非常安全的设置,常用于敏感文件(如 SSH 私钥)。
      • 只有所有者可以读写,其他任何人都没有任何权限。
  • 最佳实践:最小权限原则 (Principle of Least Privilege)
    • 始终只授予用户或进程完成其任务所需的最小权限。这可以极大地限制潜在的错误或攻击所造成的损害。

34-unattended-upgrades

  • 保持系统更新的重要性
    • 过时的软件是服务器被入侵的最常见原因之一。许多攻击都利用了早已被发现并修复的旧漏洞。
    • 手动更新很繁琐且容易忘记,因此自动化更新是最佳实践。
  • unattended-upgrades:自动化安全更新
    • 这是一个 Ubuntu 软件包,它可以在后台自动下载并安装重要的安全更新,无需任何人工干预。
  • 安装和配置
    1. 安装软件包:
      • sudo apt install unattended-upgrades
      • 在某些 Ubuntu 版本中,这个包可能已经预装了。
    2. 配置更新行为:
      • sudo dpkg-reconfigure --priority=low unattended-upgrades
      • dpkg-reconfigure: 一个用于重新配置已安装软件包的命令。
      • -priority=low: 以低优先级运行,确保更新过程不会干扰正常的系统服务。
    3. 确认配置:
      • 运行上述命令后,会弹出一个文本界面的对话框。
      • 它会询问你是否要“自动下载并安装稳定的更新”。
      • 选择 "Yes" 并按回车。
    • 完成: 就这么简单。现在你的服务器会自动保持最新的安全状态,大大提升了安全性。这比过去需要手动编写 Cron Job 来执行更新要方便得多。

35-continuous-integration-deployment

  • CI/CD: 什么是它,为什么重要?
    • 面对多开发者协作、大规模部署的场景,传统的“每周五手动大合并”模式风险极高且不可持续。CI/CD 提供了一套自动化的解决方案。
    • CI (Continuous Integration - 持续集成)
      • 核心思想:开发人员频繁地(每天多次)将代码变更合并到主分支。
      • 每次合并都会自动触发构建和自动化测试,以尽早发现集成错误。
      • 这要求开发者提交小的、原子化的变更,而不是积攒几周的大量代码。
    • CD (Continuous Delivery / Continuous Deployment)
      • Continuous Delivery (持续交付):
        • 在持续集成的基础上,将每一次通过测试的代码构建成一个可以随时部署到生产环境的版本。
        • 部署到生产环境的最后一步通常是手动的,需要人工确认。这适用于对稳定性要求极高的应用,如医疗、金融。
      • Continuous Deployment (持续部署):
        • 最激进的模式。每一次通过所有测试流程的代码变更,都会自动、直接地部署到生产环境,无需人工干预。
  • CI/CD 的核心:测试 (Testing)
    • 许多人误以为 CI/CD 只是为了“快速部署”,但其真正的基石是自动化测试
    • 没有全面、可靠的测试,CI/CD 就失去了安全保障,变成了“持续地推送 bug”。
  • CI/CD 工具
    • Spinnaker:
      • 由 Netflix 和 Google 联合开发的一个功能极其强大的开源多云持续交付平台。
      • 它可以实现一键创建包含数百台服务器、负载均衡器、防火墙等复杂环境的集群。
      • Netflix 的整个基础设施都运行在 Spinnaker 之上。
      • 对于个人项目或小团队来说,Spinnaker 过于复杂,属于“杀鸡用牛刀”。
    • CI/CD Pipeline (管道)
      • 一个典型的 CI/CD 流程被称为“管道”,它由一系列连续的阶段组成:
      • 代码提交 -> 触发构建 -> 单元测试 -> 集成测试 -> (手动审批) -> 部署到生产环境
  • 本课程的目标
    • 我们将构建一个简化的、“假的” CI/CD 管道
    • 我们不会使用 Spinnaker 这样的重型工具,而是用几个简单的脚本来模拟这个过程,从而理解其核心思想。

36-cron-for-ci

  • 构建“假的” CI/CD 管道

    • 核心思路:
      1. 编写一个脚本,该脚本的作用是从 GitHub pull 最新的代码。
      2. 使用一个定时器,每隔一分钟自动执行一次这个脚本。
    • 这个流程实现了“持续部署”的核心概念:服务器会自动、频繁地拉取最新代码并部署。
    • 重要警告:
      • 这是一个为了教学目的而设计的“黑客”方法
      • 缺少了 CI/CD 最关键的部分:自动化测试
      • 绝对不能在真实的生产环境中使用这种没有测试保障的自动部署。
  • 定时器: Cron

    • Cron: 一个在 Unix-like 系统中用于设置周期性被执行的任务的工具。
    • Cron Job (Cron 任务): 一个具体的定时任务。
  • Bash 脚本 (Shell Scripting)

    • .sh 文件: 包含一系列可以在命令行中执行的命令的文本文件。
    • shebang: 脚本的第一行,如 #!/bin/bash。它告诉系统应该用哪个解释器(这里是 bash)来执行这个脚本。
    • 执行权限:
      • 新创建的脚本文件默认没有执行权限。
      • 需要使用 chmod +x your_script.shchmod 755 your_script.sh 来赋予它执行权限。
  • 创建拉取代码的脚本

    1. vim github.sh

    2. 写入以下内容:

      #!/bin/bash
      cd /var/www/app
      git pull origin main --ff-only
      
      
      • cd /var/www/app: 至关重要。Cron 任务默认在用户的 home 目录运行,你必须先 cd 到你的 Git 仓库目录,否则 git pull 会失败。
      • -ff-only: 只允许“快速前推”(Fast-forward)。这可以防止在服务器上有本地修改时产生合并冲突,让自动拉取更安全。
    3. 赋予执行权限: chmod 700 github.sh

  • 设置 Cron Job

    1. 编辑 Crontab:

      • crontab -e
    2. Cron 语法:

      • * * * * command_to_execute
      • 五个星号分别代表:分 (0-59),时 (0-23),日 (1-31),月 (1-12),星期 (0-6)
      • 表示“每一个”。
    3. Crontab.guru: 一个非常好用的网站,可以帮助你生成和解释 Cron 表达式。

    4. 添加任务:

      • 在 crontab 文件中添加以下行:

        */1 * * * * sh /var/www/app/github.sh >> /var/log/cron.log 2>&1
        
        
        • /1 * * * *: 表示“每隔 1 分钟”。
        • sh ...: 执行我们的脚本。
        • >> /var/log/cron.log 2>&1: 将脚本的标准输出 (>) 和标准错误 (2>) 都重定向 (&1) 并追加 (>>) 到指定的日志文件中。
  • 验证

    • tail -f /var/log/syslog: 查看系统日志,可以看到 Cron 正在按时执行你的任务。
    • 在本地修改代码并推送到 GitHub,等待一分钟,服务器上的代码应该会自动更新。

37-logging-streams-redirection

  • 日志 (Logs):系统调试的眼睛
    • 为什么需要日志: 没有日志,你就无法知道系统内部发生了什么,无法诊断问题,也无法确认系统是否正常工作。
    • 常见日志文件位置: /var/log/
      • syslog: 记录系统范围内的所有事件。
      • auth.log: 记录认证和授权相关的事件(如登录尝试)。
      • nginx/access.log & nginx/error.log: Nginx 的访问和错误日志。
  • 读取日志文件的工具
    • cat: 显示整个文件。只适用于小文件。
    • tail: 显示文件的末尾部分。
      • tail -f a_log_file.log: (f 代表 follow) 实时跟踪并显示文件的最新内容,是监控日志最常用的命令。
    • head: 显示文件的开头部分。
    • less: 分页显示整个文件,可以上下滚动。适用于大文件。
  • 标准流 (Standard Streams)
    • POSIX 标准: Unix/Linux 系统最强大的设计之一。它规定了所有命令行程序都共享三个标准的数据流:
      1. Standard Input (stdin) (文件描述符 0): 程序的默认输入来源(通常是键盘)。
      2. Standard Output (stdout) (文件描述符 1): 程序的默认输出目的地(通常是屏幕)。
      3. Standard Error (stderr) (文件描述符 2): 程序的错误信息输出目的地(通常也是屏幕)。
    • 威力所在: 因为所有程序都遵循这个统一的接口,所以我们可以像积木一样将它们链接起来,实现极其复杂的数据处理。
  • 重定向 (Redirection)
    • 用于改变标准流的默认来源或目的地。
    • | (Pipe - 管道):
      • 前一个命令的 stdout 作为后一个命令的 stdin
      • 例如: ps aux | grep node
    • > (输出重定向):
      • 将命令的 stdout 写入到一个文件。
      • 注意: 这会覆盖文件的原有内容。
      • 例如: echo "hello" > file.txt
    • >> (追加重定向):
      • 将命令的 stdout 追加到一个文件的末尾,而不覆盖
      • 例如: echo "world" >> file.txt
    • < (输入重定向):
      • 将一个文件的内容作为命令的 stdin。
    • 2>&1:
      • 这是一个复合重定向。
      • 2>: 重定向 stderr。
      • &1: 指向 stdout 当前所在的位置。
      • 合起来的意思是:“将标准错误 (stderr) 重定向到与标准输出 (stdout) 相同的地方”。这常用于将所有输出(无论正常还是错误)都记录到同一个日志文件中。

38-find-grep

  • find 命令:按条件查找文件
    • 作用: 在指定的目录树中,根据各种条件(如名字、类型、大小、修改时间等)来查找文件或目录。
    • 基本语法: find [在哪里查找] [查找什么类型] [根据什么条件]
    • 常用示例:
      • sudo find /var/log -type f -name "*.log":
        • sudo: 因为 find 需要读取很多受保护的目录,所以通常需要 sudo
        • /var/log: 在 /var/log 目录及其所有子目录中查找。
        • type f: 只查找文件 (file)。
        • name "*.log": 查找所有以 .log 结尾的文件。
      • sudo find / -type d -name "log":
        • /: 从根目录开始查找整个文件系统。
        • type d: 只查找目录 (directory)。
        • name "log": 查找名字正好是 "log" 的目录。
    • sudo !!: 一个非常有用的快捷方式,它会以 sudo 的权限重新运行你上一条执行的命令。
  • grep 命令:在文本中查找模式
    • 作用: grep 的工作对象是文本内容。它可以从文件或标准输入中,查找包含指定模式(字符串或正则表达式)的行。
    • find 的区别: find 找的是文件名或目录名grep 找的是文件内部的内容
    • 语法: grep [选项] [要查找的模式] [在哪个文件中查找]
    • 与管道 (|) 结合使用(最常用):
      • ps aux | grep node
        • ps aux: 列出系统上所有正在运行的进程。
        • |: 将 ps 的输出通过管道传递给 grep
        • grep node: 从管道接收到的文本中,筛选出包含 "node" 这个词的行。
      • 这是快速从大量输出中定位信息的关键技巧。

39-nginx-redirection-gzip

  • Nginx 中的重定向 (Redirection)

    • 作用: 将用户从一个 URL 自动跳转到另一个 URL。

    • 实现方式: 在 Nginx 的 serverlocation 配置块中使用 returnrewrite 指令。

    • 示例:

      location /help {
          return 301 <https://developer.mozilla.org/>;
      }
      
      
      • 当用户访问 yourdomain.com/help 时,Nginx 会返回一个 301 (永久重定向) 状态码,并告诉浏览器跳转到 MDN 网站。
  • Gzip 压缩

    • 什么是 Gzip: 一种非常流行的文件压缩算法。
    • 在 Web 中的作用:
      • Nginx 可以在将 HTML, CSS, JavaScript 等文本文件发送给浏览器之前,先使用 Gzip 对其进行压缩
      • 浏览器接收到压缩后的文件后,会自动进行解压
      • 效果: 大大减小了传输文件的大小,显著加快了网页加载速度。
    • 配置:
      • Gzip 相关的配置在主配置文件 sudo vim /etc/nginx/nginx.conf 中。
      • 你可以调整 gzip_comp_level 来设置压缩级别(1-9)。级别越高,压缩效果越好,但消耗的 CPU 也越多。通常默认的 6 是一个很好的平衡点。
  • 压缩 (Compression) vs. 哈希 (Hashing)

    • 压缩: 可逆的过程。目的是减小文件体积,压缩后的数据必须能够被完整地解压回原始状态。
    • 哈希: 通常是不可逆的过程。目的是为数据生成一个唯一的“指纹”,用于验证数据完整性或安全存储,而不是为了恢复原始数据。
  • 为什么不能 Gzip 图片?

    • 像 JPEG, PNG, MP3 等媒体文件,它们本身就已经是高度压缩过的格式了
    • 对一个已经压缩过的文件再次进行 Gzip 压缩,几乎不会有任何效果,甚至可能因为增加了额外的头部信息而使文件体积变得更大。

40-subdomains

  • 子域名 (Subdomain)

    • 作用: 在主域名下创建独立的命名空间,用于组织网站的不同部分。
    • 示例:
      • blog.yourdomain.com (博客)
      • api.yourdomain.com (API 服务)
      • dev.yourdomain.com (开发环境)
    • 使用子域名可以方便地将不同功能或应用部署到不同的服务器或端口,同时保持品牌的一致性。
  • 创建子域名的步骤

    1. 在 DNS 中添加 A 记录:

      • 登录你的 DNS 提供商(本课程中是 DigitalOcean 的 Networking 面板)。
      • 为你的主域名添加一条新的 A 记录
      • 主机名 (Hostname): 填入你想要的子域名,例如 blog
      • 指向 (Will Direct To): 选择你的服务器 Droplet。
      • 保存后,blog.yourdomain.com 就会解析到你的服务器 IP 地址。
    2. 在 Nginx 中配置新的虚拟主机:

      • DNS 只是把流量引到了你的服务器,Nginx 还需要知道如何处理这些针对子域名的请求。

      • 创建一个新的 Nginx 配置文件:sudo vim /etc/nginx/sites-enabled/blog.conf

      • 写入新的 server 配置块:

        server {
            listen 80;
            listen [::]:80;
        
            # 这是关键,它告诉 Nginx 这个配置块只处理对 blog.jemstack.lol 的请求
            server_name blog.jemstack.lol;
        
            location / {
                # 这里可以代理到另一个完全不同的应用,例如运行在 8080 端口的博客系统
                proxy_pass <http://localhost:8080>;
            }
        }
        
        
    3. 在 Nginx 主配置中加载新文件:

      • 编辑 sudo vim /etc/nginx/nginx.conf
      • include /etc/nginx/sites-enabled/fsfe; 下面,添加一行来加载新的配置文件:
        • include /etc/nginx/sites-enabled/blog.conf;
    4. 验证并重启 Nginx:

      • sudo nginx -t (验证配置)
      • sudo service nginx restart (重启服务)
  • 回顾与理解

    • 通过这个练习,我们再次强化了对整个流程的理解:从 DNS 解析,到 Nginx 如何根据 server_name 区分不同的请求,再到 proxy_pass 将请求转发到后端的不同应用。这些是构建复杂、多服务架构的基础。

41-websockets-overview

  • WebSockets vs. HTTP

    • HTTP: 是一种单向、无状态的协议。客户端发起一个请求,服务器返回一个响应,然后连接就关闭了。它不是持久的。
    • WebSockets: 是一种全双工 (bidirectional)、持久化的通信协议。一旦建立连接,客户端和服务器之间就可以在任何时候互相发送数据,连接会一直保持开放状态。这使得实时通信成为可能。
  • 实现 WebSockets 的步骤

    1. 更新 Nginx 配置: 我们需要告诉 Nginx 如何处理升级到 WebSocket 的请求。
    2. 创建新的 Node.js 服务器: 使用 Express 框架来简化服务器的创建。
    3. 在服务器端启用 WebSockets: 使用 ws 库。
    4. 在客户端 (HTML) 建立 WebSocket 连接: 编写 JavaScript 代码来连接服务器。
  • 在 Nginx 中启用 WebSocket 代理

    • WebSockets 在技术上是对 HTTP 连接的一种“升级 (Upgrade)”。我们需要在 Nginx 中将这个升级请求头传递给后端的 Node.js 应用。

    • 编辑默认的 Nginx 配置文件 sudo vim /etc/nginx/sites-enabled/fsfe

    • location / 块中,添加以下两行:

      location / {
          proxy_pass <http://127.0.0.1:3000>;
          # 告诉后端服务器,客户端请求升级连接
          proxy_set_header Upgrade $http_upgrade;
          # 传递 Connection 头
          proxy_set_header Connection "upgrade";
      }
      
      
    • 保存后,验证 (sudo nginx -t)重启 (sudo service nginx restart) Nginx 服务。

  • 使用 Express 创建服务器

    • Express.js: Node.js 社区中最流行、最成熟的 Web 应用框架。它极大地简化了路由、中间件等常用功能的实现。

      • Fastify: 一个新兴的、以性能和低开销著称的框架,被认为是 Express 的现代替代品。但对于学习而言,Express 的生态和文档更丰富。
    • 基本设置:

      1. 在本地电脑(或服务器)的项目目录中,安装 Express: npm install express

      2. 创建一个新的服务器文件,例如 index-ws.js

      3. 编写基本的 Express 服务器代码,使其能响应根路径请求并发送一个静态的 index.html 文件。

        const express = require("express");
        const http = require("http");
        const path = require("path");
        
        const app = express();
        const server = http.createServer(app);
        
        // 当访问根路径时,发送 index.html 文件
        app.get("/", (req, res) => {
          res.sendFile(path.join(__dirname, "index.html"));
        });
        
        server.listen(3000, () => {
          console.log("Server started on port 3000");
        });
        
  • 课程代码 (WebSocket Example)

    • 幻灯片中提供了带有完整 WebSocket 示例代码的 GitHub 仓库链接,以供参考。

42-using-websockets-with-express

  • 在 Express 服务器上集成 WebSockets

    • 使用 ws: 这是一个简单、高效的 WebSocket 库,可以轻松地与现有的 Node.js HTTP 服务器集成。
    • 安装: npm install ws
  • 编写 WebSocket 服务器端逻辑

    1. 引入并实例化 WebSocket 服务器:

      const WebSocket = require("ws");
      const WSS_PORT = 8080; // 可以单独使用一个端口,或者附加到现有服务器
      const wss = new WebSocket.Server({ server }); // 将 WebSocket 附加到现有的 HTTP 服务器上
      
    2. 监听 connection 事件:

      • 当有新的客户端通过 WebSocket 连接时,wss 会触发一个 connection 事件。

        wss.on("connection", (ws) => {
          // 'ws' 对象代表与单个客户端的连接
          console.log("A new client connected!");
        
          // 向新连接的客户端发送欢迎消息
          ws.send("Welcome to the WebSocket server!");
        
          // 监听该客户端发来的消息
          ws.on("message", (message) => {
            console.log(`Received: ${message}`);
          });
        
          // 监听连接关闭
          ws.on("close", () => {
            console.log("Client has disconnected.");
          });
        });
        
    3. 实现广播 (Broadcast) 功能:

      • 编写一个函数,可以将消息发送给所有已连接的客户端。

        // 广播函数
        function broadcast(message) {
          wss.clients.forEach((client) => {
            if (client.readyState === WebSocket.OPEN) {
              client.send(message);
            }
          });
        }
        
        // 在有新连接时,广播当前在线人数
        wss.on("connection", (ws) => {
          // ... (其他代码)
          broadcast(`Current visitors: ${wss.clients.size}`);
        });
        
  • 服务器端完整代码结构:

    • 在现有的 index-ws.js 文件中,添加上述 WebSocket 相关的代码块,使其与 Express 应用一起运行。

43-creating-a-websocket-connection

  • 在客户端 (浏览器) 建立 WebSocket 连接
    • 目标: 在我们的 index.html 页面中,使用 JavaScript 来连接到服务器端的 WebSocket 服务。
  • 编写客户端 JavaScript 代码
    1. index.html 中添加 <script> 标签
    2. 确定协议:
      • WebSocket 连接也有安全 (wss://) 和非安全 (ws://) 两种协议。我们需要根据当前页面的协议 (http/https) 来动态选择。
        const proto = window.location.protocol === "https:" ? "wss" : "ws";
        
    3. 创建 WebSocket 实例:
      • 浏览器原生提供了 WebSocket 对象。
        const ws = new WebSocket(`${proto}://${window.location.host}`);
        
        • window.location.host 会返回当前页面的域名和端口,确保连接到正确的服务器。
    4. 监听 message 事件:
      • 当从服务器收到消息时,ws 对象会触发一个 onmessage 事件。我们需要监听这个事件来处理收到的数据。
        ws.onmessage = (event) => {
          // event.data 包含了服务器发送的消息内容
          console.log("Received from server:", event.data);
        };
        
  • 部署和测试
    1. 代码提交:
      • 在本地完成服务器端和客户端代码的编写。
      • 创建一个 .gitignore 文件来忽略 node_modules 目录。
      • 使用 git add ., git commit, git push 将所有更改推送到 GitHub。
    2. 服务器同步:
      • 我们在上一节配置的 Cron Job 会在 1-2 分钟内自动从 GitHub pull 最新的代码到服务器上。
    3. 重启应用:
      • 关键一步: 我们的 Node.js 应用代码已经更新,但 PM2 仍在运行旧版本的应用。
      • pm2 stop app (停止旧的简单服务器)
      • pm2 start index-ws.js --watch (用 PM2 启动我们新的 WebSocket 服务器)
      • pm2 save (保存新的进程列表,以便服务器重启后能自动加载)
    4. 最终测试:
      • 打开浏览器,访问你的域名 https://yourdomain.com
      • 打开开发者工具的 Console。
      • 你应该能看到 "Welcome to my server" 以及 "Current visitors: 1" 的消息。
      • 再打开一个浏览器标签页访问该域名,两个页面的 Console 应该都会显示 "Current visitors: 2"。这证明了实时广播功能是正常的。
    • 处理 npm install: 为了让自动化部署更完善,可以在拉取代码的脚本 (github.sh) 中增加 npm install 命令,确保每次更新代码后都会安装新的依赖。

44-databases-overview

  • 为什么需要数据库?
    • 直接将数据存储在单个文件中存在许多问题:
      • 不可扩展 (Not Scalable): 无法被多个服务器同时读写。
      • 无结构 (Unstructured): 缺乏统一的数据格式和约束。
      • 低效 (Inefficient): 查询特定数据时可能需要读取整个文件。
  • “90% 的工作都是读写数据库”
    • 这是对软件工程工作核心本质的一个深刻洞察。无论前端还是后端,我们大部分时间都在处理数据:从 API 获取数据、向 API 发送数据,而这些 API 的背后几乎总是数据库。
  • 数据库的两种主要类型
    • 关系型数据库 (Relational Databases / SQL):
      • 特点: 数据以严格的、预定义的表格 (Tables) 形式组织。数据之间通过关系 (Relations) 相互关联。
      • 代表: MySQL, PostgreSQL, SQL Server, SQLite
      • 优点: 结构化强,数据一致性高,适合复杂查询 (JOINs)。
      • 缺点: 灵活性差,扩展(尤其是水平扩展)相对复杂。
    • 非关系型数据库 (Non-relational Databases / NoSQL):
      • 特点: 数据存储格式灵活,通常是键值对、文档 (JSON-like)、列族或图形。
      • 代表: MongoDB, Redis, Cassandra, DynamoDB。
      • 优点: 灵活性高,性能好(特别是读写),易于水平扩展。
      • 缺点: 数据一致性模型更复杂,不适合复杂的关联查询。
  • 关系型数据库的核心概念
    • Table (表): 数据的集合,像一个电子表格。
    • Field / Column (字段/列): 表中的一列,定义了数据的类型(如文本、整数)。
    • Row / Record (行/记录): 表中的一行,代表一个具体的数据实体。
    • Primary Key (主键): 表中每一行的唯一标识符
    • Foreign Key (外键): 一个表中的字段,它引用了另一个表的主键,以此建立两个表之间的关系
  • SQL (Structured Query Language)
    • 用于与关系型数据库交互的标准化语言。
    • SELECT: 查询数据。
    • INSERT: 插入数据。
    • UPDATE: 更新数据。
    • DELETE: 删除数据。
    • JOIN: 将多个表中的数据根据它们之间的关系连接起来,是 SQL 最强大的功能之一。

45-sqlite

  • SQLite:轻量级数据库的选择

    • 特点:
      • 服务器 less: SQLite 不是一个需要独立运行的服务。它是一个库,直接嵌入到你的应用程序中。
      • 文件即数据库: 整个数据库存储在一个单一的文件中。
      • 零配置: 无需复杂的安装和配置。
    • 优点: 非常适合学习、原型制作、移动应用和一些小型项目。我们使用它来避免安装和配置 MySQL 等重量级数据库的麻烦。
  • 在 Node.js 中使用 SQLite

    1. 安装库: npm install sqlite3

    2. 创建数据库连接:

      const sqlite3 = require("sqlite3").verbose();
      // ':memory:' 表示创建一个内存数据库,它在程序结束时会消失。
      // 如果想持久化,可以提供一个文件名,如 './fsfe.db'。
      const db = new sqlite3.Database(":memory:");
      
    3. 执行 SQL 命令 (创建表):

      • db.serialize(): 这是一个包装函数,确保其中的命令按顺序执行。
      • db.run(): 用于执行不返回结果的 SQL 命令(如 CREATE, INSERT, UPDATE)。
        db.serialize(() => {
          db.run(`CREATE TABLE visitors (
                count INTEGER,
                time TEXT
            )`);
        });
        
    4. 查询数据:

      • db.each(): 用于查询多行数据,并对每一行执行一个回调函数。
        db.each("SELECT * FROM visitors", (err, row) => {
          console.log(`At ${row.time}, there were ${row.count} visitors.`);
        });
        
  • 将数据库与 WebSocket 集成

    • 目标: 每次有新的 WebSocket 连接时,将当前的访客数量和时间戳写入 visitors 表。
    • wss.on('connection', ...) 的回调函数中,添加 db.run() 命令来插入数据:
      wss.on("connection", (ws) => {
        const numClients = wss.clients.size;
        // ... (其他代码)
        db.run('INSERT INTO visitors (count, time) VALUES (?, datetime("now"))', [
          numClients,
        ]);
      });
      
      • ?参数化查询的占位符,这是一种防止 SQL 注入攻击的安全实践。[numClients] 数组中的值会安全地替换 ?
      • datetime("now"): SQLite 的内置函数,用于获取当前时间。
  • 优雅地关闭数据库连接

    • 问题: 如果不关闭数据库连接,程序退出时可能会导致资源泄露或数据损坏。

    • 解决方案: 监听 Node.js 进程的 SIGINT 事件(即当用户按下 Ctrl+C 时触发的事件),并在事件处理程序中安全地关闭所有连接。

      process.on("SIGINT", () => {
        wss.clients.forEach((client) => client.close()); // 1. 关闭所有 WebSocket 连接
        server.close(() => {
          // 2. 关闭 HTTP 服务器
          shutdownDB(); // 3. 关闭数据库
        });
      });
      
      function shutdownDB() {
        // ... (查询并打印最终数据)
        db.close();
      }
      
  • 调试经验

    • 课程中遇到的“信号中断不工作”问题,最终定位到是因为 WebSocket 连接没有被正确关闭,导致进程无法正常退出。这突显了在处理多重异步资源(HTTP 服务器、WebSocket、数据库)时,必须确保所有资源都被有序、正确地关闭。

46-https-overview

  • HTTP:协议回顾
    • HTTP (Hypertext Transfer Protocol): 互联网上应用最广泛的一种网络协议,是客户端和服务器之间通信的基础。
    • 它由请求 (Request)响应 (Response) 两部分组成。
  • HTTP 请求/响应结构
    • 两者都包含:
      • 起始行:
        • 请求:GET /path/to/resource HTTP/1.1 (方法, 路径, 协议版本)
        • 响应:HTTP/1.1 200 OK (协议版本, 状态码, 状态信息)
      • 头部 (Headers): 一系列的键值对,传递关于请求/响应的元数据。
      • 消息体 (Body): 可选的,包含实际的数据(如 POST 请求的数据,或响应的 HTML 内容)。
  • 常见的 HTTP 头部
    • User-Agent: 标识客户端(浏览器、爬虫等)的类型和版本。
    • Accept: 告诉服务器客户端能理解哪些内容类型。
    • Accept-Language: 客户端偏好的语言。
    • Content-Type: 指明消息体的媒体类型(如 application/json)。
    • Cookie / Set-Cookie: 用于在客户端和服务器之间传递状态信息。
    • X- 开头的头部: 自定义头部,用于非标准用途。
  • HTTP 状态码 (Status Codes)
    • 用于表示服务器对请求的处理结果。它们被分为五大类:
      • 1xx (Informational): 信息性,表示请求已接收,继续处理(如 101 Switching Protocols)。
      • 2xx (Successful): 成功,表示请求被成功接收、理解、接受(如 200 OK, 201 Created)。
      • 3xx (Redirection): 重定向,需要客户端采取进一步的操作才能完成请求(如 301 Moved Permanently, 302 Found)。
      • 4xx (Client Error): 客户端错误,表示请求包含语法错误或无法完成(如 401 Unauthorized, 404 Not Found)。
      • 5xx (Server Error): 服务器错误,表示服务器在处理一个有效请求时发生内部错误(如 500 Internal Server Error, 502 Bad Gateway)。
  • HTTPS:安全的 HTTP
    • 问题: 普通的 HTTP 传输是明文的。在请求从你的电脑到服务器的漫长路径中,任何中间节点(如不安全的 WiFi、ISP)都可以窃听或篡改你的数据(如密码、信用卡号)。
    • HTTPS (HTTP Secure): 解决方案。它在 HTTP 和 TCP 之间增加了一个加密层 (SSL/TLS)
    • 作用:
      • 加密 (Encryption): 确保数据在传输过程中是加密的,中间人无法读取。
      • 认证 (Authentication): 验证你正在通信的服务器确实是它所声称的那个服务器,防止“中间人攻击”。
    • 现代 Web 的必需品: 所有现代浏览器都强制要求使用 HTTPS,否则会显示“不安全”警告。许多新的 Web API(如 Service Workers, Web Bluetooth)也只能在 HTTPS 环境下使用。

47-implementing-https-with-certbot

  • 实现 HTTPS 的核心:SSL/TLS 证书

    • 你需要一个由受信任的证书颁发机构 (Certificate Authority, CA) 签发的 SSL/TLS 证书,来向浏览器证明你的网站身份。
    • 过去,获取证书通常需要付费。
  • Certbot 与 Let's Encrypt

    • Let's Encrypt: 一个免费、自动化、开放的证书颁发机构,由电子前哨基金会 (EFF) 等组织支持。它的使命是让全网都用上 HTTPS。
    • Certbot: 一个由 EFF 开发的工具,它可以自动完成在你的服务器上获取、配置和续订 Let's Encrypt 证书的整个过程。
  • 使用 Certbot 配置 HTTPS

    1. 访问 Certbot 官网: certbot.eff.org

    2. 选择你的环境: 在网站上选择你的 Web 服务器 (Nginx) 和操作系统 (Ubuntu 20)。网站会为你生成精确的安装步骤。

    3. 安装 Certbot:

      • 按照官网的指示,使用 snap (Ubuntu 的一种包管理器) 来安装 certbot
      sudo snap install core; sudo snap refresh core
      sudo snap install --classic certbot
      sudo ln -s /snap/bin/certbot /usr/bin/certbot
      
      
    4. 运行 Certbot:

      • sudo certbot --nginx: 这是最神奇的一步。
        • -nginx: 告诉 Certbot 你正在使用 Nginx。
        • Certbot 会自动扫描你的 Nginx 配置文件,找出你已经配置的所有域名(包括主域名和子域名)。
        • 它会询问你希望为哪些域名申请证书。
        • 然后,它会与 Let's Encrypt 服务器通信,通过在你服务器上创建临时文件来验证你对这些域名的所有权。
        • 验证成功后,它会获取证书自动修改你的 Nginx 配置文件,为你配置好 HTTPS。
    5. 防火墙设置:

      • Certbot 不会自动修改防火墙。你需要手动打开 443 端口。
      • sudo ufw allow https
  • Certbot 自动修改的内容

    • Certbot 会在你的 Nginx 配置文件中:
      • 添加 listen 443 ssl; 来监听 HTTPS 端口。
      • 配置 ssl_certificatessl_certificate_key 指令,指向它为你下载的证书文件。
      • 在原有的 80 端口配置中,添加一个 301 重定向,将所有 HTTP 流量强制跳转到 HTTPS。
  • 自动续订

    • Let's Encrypt 证书的有效期为 90 天。Certbot 在安装时会自动设置一个定时任务(Cron Job 或 systemd timer),定期检查并自动续订即将过期的证书。你完全无需手动干预。
  • 最终测试

    • 刷新你的网站,你应该能看到浏览器地址栏出现了安全锁标志,表示 HTTPS 已成功启用。

48-supporting-http-2

  • HTTP/1.1 vs. HTTP/2

    • HTTP/1.1:
      • 工作方式: 采用“请求-响应”模型。浏览器为每一个需要获取的资源(如 HTML, CSS, JS, 图片)都建立一个独立的 TCP 连接。
      • 问题: 当页面资源很多时,建立大量连接会带来显著的开销和延迟(队头阻塞 Head-of-line blocking)。
    • HTTP/2:
      • 核心改进: 多路复用 (Multiplexing)
      • 工作方式: 在单个 TCP 连接上,可以同时、并行地传输多个请求和响应。
      • 优点: 大大减少了连接开销,提高了页面加载速度,尤其是在资源数量多的页面上。
  • 启用 HTTP/2

    • 前提条件: 主流浏览器只在 HTTPS 连接上支持 HTTP/2。

    • 在 Nginx 中启用:

      • 启用 HTTP/2 非常简单,只需在你 Nginx 配置文件中监听 443 端口的 listen 指令后面,加上 http2 这个词。

      • sudo vim /etc/nginx/sites-enabled/fsfe (或你的主配置文件)

      • 修改 listen 行:

        # 同时为 IPv4 和 IPv6 启用
        listen 443 ssl http2;
        listen [::]:443 ssl http2;
        
        
    • 验证和重启:

      • sudo nginx -t
      • sudo service nginx restart
  • 测试 HTTP/2 是否生效

    • 打开浏览器开发者工具,切换到 "Network" (网络) 标签。
    • 在列标题上右键,勾选 "Protocol" (协议) 这一列。
    • 刷新你的网站,你应该能在 "Protocol" 列中看到 h2,这表示连接正在使用 HTTP/2。
  • 回顾与成就

    • 你已经通过非常简单的步骤,成功地将你的网站升级到了现代化的 HTTP/2 协议,进一步提升了性能和效率。

49-containers

  • 微服务 (Microservices) vs. 单体应用 (Monolith)
    • 单体应用 (Monolith):
      • 整个应用程序作为一个单一的、紧密耦合的单元来构建和部署。
      • 优点: 开发简单,易于调试,因为所有东西都在一个代码库中。
      • 缺点: 难以扩展(只能整体扩展),技术栈单一,一个部分的改动可能影响全局,部署风险高。
    • 微服务 (Microservices):
      • 将一个大的应用程序拆分成一组小的、松散耦合的、独立的服务。
      • 每个服务都围绕一个特定的业务功能构建,可以独立开发、部署和扩展。
      • 优点: 技术栈灵活(每个服务可用不同语言),团队自主性高,易于扩展,故障隔离。
      • 缺点: 增加了运维复杂性,服务间的通信、协调和数据一致性是挑战。
  • 容器化 (Containerization):实现微服务的基础
    • 虚拟化 (Virtualization): 在物理服务器上创建虚拟机 (VMs)。每个 VM 都包含一个完整的操作系统,相对较重。我们的 VPS 就是一个 VM。
    • 容器化 (Containerization):
      • 一种更轻量级的虚拟化。容器共享宿主机的操作系统内核。
      • 容器内只包含应用程序及其必需的库和依赖,不包含完整的操作系统。
      • 结果: 容器启动快,资源占用少,非常适合打包和运行单个微服务。
  • 容器化的优势
    • 轻量 (Lightweight): 只打包应用所需,启动迅速。
    • 可移植 (Portable): "一次构建,到处运行"。一个容器镜像可以在任何支持容器技术的操作系统上运行(Linux, a, Windows)。
    • 易于开发和管理 (Easier Development & Management): 开发人员可以在本地运行与生产环境完全一致的容器,避免了“在我电脑上是好的”问题。
    • 解耦 (Decoupling): 将应用程序与其运行的基础设施彻底分离。开发者只需关心应用本身,而无需关心底层的操作系统或硬件。
  • Docker
    • 目前最流行的容器化平台。
    • 它提供了一套工具,使得创建、管理和部署容器变得非常简单。

50-creating-a-docker-container

  • 目标: 将我们之前创建的 Node.js 应用程序容器化

  • Dockerfile

    • 一个文本文件,它包含了一系列指令,用于告诉 Docker 如何构建一个容器镜像。它就像一个“菜谱”。
  • 编写 Dockerfile

    1. 在项目根目录创建名为 Dockerfile 的文件:vim Dockerfile

    2. 写入以下指令:

      # 1. 指定基础镜像
      # 我们从一个官方的、包含 Node.js 19 和轻量级 Alpine Linux 的镜像开始
      FROM node:19-alpine3.16
      
      # 2. 创建工作目录并设置权限
      # 在容器内部创建一个目录,并将其所有者设置为 'node' 用户 (这是基础镜像内置的用户)
      RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
      
      # 3. 设置工作目录
      # 后续的命令都会在这个目录下执行
      WORKDIR /home/node/app
      
      # 4. 复制 package.json 并安装依赖
      # 这一步是为了利用 Docker 的层缓存。只要 package.json 不变,就不需要重新安装依赖
      COPY --chown=node:node package*.json ./
      RUN npm install
      
      # 5. 切换到非 root 用户
      USER node
      
      # 6. 复制应用代码
      COPY --chown=node:node . .
      
      # 7. 暴露端口
      # 告诉 Docker,这个容器的应用会监听 3000 端口
      EXPOSE 3000
      
      # 8. 定义启动命令
      # 当容器启动时,执行这个命令
      CMD [ "node", "app.js" ]
      
      
  • 安装 Docker 并构建镜像

    1. 安装 Docker: sudo apt install docker.io
    2. 构建镜像:
      • sudo docker build -t node-fsfe .
        • sudo: 因为 Docker 守护进程需要 root 权限。
        • t node-fsfe: t (tag) 给镜像起一个名字,方便后续引用。
        • .: 指定 Dockerfile 所在的上下文目录(当前目录)。
    3. 查看镜像: sudo docker image ls,确认你的镜像已成功创建。
  • 运行容器

    1. sudo docker run -d -p 3000:3000 node-fsfe:
      • d: 在后台 (detached) 运行容器。
      • p 3000:3000: 端口映射 (p)。将宿主机 (host) 的 3000 端口映射到容器内部的 3000 端口。这是让外部能够访问到容器内服务的关键。格式是 [host_port]:[container_port]
      • node-fsfe: 要运行的镜像名称。
  • 运行多个实例

    • Docker 的强大之处在于可以轻松地启动同一个镜像的多个实例。
    • sudo docker run -d -p 3001:3000 node-fsfe
    • 现在,我们有两个完全隔离的容器实例在运行:一个可以通过宿主机的 3000 端口访问,另一个通过 3001 端口访问。
    • sudo docker ps: 查看正在运行的容器。

51-orchestration-load-balancing

  • 容器编排 (Orchestration)
    • 问题: 我们已经可以手动创建和运行几个容器,但当需要管理成百上千个容器时,手动操作变得不可行。
    • 编排: 自动地部署、管理、扩展和联网容器。编排工具负责处理诸如服务发现、健康检查、滚动更新、故障恢复等复杂任务。
    • 主流工具:
      • Kubernetes (K8s): 目前事实上的行业标准。
      • Docker Swarm: Docker 自家的编排工具,更简单。
      • Apache Mesos: 传统的大规模集群管理系统。
  • 负载均衡 (Load Balancing)
    • 问题: 我们现在有多个运行相同应用的容器实例(例如,在 3000 和 3001 端口)。如何将传入的流量公平地分配给它们,以避免单个实例过载?
    • 负载均衡器: 一个位于客户端和后端服务器(我们的容器)之间的组件,它负责接收所有请求,并根据某种算法将请求分发到后端服务器集群。
    • Nginx 作为负载均衡器: Nginx 不仅是 Web 服务器和反向代理,还是一个非常优秀的软件负载均衡器。
  • 负载均衡算法 (Scheduling Algorithms)
    • Round Robin (轮询): 默认算法。按顺序将请求依次分配给每个服务器。
    • Least Connections (最少连接): 将新请求分配给当前活动连接数最少的服务器。
    • IP Hash (IP 哈希): 根据客户端的 IP 地址进行哈希计算,确保来自同一个客户端的请求总是被发送到同一个服务器。这对于需要维持会话状态的应用很有用。
  • 查看服务器负载
    • htop: 一个交互式的进程查看器,比 top 命令更友好、信息更丰富。它可以实时显示 CPU 使用率、内存使用情况、平均负载 (load average) 以及各个进程的资源消耗。
    • 这是诊断服务器性能问题的首选工具。

52-adding-a-load-balancer

  • 在 Nginx 中实现负载均衡

    • upstream: 这是 Nginx 用于定义一组后端服务器(我们的容器实例)的配置块。
  • 配置步骤

    1. 编辑 Nginx 主配置文件: sudo vim /etc/nginx/nginx.conf

    2. 定义 upstream: 在 http 块内部,但在任何 server 块之外,定义你的后端服务器集群。

      http {
          # ... 其他配置 ...
      
          upstream node_backend {
              # 这是我们的容器实例列表
              server localhost:3000;
              server localhost:3001;
          }
      
          # ... 你的 include 指令 ...
      }
      
      
      • node_backend 是我们给这个集群起的名字。
    3. 修改 server 块以使用 upstream:

      • 编辑你的虚拟主机配置文件 sudo vim /etc/nginx/sites-enabled/fsfe

      • proxy_pass 指令的目标从一个具体的地址 (localhost:3000) 修改为我们刚刚定义的 upstream 块的名称。

        server {
            # ... listen, server_name ...
            location / {
                proxy_pass http://node_backend;
            }
        }
        
        
    4. 验证并重启 Nginx:

      • sudo nginx -t
      • sudo service nginx restart
  • 验证负载均衡效果

    • 此时,Nginx 就会自动以轮询的方式,将请求交替发送到 3000 和 3001 端口的容器。
    • 要直观地看到这个过程,可以配置更详细的 Nginx 日志格式来记录请求被转发到了哪个后端服务器。
    1. nginx.conf 中定义新的日志格式:

      log_format upstream_log '$remote_addr ... $upstream_addr ...';
      
      
      • $upstream_addr: 这个变量会记录请求被转发到的具体后端服务器地址。
    2. 在你的 server 块中应用这个日志格式:

      access_log /var/log/nginx/access.log upstream_log;
      
      
    3. 重启 Nginx 后,通过 sudo tail -f /var/log/nginx/access.log 查看日志。

    4. 多次刷新你的网页,你会在日志中看到请求交替地被发送到 127.0.0.1:3000127.0.0.1:3001


53-wrapping-up

  • Q&A 和回顾
    • CORS (Cross-Origin Resource Sharing): 浏览器的一种安全机制。默认情况下,浏览器禁止网页向其来源(域、协议、端口)之外的服务器发起请求。CORS 允许服务器明确地声明“我允许来自 otherdomain.com 的请求”,从而安全地实现跨域资源访问。
    • 在服务器上使用 NVM: 完全可以。NVM (Node Version Manager) 是管理多个 Node.js 版本的优秀工具,在服务器上使用它没有问题,只是在课程中为了简化步骤而直接安装了 Node。
    • Docker 环境中的安全: Docker 容器通过命名空间和 cgroups 提供了很好的隔离。默认情况下,容器内用户(如 node 用户)权限受限,且只有通过 p 明确暴露的端口才能被外部访问,这本身就是一种安全措施。
    • Docker Hub: 类似于 GitHub,但用于存储和分享 Docker 镜像。你可以将本地构建的镜像推送到 Docker Hub,然后在任何其他机器上拉取并运行它。
  • 学习路径 (Learning Path)
    • 本课程是一个全面的入门,涵盖了全栈开发的各个层面。
    • Frontend Masters 提供了深入学习各个主题的完整路径,包括 Linux 命令行、Vim、容器、API 设计、AWS、各种数据库等。
  • 最终总结
    • 我们从零开始,走过了整个现代 Web 应用的构建之旅:
      • 理解了互联网的工作原理。
      • 购买并配置了域名和服务器。
      • 手动搭建了 Nginx Web 服务器。
      • 实现了反向代理、HTTPS、HTTP/2。
      • 构建了 Node.js 应用,并使用 WebSocket 实现了实时通信。
      • 集成了数据库来持久化数据。
      • 学习了容器化技术 (Docker) 并实现了负载均衡。
    • 你现在不仅知道“如何做”,更重要的是理解了“为什么这么做”。你已经具备了构建、部署和扩展一个功能齐全的现代 Web 应用所需要的基础知识和技能。
    • "你完成了一项非常艰巨的成就,为你自己感到骄傲。"