<Bootstrapping.Microservices.with.Docker.Kubernetes.and.Terraform> 读书笔记


1.  作者2013开始关注微服务, 2018年才重新关注. 曾经学习到放弃K8s数次. 说明学习曲线太陡峭

2. MicroService First,  从头创建一个粗糙的微服务架构,比重构一个单体应用要简单多. 关键现在的工具使得创建一个分布式的应用的花费比单体应用不会多很多.

3.MicroService 可以用不同的技术栈,(我的理解: 比如你要部署个blog, 你知道WordPress是最好的,但你的技术栈是NET或JAVA,假如是单体应用,你不会想用它,你会想我要不要找一个同一个技术栈的,但功能差很多的.)部署更新测试的花费要少很多.

因为要用到很多命令行命令, 所以我们可以把命令行窗口的起始目录改为代码目录,这样可以减少操作,参考这个

windows怎么修改cmd命令提示符的默认初始路径

Node.js 用到的代码

const express = require("express");
const fs = require("fs");  // 如果没有引用, 就会出现 ReferenceError: fs is not defined

书本上, 搭建微服务用到的命令


 //更改npm 淘宝镜像,速度快很多

npm init -y //创建package.json文件

npm install --save express //安装Express HTTP Server, 更新package.json文件, 并创建package-lock.json(指定依赖的具体版本号)
node index.js  //执行index.js

set PORT=3000 //假如把端口定义放在环境变量里,就要手工指定

npm install --save-dev nodemon //在开发环境安装热重载nodemon, 在package.json里定义script "start:dev": "nodemon index.js"

//如果删掉了node_modules,就要用npm install 重新安装
npm run start:dev //使用热重载nodemon,修改index.js会马上重启Nodejs,调试用这个命令,调试好之后才用docker build,docker run的命令
docker build -t video-streaming --file Dockerfile .   //建立一个Docker Image 用指定的Tagname "video-streaming" 可以把Image理解为操作系统的镜像
docker run -d -p 3000:3000 video-streaming        //根据Image创建Containter, Container(容器):容器是Image的一个具体的实例 容器的实质是进程
docer container list                 //找到容器的id,比如是6f0835f32957
docker logs              //查看容器里面的log 例如 docker logs 6f0835f32957

docker rmi --force //删除镜像,假如一个Image被多次引用,必须加上--force参数来删除

docker-compose up --build   //保证Build之后再启动Docker
docker-compose down      //相对于正常关机,尽量不用Ctrl +C
//编写短脚本来缩写
? down.sh for docker-compose down
? reboot.sh for docker-compose down && docker-compose up --build
 

 书本上讲的是用Private Docker Registery,  如果没有网络要把Image 复制到另一台机,

docker pull  # 必须先 pull 镜像到本地
docker save -o     #例如 docker save -o myDockerImage video-streaming
使用 rsync 或 scp 复制到远程服务器,然后

docker load -i 

Docker Compose 是开发的最佳选择,但可能不是最好的 生产的选择。

Docker Compose is the best option for development, but probably not the best option for production.

我们可以通过直接连接我们的视频流微服务来到云存储提供商。 但我们不会那样做。 相反,我们将采用良好的设计原则,即关注点分离和单一职责原则;

我们将创建一个 新的微服务,其目的是成为我们文件存储提供程序的抽象。这使得以后更换存储机制和 可以为我们的应用程序支持多个存储提供程序铺平道路。

We could add cloud storage by directly connecting our video-streaming microservice
to the storage provider. We won’t do that. Instead, we’ll employ good design principles, namely, separation of concerns and single responsibility principle; and we’ll create a
new microservice whose purpose is to be an abstraction of our file storage provider.

  如果您正在考虑将此处提供的代码移植到 AWS 或 GCP,从 Azure 存储微服务转换到另一个提供商不是一个简单的任务。

in case you were thinking of porting the code presented here over to AWS or GCP, converting from the Azure store microservice over to another provider is not a simple task.

 

对标书上的Azure的Private Container Registery,国内的是阿里云的容器镜像服务

创建命名空间,仓库名称后, 阿里云可以从各个代码源,根据代码变更自动更新镜像, 我们就选择本地仓库好了

创建后,页面提示具体的使用方法

1. 登录阿里云Docker Registry
$ docker login --username=<阿里云账号> registry.cn-guangzhou.aliyuncs.com
用于登录的用户名为阿里云账号全名,密码为开通服务时设置的密码。

您可以在访问凭证页面修改凭证密码。

