阿里云 serverless 初探

阿里云 FaaS 云函数应该是基于 Kubernates 和容器 的 Serverless 架构。今天以阿里云 http 的云函数为例子,记录探索的过程。

Runtime

此前没有使用 FaaS 的经验, 所以好奇云函数是运行在 Nodejs 环境中还是受限的 VM 环境中。

确认 require 函数 是否可以用

exports.handler = (event, context, callback) => {

  const os = require('os');
  const cpus = os.cups();
  callback(null, `hello 
    ${JSON.stringify(cpus, null, 2)}
  `);
}
复制代码

打印

 [
  {
    "model": "Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz",
    "speed": 2499,
    "times": {
      "user": 220400,
      "nice": 0,
      "sys": 106800,
      "idle": 22482700,
      "irq": 0
    }
  },
  {
    "model": "Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz",
    "speed": 2499,
    "times": {
      "user": 248000,
      "nice": 0,
      "sys": 102900,
      "idle": 22490600,
      "irq": 0
    }
  }
]
复制代码

可见:

  1. require 模块是可以使用的
  2. 运行实例的服务器CPU配置还是挺高的(给阿里点赞)

确认用户代码路径

Module模块有两个参数: filename, paths, 分别代表了当前模块路径和模块查找的路径。

exports.handler = (event, context, callback) => {

  callback(null, `hello 
    ${require.main.filename} // 主模块
    ${require.main.paths}
    ${module.filename} // 当前模块
    ${module.paths} //
  `);
}
复制代码

打印

  • 主模块
    • 主模块路径 – /var/fc/runtime/nodejs10/src/server.js
    • 主模块寻址路径 – /var/fc/runtime/nodejs10/src/node_modules,/var/fc/runtime/nodejs10/node_modules,/var/fc/runtime/node_modules,/var/fc/node_modules,/var/node_modules,/node_modules
  • 用户模块
    • 用户模块路径 – /code/index.js
    • 用户模块寻址路径 – /code/node_modules,/node_modules

由此可见:

  1. 用户模块挂载到 /code 目录下
  2. 程序注入口在 /var/fc/runtime/nodejs10 目录下

通过以上信息,我们基本上可以判断程序是运行在完整的 Nodejs 环境

Containerd

确定了应用是运行在完整 Nodejs 环境后, 那么Nodejs又是运行在什么环境中呢?基于目前对国内Faas的一点点认知,猜测是运行在 Containerd 中。由于Nodejs核心包都可以使用,所以我们直接使用child_process.spawn来查看容器内的信息。

exports.handler = (event, context, callback) => {
    const { spawnSync} = require('child_process');
    const {stdout } = spawnSync('cat', ['/proc/self/cgroup'])
  callback(null, `hello 
    ${stdout.toString()}
  `);
}
复制代码

第一次打印:
11:hugetlb:/docker/f8d493c517bfb01120053b23882eb33cc326b748d91d347bf15696e13ddcd43d
第二次打印(修改代码,再次执行):
11:hugetlb:/docker/141a4a8f9cabf953c4c0510874ffc0023c62276b055dac33914b28a436e3190a

可见:

  1. Nodejs 运行在容器环境中(能看出来阿里 K8S 调度的是 Docker 还是 Containerd 吗)
  2. 每次代码修改执行都会启动新的容器

serverless framework

通过上面的步骤我们可知:

  1. 代码的主入口在 /var/fc/runtime/nodejs10/src/server.js
  2. 可以使用任意的 Nodejs 模块 – 代表了获取了整个容器的权限

所以我们可以获取所有的代码, 下面是获取代码的方式:

const fs = require('fs');
const path = require('path');


let length = 1024*1024;
let byteOffset = 0;
let buffer = Buffer.alloc(length);
function addToBuffer(b) {
  if (!Buffer.isBuffer(b)) b = Buffer.from(b)
  const len = b.length;

  if (len + byteOffset > length)  {
    length = len < length/2 ? Math.floor(length*1.5) : length*2;
    const bigBuf = Buffer.alloc(length)
    buffer.copy(bigBuf)
    buffer = bigBuf;
  }

  b.copy(buffer, byteOffset)
  byteOffset = byteOffset + len;
  return buffer;
}

