阿里云Serverless实践
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
创建服务
创建函数
使用容器镜像创建
函数详情
FC服务验证
公网访问地址
⚠️ 访问会直接下载文件
返回内容
{
"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": ""
}
至此实践全部完成,如有什么问题评论区留言一起交流学习