2. 从Registry中拉取镜像
$ docker pull registry.cn-guangzhou.aliyuncs.com/<命名空间>/<仓库名>:[镜像版本号]
3. 将镜像推送到Registry
$ docker login --username=<阿里云账号> registry.cn-guangzhou.aliyuncs.com
$ docker tag [ImageId] registry.cn-guangzhou.aliyuncs.com/<命名空间>/<仓库名>:[镜像版本号]
$ docker push registry.cn-guangzhou.aliyuncs.com/<命名空间>/<仓库名>:[镜像版本号]
请根据实际镜像信息替换示例中的[ImageId]和[镜像版本号]参数。

4. 选择合适的镜像仓库地址
从ECS推送镜像时,可以选择使用镜像仓库内网地址。推送速度将得到提升并且将不会损耗您的公网流量。

如果您使用的机器位于VPC网络,请使用 registry-vpc.cn-guangzhou.aliyuncs.com 作为Registry的域名登录。

5. 示例
使用"docker tag"命令重命名镜像,并将它通过专有网络地址推送至Registry。

$ docker images
REPOSITORY                                                         TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
registry.aliyuncs.com/acs/agent                                    0.7-dfb6816         37bb9c63c8b2        7 days ago          37.89 MB
$ docker tag 37bb9c63c8b2 registry-vpc.cn-guangzhou.aliyuncs.com/acs/agent:0.7-dfb6816
使用 "docker push" 命令将该镜像推送至远程。

$ docker push registry-vpc.cn-guangzhou.aliyuncs.com/acs/agent:0.7-dfb6816

容器镜像服务已停止控制台授权功能,请先删除控制台授权再迁移至RAM控制台设置子账号权限,具体参考文档 《RAM权限管理文档》。

RAM 访问控制 (aliyun.com)

如果你用书上用Image的名字来打Tag,不能Push上去,错误提示如下,一定要用

bad4f9146914: Preparing
0c3f34be7e88: Preparing
4da12ed8ecbc: Preparing
23c426cdc5d6: Preparing
58130d3fd6ad: Preparing
c99dbbb91c1e: Waiting
743fabfe0b03: Waiting
529f2cda666c: Waiting
1a058d5342cc: Waiting
denied: requested access to the resource is denied

docker tag  a244a617f4cf  registry.cn-guangzhou.aliyuncs.com<命名空间>/<仓库名>:video-streaming

docker push registry.cn-guangzhou.aliyuncs.com/<命名空间>/<仓库名>:video-streaming

The push refers to repository [registry.cn-guangzhou.aliyuncs.com/meta88/metabase]
bad4f9146914: Pushed
0c3f34be7e88: Pushed
4da12ed8ecbc: Pushed
23c426cdc5d6: Pushed
58130d3fd6ad: Pushed
c99dbbb91c1e: Pushed
743fabfe0b03: Pushed
529f2cda666c: Pushed
1a058d5342cc: Pushed
video-streaming: digest: sha256:11119fcaa3d54b36904336cbf38318bab7bfe0e2c019033588d3faf8ac5e42fd size: 2203

Dockerfile里面注意有基准路径,假如没有把video目录copy进去, const path ="./videos/SampleVideo_1280x720_1mb.mp4"; 这句代码就找不到路径了

FROM node:12.22.8-alpine

WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install 
COPY ./src ./src
COPY ./videos ./videos
CMD npm run start:dev

 第4章,把Video的存储在Azure里作为一个单独的服务,注意Azure的Container的名字是否和代码的一样,否则会找不到

 每个单独的服务,最好在每个微服务目录下面单独写一个up.bat,例如存储这个微服务

set PORT=3000
set STORAGE_ACCOUNT_NAME=XXX
set STORAGE_ACCESS_KEY=XXXXXXXXXXXXXXX

npm run start:dev 

用Docker-Compose 命令是把批量的container都启动起来,假如发现问题时要修改代码,我要修改重启某一个微服务时,要怎样做呢?

用DockerDesktop管理界面,可以停止重启,但是不会重新build的,

docker-compose restart history

 解决方法: 在第5章126页, 不要把代码拷贝进Container,    COPY ./src ./src, 用Volumes映射,左边是宿主机的路径,右边是container的路径,为啥是/usr/src/app?

    volumes:
      - /tmp/video-streaming/npm-cache:/root/.npm:z
      - ./video-streaming/src:/usr/src/app/src:z
      - ./video-streaming/videos:/usr/src/app/videos:z

To enable live reload on a larger scale, we synchronize our code between our development workstation and the container so that changes to the code automatically propagate through into the container

假如我输入的中文的引号,编译错误会这样的

