阿里云Serverless实践

275 阅读4分钟

Serverless简介

概念

Serverless的全称是Serverless computing无服务器运算,又被称为函数即服务(Function-as-a-Service,缩写为 FaaS),是云计算的一种模型。以平台即服务(PaaS)为基础,无服务器运算提供一个微型的架构,终端客户不需要部署、配置或管理服务器服务,代码运行所需要的服务器服务皆由云端平台来提供。

价值

  • 免运维:无需管理基础设施,可以专注业务开发
  • 按量计费:闲时不计费,降低成本
  • 弹性伸缩:峰时自动扩容,无需考虑可用性问题

这是一条朴实无华的分割线


创建项目并调试

项目文件

package.json

{
  "name": "fc",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body": "^5.1.0",
    "raw-body": "^2.4.3"
  }
}

.npmrc

"registry"="https://registry.npm.taobao.org/"

app.js


/**
 * Module dependencies.
 */

var app = require('./index.js');
console.log(app)
var http = require('http');
var port = normalizePort(process.env.PORT || '3000');
var server = http.createServer(app.handler);

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

function normalizePort(val) {
  var port = parseInt(val, 10);
  if (isNaN(port)) {
    // named pipe
    return val;
  }
  if (port >= 0) {
    // port number
    return port;
  }
  return false;
}

/**
 * Event listener for HTTP server "error" event.
 */

function onError(error) {
  if (error.syscall !== 'listen') {
    throw error;
  }
  var bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;
  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
      break;
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
      break;
    default:
      throw error;
  }
}

/**
 * Event listener for HTTP server "listening" event.
 */

function onListening() {
  var addr = server.address();
  var bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port;
  console.log('Listening on ' + bind);
}

index.js

var getRawBody = require('raw-body');
var getFormBody = require('body/form');
var body = require('body');

exports.handler = (req, resp, context) => {
    console.log('hello world');
    var params = {
        path: req.path,
        queries: req.queries,
        headers: req.headers,
        method : req.method,
        requestURI : req.url,
        clientIP : req.clientIP,
    }

    getRawBody(req, function(err, body) {
        for (var key in req.queries) {
          var value = req.queries[key];
          resp.setHeader(key, value);
        }
        resp.setHeader("Content-Type", "text/plain");
        params.body = body.toString();
        resp.end(JSON.stringify(params, null, '    '));
    });
}

调试项目

1.安装依赖

[root@galaxy-node-master fc]# npm install

added 19 packages in 785ms
[root@galaxy-node-master fc]# ls
app.js  Dockerfile  index.js  node_modules  package.json  package-lock.json

2.服务启动

[root@galaxy-node-master fc]# node app.js
{ handler: [Function (anonymous)] }
Listening on port 3000
hello world

3.请求接口