// 遍历目录, 把文件内容放到buffer中
function getFileContent(dir) {
  const dirs = fs.readdirSync(dir);
  for (const item of dirs) {
    const itemPath = path.join(dir, item);
    const stat = fs.statSync(itemPath);
    if (stat.isFile()) {
      addToBuffer(Buffer.from('-----------> '+itemPath+'\n\n'))
      const buffer = fs.readFileSync(itemPath);
      addToBuffer(buffer);
      addToBuffer(Buffer.from('\n\n\n\n'));
    } else {
      getFileContent(itemPath);
    }
  }
}

getFileContent('/var/fc/runtime/nodejs10/src');

console.log(buffer.toString())
复制代码

整个代码结构如下:

serverless
    ├── agent.sh
    ├── package.json
    └── src
        ├── buffer_builder.js
        ├── callback.js
        ├── config.js
        ├── console.js
        ├── constant.js
        ├── context.js
        ├── environment.js
        ├── even_loop.js
        ├── httpparam.js
        ├── invoke.js
        ├── logger.js
        ├── npp.js
        ├── pandora // pardora 目录, 没去打印
        ├── prepare_code.js
        ├── response_parser.js
        ├── sanitizer.js
        ├── server.js
        ├── setup.js
        ├── throttle.js
        └── validator.js
复制代码

通过 package.json 管中窥豹

{
    "author": "aliyun-fc",
    "name": "aliyun-fc-nodejs",
    "version": "1.0.0",
    "description": "This package contains nodejs container agent.",
    "main": "src/server.js",
    "license": "UNLICENSED",
    "readme": "README",
    "repository": "http://gitlab.alibaba-inc.com/serverless/lambda",
    "directories": {
      "test": "test"
    },
    "dependencies": {
      "@pandorajs/component-actuator-server": "^3.0.1",
      "@pandorajs/component-decorator": "^3.0.1",
      "@pandorajs/component-process-info": "^3.0.1",
      "@pandorajs/core-sdk": "^3.0.1",
      "@pandorajs/hub": "^3.0.1",
      "express": "^4.14.0",
      "winston": "^2.2.0",
      "winston-daily-rotate-file": "^1.2.0",
      "body-parser": "^1.15.2",
      "mkdirp": "^0.5.1"
    },
    "devDependencies": {
      "co": "^4.6.0",
      "body": "^5.1.0",
      "istanbul": "^0.4.4",
      "js-beautify": "^1.6.12",
      "raw-body": "^2.3.2",
      "mocha": "^3.0.2"
    },
    "engines": {
      "node": "*",
      "npm": ">=2.15.8"
    },
    "scripts": {
      "lint": "find src test -name '*.js' | xargs -I F js-beautify -r F",
      "start": "./agent.sh start",
      "test": "istanbul cover _mocha -- -t 10000"
    }
  }
复制代码

通过 server.js 窥探运行的生命周期

// Set up server.
var app = express();
app.post('/invoke', [setup(config), responseParser, validator.validateReqHeader, throttle, caRequest]);
app.post('/initialize', [setup(config), responseParser, validator.validateReqHeader, throttle, caRequest]);
app.get('/pre-stop', [setup(config), responseParser, validator.validateReqHeader, throttle, caRequest]);
app.get('/pre-freeze', [setup(config), responseParser, validator.validateReqHeader, throttle, caRequest]);
// add a new api to load code from disk into memory
app.post('/prepare_code', [setup(config), responseParser, validator.validatePrepareCode, throttle, prepare_code.prepareFunction]);
复制代码

总结

通过上面的信息,可以看到阿里的http FaaS 云函数是通过 express 承载业务逻辑,通过K8S和容器做负载扩容的, 整个思路比较简单。除了基本的业务流程, FaaS 还要求冷启动,毫秒响应,应用监控等等我不知道的东西。

在探索的过程中,有两个问题还没来得及实践:

  1. 能否获取到反弹shell
  2. 能够绕过 FaaS 的收费策略

关注我的微信公众号,欢迎留言讨论,我会尽可能回复,感谢您的阅读。

qrcode_for_gh_367076f642e3_344.jpg

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享