SyntaxError: Invalid or unexpected token
    at Object.compileFunction (node:vm:352:18)
    at wrapSafe (node:internal/modules/cjs/loader:1031:15)
    at Module._compile (node:internal/modules/cjs/loader:1065:27)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:17:47

我们可以将应用程序的文件和数据存储在 Kubernetes 集群中,但是我更喜欢我的生产集群是无状态的。

我们使用云存储而不是集群存储,因为它很简单,它适用于我们在开发中运行,它很便宜,并且为我们管理。这意味着我可以摧毁和重建随意集群,没有丢失数据的风险。

我们的应用程序最终将成为许多微服务之间通信的蜘蛛网。 一个服务的变化将有可能导致整个应用程序出现指数级的问题。

精心构建微服务之间的接口,尽量减少它们之间的耦合,帮助我们充分利用我们的微服务架构。

Docker里面微服务的端口尽量不要和宿主机的端口重复,比如MongoDB默认的端口是27017,因为宿主机可能已经用了27017,

On our host operating system (OS), we have mapped the port to 4000. That’s an arbitrary choice. We could have given it any number, including 27017. I prefer not to give it the standard MongoDB port because that would conflict with an instance of MongoDB that we might have running on our host OS.

如果我们使用路径来识别我们的视频,那么以后很难将视频移动到一个不同的位置,如果将来我们决定要重组我们的存储文件系统。

因为是各种其他数据库和记录已经引用了我们的视频路径

If we use paths to identify our videos, that makes it difficult to later move videos to a different location if in the future we decide we’d like to restructure our storage filesystem. The reason this is a problem is that various other databases and records will need to refer to our videos. 

在VSCode里安装MongoDB的扩展,可以查询管理MongoDB. Working with MongoDB in Visual Studio Code

添加数据库Video-streaming和Collection

 添加记录,

MongoDB 中存储的文档必须有一个"_id" 键。这个键的值可以是任何类型的,默认是个ObjectId 对象。

ObjectId 使用12 字节的存储空间,每个字节两位十六进制数字,是一个24 位的字符串。如果插入文档的时候没有"_id" 键,系统会自动帮你创建一个

假如要制定OjectId,就要这样写

// The current database to use.
use('video-streaming');

db.videos.insertOne({
  "_id":ObjectId("5d9e690ad76fe06a3d7ae416"),
 "videoPath" : "SampleVideo_1280x720_1mb.mp4" 
})

刚才我们手工插入数据到MongoDB, 假如用Docker-Compose命令重启后,数据就消失了.要重新插入

 

第4章例子3的 这个写法,为啥不会替换成变量值呢???  要用模板字符串的话不能用单双引号(" '),要用反引号(`)。Tab键上面那个。

console.log(`Translated id ${videoId} to path ${videoRecord.videoPath}.`); 

===============================================================================

数据库要防止Docker的集群里吗,建议不要,因为数据库是有状态的, 集群最好是没有状态的

每个微服务应该有自己的数据库,而且数据库不应该共享出来,共用数据库会导致事实上的耦合,系统脆弱很难扩容

==================================================================================

 The HTTP GET request received by the video-streaming microservice was forwarded to the video-storage microservice.

This is the first and simplest form of communication that one microservice can use to request or delegate tasks to another.

某些用例通常需要直接消息传递。 它有一个主要的缺点 它需要将位于两端的两个微服务紧密耦合沟通。 通常,我们更愿意避免微服务之间的紧密耦合,

因此,我们将频繁使用间接消息传递而不是直接消息

We direct the request to the history microservice using the URL http://history/viewed.

This URL incorporates both the hostname (history in this case) and the route (viewed in this case)

docker 自带DNS???  对应的是Container的名字, docker-compose.yml里面有多个history,我们把他们改名就知道对应哪一个了

 history0:
    image: history1
    build: 
      context: ./history
      dockerfile: Dockerfile-dev
    container_name: history3
    volumes:
      - /tmp/history/npm-cache:/root/.npm:z
      - ./history/src:/usr/src/app/src:z
    ports:
      - "4002:80"
    environment:
      - PORT=80
      - DBHOST=mongodb://db:27017
      - DBNAME=history
      - NODE_ENV=development
    depends_on:
      - db
    restart: "no"

 另外有个疑问,为什么我访问一次 http://localhost:4001/video,却会有2条history的记录呢?   第5章的第2个例子,查看F12,看到有2个请求,一个是document,一个是media

{"history":[{"_id":"61d5b4e00e2266003d3166f7","videoPath":"./videos/SampleVideo_1280x720_1mb.mp4"},{"_id":"61d5b4e00e2266003d3166f8","videoPath":"./videos/SampleVideo_1280x720_1mb.mp4"}]}