[root@galaxy-node-master ~]# curl -v 127.0.0.1:3000/11/1/1/1/11111
* About to connect() to 127.0.0.1 port 3000 (#0)
*   Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 3000 (#0)
> GET /11/1/1/1/11111 HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 127.0.0.1:3000
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Date: Wed, 16 Feb 2022 10:29:01 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< Content-Length: 195
<
{
    "headers": {
        "user-agent": "curl/7.29.0",
        "host": "127.0.0.1:3000",
        "accept": "*/*"
    },
    "method": "GET",
    "requestURI": "/11/1/1/1/11111",
    "body": ""
}

服务正常

FC 发布过程

一、容器创建

创建Dockerfile

FROM node:latest
WORKDIR /work
COPY app.js .npmrc  index.js package.json  package-lock.json /work/
RUN npm install
CMD node /work/app.js

构建容器

## 当前目录下构建容器
[root@galaxy-node-master fc]# ls
app.js  Dockerfile  index.js  node_modules  package.json  package-lock.json

## 构建容器
[root@galaxy-node-master fc]#  docker build  -t  simple-node-js:0.0.1 .
Sending build context to Docker daemon  743.9kB
Step 1/5 : FROM node:latest
 ---> f8c8d04432c3
Step 2/5 : WORKDIR /work
 ---> Using cache
 ---> 803555ec4376
Step 3/5 : COPY app.js .npmrc  index.js package.json  package-lock.json /work/
 ---> Using cache
 ---> e2a875c1013b
Step 4/5 : RUN npm install
 ---> Using cache
 ---> 7d93e3f32443
Step 5/5 : CMD node /work/app.js
 ---> Using cache
 ---> 2a2a907370df
Successfully built 2a2a907370df
Successfully tagged simple-node-js:0.0.1

启动容器

[root@galaxy-node-master fc]# docker run -d -p 3000:3000 --name=simple-node-js simple-node-js:0.0.1
34e4b4694beb96c24bcfcc333a434af3c962b801f4f6b6388d7d74016326cb9e
[root@galaxy-node-master fc]# docker ps -a
CONTAINER ID   IMAGE      COMMAND     CREATED      STATUS                     PORTS           NAMES
34e4b4694beb   simple-node-js:0.0.1      "docker-entrypoint.s…"   17 seconds ago   Up 16 seconds              0.0.0.0:3000->3000/tcp, :::3000->3000/tcp                           simple-node-js

查看容器信息

[root@galaxy-node-master fc]# docker inspect 34e4b4694beb
[
    {
        "Id": "34e4b4694beb96c24bcfcc333a434af3c962b801f4f6b6388d7d74016326cb9e",
        "Created": "2022-02-16T10:40:00.646019726Z",
        "Path": "docker-entrypoint.sh",
        "Args": [
            "/bin/sh",
            "-c",
            "node /work/app.js"
        ],
        "State": {
            "Status": "running",
            "Running": true,
        },
        "Image": "sha256:2a2a907370df87f1e63db0c060cf23766969472a7ff2c05b6303e08d4350d853",

        "Name": "/simple-node-js",
        "RestartCount": 0,
        "Driver": "overlay2",
        "Platform": "linux",
        "MountLabel": "",
        "ProcessLabel": "",
        "AppArmorProfile": "",
        "ExecIDs": null,
        "HostConfig": {
            "Binds": null,
            "ContainerIDFile": "",
            "LogConfig": {
                "Type": "json-file",
                "Config": {}
            },
            "NetworkMode": "default",
            "PortBindings": {
                "3000/tcp": [
                    {
                        "HostIp": "",
                        "HostPort": "3000"
                    }
                ]
            },
        },
        "GraphDriver": {
                
        },
        "Mounts": [],
        "Config": {
            "Hostname": "34e4b4694beb",
            "Domainname": "",
            "User": "",
            "AttachStdin": false,
            "AttachStdout": false,
            "AttachStderr": false,
            "ExposedPorts": {
                "3000/tcp": {}
            },
            "Tty": false,
            "OpenStdin": false,
            "StdinOnce": false,
            "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "NODE_VERSION=17.5.0",
                "YARN_VERSION=1.22.17"
            ],
            "Cmd": [
                "/bin/sh",
                "-c",
                "node /work/app.js"
            ],
            "Image": "simple-node-js:0.0.1",
            "Volumes": null,
            "WorkingDir": "/work",
            "Entrypoint": [
                "docker-entrypoint.sh"
            ],
            "OnBuild": null,
            "Labels": {}
        },
        "NetworkSettings": {
            
        }
    }
]

验证服务

[root@galaxy-node-master ~]# curl -v 127.0.0.1:3000/11/1/
* About to connect() to 127.0.0.1 port 3000 (#0)
*   Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 3000 (#0)
> GET /11/1/ HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 127.0.0.1:3000
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Date: Wed, 16 Feb 2022 10:44:02 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< Content-Length: 186
<
{
    "headers": {
        "user-agent": "curl/7.29.0",
        "host": "127.0.0.1:3000",
        "accept": "*/*"
    },
    "method": "GET",
    "requestURI": "/11/1/",
    "body": ""
}

二、容器发布阿里云

docker login --username=swe_ling@aliyun.com registry.cn-qingdao.aliyuncs.com
docker build  --platform linux/amd64 -tag node-poster:1.0.2 .
docker tag [ImageId] registry.cn-qingdao.aliyuncs.com/poster-service/poster:[镜像版本号]
docker push registry.cn-qingdao.aliyuncs.com/poster-service/poster:[镜像版本号]

阿里云说明文档

1. 登录阿里云Docker Registry

$ docker login --username=swe_ling@aliyun.com registry.cn-qingdao.aliyuncs.com 

用于登录的用户名为阿里云账号全名,密码为开通服务时设置的密码。

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

2. 从Registry中拉取镜像

$ docker pull registry.cn-qingdao.aliyuncs.com/poster-service/poster:[镜像版本号]

3. 将镜像推送到Registry

$ docker login --username=swe_ling@aliyun.com registry.cn-qingdao.aliyuncs.com
$ docker tag [ImageId] registry.cn-qingdao.aliyuncs.com/poster-service/poster:[镜像版本号]
$ docker push registry.cn-qingdao.aliyuncs.com/poster-service/poster:[镜像版本号]

请根据实际镜像信息替换示例中的[ImageId]和[镜像版本号]参数。

4. 选择合适的镜像仓库地址

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

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

5. 示例

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

$ docker imagesREPOSITORY                                                         TAG                 IMAGE ID            CREATED             VIRTUAL SIZEregistry.aliyuncs.com/acs/agent                                    0.7-dfb6816         37bb9c63c8b2        7 days ago          37.89 MB$ docker tag 37bb9c63c8b2 registry-vpc.cn-qingdao.aliyuncs.com/acs/agent:0.7-dfb6816

使用 "docker push" 命令将该镜像推送至远程。

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

发布容器镜像服务 ACR

本地创建TAG

[root@galaxy-node-master fc]#  docker build  -t  simple-node-js:0.0.1 .
Sending build context to Docker daemon  743.9kB
... ...
Successfully built 2a2a907370df
Successfully tagged simple-node-js:0.0.1

[root@galaxy-node-master fc]# docker images
REPOSITORY       TAG     IMAGE ID       CREATED          SIZE
simple-node-js   0.0.1   2a2a907370df   48 minutes ago   996MB

创建镜像TAG

docker tag simple-node-js:0.0.1 registry.cn-qingdao.aliyuncs.com/poster-service/poster:simple-0.0.1

发布镜像TAG

docker push registry.cn-qingdao.aliyuncs.com/poster-service/poster:simple-0.0.1

本地验证容器正常

删除本地镜像

[root@galaxy-node-master fc]# docker rmi registry.cn-qingdao.aliyuncs.com/poster-service/poster:simple-0.0.1
Untagged: registry.cn-qingdao.aliyuncs.com/poster-service/poster:simple-0.0.1
Untagged: registry.cn-qingdao.aliyuncs.com/poster-service/poster@sha256:efb49dcd51e7d790ec9dcb4ad466284676109dfd08d6e4e30ea31fa388ed682d

拉取远程镜像

docker pull registry.cn-qingdao.aliyuncs.com/poster-service/poster:simple-0.0.1

启动Docker

docker run -p 9000:9000  registry.cn-qingdao.aliyuncs.com/poster-service/poster:simple-0.0.1

服务验证

[root@galaxy-node-master ~]# curl -v 127.0.0.1:3000/11/1/
* About to connect() to 127.0.0.1 port 3000 (#0)
*   Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 3000 (#0)
> GET /11/1/ HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 127.0.0.1:3000
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Date: Wed, 16 Feb 2022 10:44:02 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
< Content-Length: 186
<
{
    "headers": {
        "user-agent": "curl/7.29.0",
        "host": "127.0.0.1:3000",
        "accept": "*/*"
    },
    "method": "GET",
    "requestURI": "/11/1/",
    "body": ""
}

三、服务挂载

ServerLess挂载Docker

运行环境 Custom Container

创建服务

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ag16z06P-1645009873316)(.assets/image-20211102164849932-5002690.png)]

创建函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bge4aRQR-1645009873318)(.assets/image-20211102164909866-5002700.png)]

使用容器镜像创建

在这里插入图片描述

在这里插入图片描述

函数详情

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e5wTqFLs-1645009873322)(.assets/image-20220216190043427.png)]

FC服务验证

公网访问地址

在这里插入图片描述

:warning: 访问会直接下载文件

返回内容

{
    "headers": {
        "host": "506583.cn-qingdao.fc.aliyuncs.com",
        "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36",
        "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
        "accept-language": "zh-CN,zh;q=0.9",
        "sec-ch-ua": "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"98\", \"Google Chrome\";v=\"98\"",
        "x-forwarded-proto": "https",
        "accept-encoding": "gzip"
    },
    "method": "GET",
    "requestURI": "/simple/?spm=5176.fcnext.0.0.5c6278c8m2FWWR",
    "body": ""
}