+ ret = handler.handle(st)
+ File "/home/app/function/handler.py", line 13, in handle
+ with open(file_name, "w") as f:
+OSError: [Errno 30] Read-only file system: '/home/app/1556714998.092464'
+```
+
+为了写到一个临时区域,设置环境变量`save_path`。
+
+```yaml
+...
+functions:
+ ingest-file:
+ lang: python3
+ handler: ./ingest-file
+ image: alexellis2/ingest-file:latest
+ readonly_root_filesystem: true
+ environment:
+ save_path: "/tmp/"
+```
+
+现在你可以再运行一次`faas-cli up -f ingest-file.yml`来测试这个修正,文件将被写入`/tmp/`。
+
+我们现在有能力锁定我们的函数代码,使其不能被意外改变或恶意更新。
+
+## 利用日志记录
+
+OpenFaaS 看门狗通过标准 I/O 流`stdin`和`stdout`传入 HTTP 请求和读取 HTTP 响应来运行。这意味着作为一个函数运行的进程不需要知道任何关于网络或 HTTP 的信息。
+
+一个有趣的情况是,当一个函数以非零退出代码退出时,`stderr`不是空的。
+默认情况下,一个函数的`stdout/stderr`是合并的,`stderr`不被打印到日志中。
+
+让我们用[Lab 3](./lab3.md#hello-world-in-python)中的`hello-openfaas`函数来检查。
+
+将`handler.py`的代码改为
+
+```python
+import sys
+import json
+
+def handle(req):
+
+ sys.stderr.write("This should be an error message.\n")
+ return json.dumps({"Hello": "OpenFaaS"})
+```
+
+构建和部署
+
+```sh
+faas-cli up -f hello-openfaas.yml
+```
+
+现在用以下方法调用该函数
+
+```sh
+echo | faas-cli invoke hello-openfaas
+```
+
+你应该看到合并输出。
+
+```plain
+This should be an error message.
+{"Hello": "OpenFaaS"}
+```
+
+> 注意:如果你用`docker service logs hello-openfaas`检查容器日志(或者`kubectl logs deployment/hello-openfaas -n openfaas-fn`),你应该看不到 stderr 输出。
+
+在这个例子中,我们需要这个函数返回有效的 JSON,可以被解析。不幸的是,日志信息使输出无效。
+所以我们需要将这些信息从 stderr 重定向到容器的日志中。
+OpenFaaS 提供了一个解决方案,所以你可以将错误信息打印到日志中,并保持函数响应的清晰,只返回`stdout`。
+为此你应该使用`combine_output`标志。
+
+让我们来试试。打开`hello-openfaas.yml`文件,添加这些行。
+
+```yaml
+ environment:
+ combine_output: false
+```
+
+部署并调用该函数。
+
+输出应该是。
+
+```plain
+{"Hello": "OpenFaaS"}
+```
+
+检查容器日志中的`stderr`。你应该看到类似的信息。
+
+```plain
+hello-openfaas.1.2xtrr2ckkkth@linuxkit-025000000001 | 2018/04/03 08:35:24 stderr: This should be an error message.
+```
+
+## 创建工作流
+
+在有些情况下,把一个函数的输出作为另一个函数的输入是很有用的。 这在客户端和通过 API 网关都可以实现。
+
+### 客户端上的连锁函数
+
+你可以使用`curl`、`faas-cli`或一些你自己的代码将一个函数的结果输送到另一个函数。这里有一个例子。
+
+优点。
+
+* 不需要代码 - 可以用 CLI 程序完成
+* 快速开发和测试
+* 容易在代码中建模
+
+缺点。
+
+* 额外的延迟 - 每个函数都要返回到服务器上
+* 聊天(更多的信息)
+
+例子。
+
+* 从*函数库*部署 NodeInfo 函数
+
+* 然后通过 Markdown 转换器推送 NodeInfo 的输出
+
+```sh
+$ echo -n "" | faas-cli invoke nodeinfo | faas-cli invoke markdown
+Hostname: 64767782518c
+
+Platform: linux
+Arch: x64
+CPU count: 4
+Uptime: 1121466
+```
+
+现在你会看到 NodeInfo 函数的输出被装饰成 HTML 标签,例如。``.
+
+另一个客户端函数链的例子可能是调用一个生成图像的函数,然后将该图像发送到另一个添加水印的函数中。
+
+### 从另一个函数中调用一个函数
+
+从另一个函数中调用一个函数的最简单方法是通过 OpenFaaS *API 网关*通过 HTTP 进行调用。这个调用不需要知道外部域名或 IP 地址,它可以简单地通过 DNS 条目将 API 网关称为`gateway`。
+
+当从一个函数访问 API 网关等服务时,最好的做法是使用环境变量来配置主机名,这很重要,有两个原因--名称可能会改变,在 Kubernetes 中有时需要一个后缀。
+
+优点。
+
+* 函数之间可以直接利用对方
+* 低延迟,因为函数可以在同一网络上相互访问
+
+缺点。
+
+* 需要一个代码库来进行 HTTP 请求
+
+例子。
+
+在[实验室 3](./lab3.md)中,我们介绍了 request 模块,并使用它来调用一个远程 API,以获得国际空间站上的一个宇航员的名字。我们可以使用同样的技术来调用部署在 OpenFaaS 上的另一个函数。
+
+* 使用用户界面,进入*函数商店*并部署*情感分析*函数。
+
+或者使用 CLI。
+
+```plain
+faas-cli store deploy SentimentAnalysis
+```
+
+情感分析函数将告诉你任何句子的主观性和极性(积极性评级)。该函数的结果是以 JSON 格式显示的,如下面的例子。
+
+```sh
+$ echo -n "California is great, it's always sunny there." | faas-cli invoke sentimentanalysis
+{"polarity": 0.8, "sentence_count": 1, "subjectivity": 0.75}
+```
+
+因此,结果显示我们的测试句子既非常主观(75%)又非常积极(80%)。这两个字段的值总是在`-1.00`和`1.00`之间。
+
+下面的代码可以用来调用*情绪分析*函数或任何其他函数。
+
+给网关主机加上`openfaas`命名空间的后缀。
+
+```python
+ r = requests.get("http://gateway.openfaas:8080/function/sentimentanalysis", text= test_sentence)
+```
+
+或者通过一个环境变量。
+
+```python
+ gateway_hostname = os.getenv("gateway_hostname", "gateway.openfaas") # uses a default of "gateway.openfaas" for when "gateway_hostname" is not set
+ test_sentence = "California is great, it's always sunny there."
+ r = requests.get("http://" + gateway_hostname + ":8080/function/sentimentanalysis", data= test_sentence)
+```
+
+由于结果总是以 JSON 格式出现,我们可以利用辅助函数`.json()`来转换响应。
+
+```python
+ result = r.json()
+ if result["polarity"] > 0.45:
+ return "That was probably positive"
+ else:
+ return "That was neutral or negative"
+```
+
+现在,在 Python 中创建一个新的函数,并将其全部整合起来
+
+```python
+import os
+import requests
+import sys
+
+def handle(req):
+ """handle a request to the function
+ Args:
+ req (str): request body
+ """
+
+ gateway_hostname = os.getenv("gateway_hostname", "gateway.openfaas") # uses a default of "gateway" for when "gateway_hostname" is not set
+
+ test_sentence = req
+
+ r = requests.get("http://" + gateway_hostname + ":8080/function/sentimentanalysis", data= test_sentence)
+
+ if r.status_code != 200:
+ sys.exit("Error with sentimentanalysis, expected: %d, got: %d\n" % (200, r.status_code))
+
+ result = r.json()
+ if result["polarity"] > 0.45:
+ return "That was probably positive"
+ else:
+ return "That was neutral or negative"
+```
+
+* 记得在你的`requirements.txt`文件中加入`requests`。
+
+注意:你不需要修改或改变 SentimentAnalysis 函数的源代码,我们已经部署了它并将通过 API 网关访问它。
+
+现在转到[实验室 5](lab5.md)。
diff --git a/translations/cn/lab5.md b/translations/cn/lab5.md
new file mode 100644
index 0000000..923a6b2
--- /dev/null
+++ b/translations/cn/lab5.md
@@ -0,0 +1,414 @@
+# Lab 5 - 创建一个 GitHub 机器人
+
+
+
+在开始这个实验之前,为你的文件创建一个新的文件夹。
+
+```sh
+$ mkdir -p lab5\
+ && cd lab5
+```
+
+我们将使用 OpenFaaS 的函数来创建一个名为 `issue-bot`的 GitHub 机器人。
+
+问题机器人的工作是通过分析 `描述` 字段的情绪来分流新的问题,然后它将应用一个*积极*或*审查*的标签。这将有助于维护者在繁忙的工作中,可以优先考虑哪些问题需要首先处理。
+
+问题机器人的图示](./diagram/issue-bot.png)
+
+## 获取一个 GitHub 账户
+
+* 注册一个[GitHub 账户](https://github.com),如果你还没有一个账户。
+
+* 创建一个新的仓库,并将其称为*bot-test*。
+
+注意:我们将只使用这个仓库作为创建问题的测试场所。你不需要在那里提交任何代码。
+
+## 建立一个带有入口的隧道
+
+你需要接收来自 GitHub 的 webhooks。幸运的是,inlets 让这一切变得非常快速和简单。它可以按月或按年订阅,所以如果你不确定是否全年都需要它,你可以只付一个月的钱。
+
+inlets 有一个叫做 inlets-operator 的 Kubernetes 集成。你可以用它来设置 LoadBalancers 或带有 TLS 的 Ingress。它的工作原理是为你创建一个云虚拟机,并在那里运行一个隧道服务器,然后为你运行一个隧道客户端作为一个 Pod,你就可以获得传入流量。
+
+在你喜欢的云提供商(如 DigitalOcean)的 API 页面下创建一个写入访问令牌,然后将内容保存到`digital-ocean-api-token.txt`。
+
+设置完订阅后,将你的密钥保存到`$HOME/.inlets/LICENSE`,然后运行以下程序。
+
+```bash
+arkade install inlets-operator \
+ --provider digitalocean \
+ --region lon1 \
+ --token-file $HOME/digital-ocean-api-token.txt
+```
+
+这将部署 inlets-operator,并指示它在 DigitalOcean 上为你的隧道服务器配置新的主机到伦敦地区。其他供应商和地区也可以使用,[更多信息请见文档](https://docs.inlets.dev/reference/inlets-operator/)。
+
+## 用网关的公共 IP 登录你的网关
+
+用信息检索你的网关密码,从。
+
+```bash
+arkade info openfaas
+```
+
+LoadBalancer 的公共 IP 大约需要 10-30 秒才能出现。
+
+```bash
+kubectl get svc -n openfaas gateway-external
+NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
+gateway-external LoadBalancer 10.96.29.46 8080:32278/TCP 5h56m
+gateway-external LoadBalancer 10.96.29.46 165.227.233.227 8080:32278/TCP 5h56m
+```
+
+然后把它保存到环境变量中。
+
+```bash
+export OPENFAAS_URL=http://165.227.233.227:8080
+```
+
+用给你的密码登录到公共 IP。
+
+```bash
+echo $PASSWORD | faas-cli login --password-stdin
+```
+
+最后测试远程 URL,如http://165.227.233.227:8080
+
+你可以通过设置`OPENFAAS_URL`环境变量或使用`--gateway`标志来对远程网关运行命令。
+
+如果你想用 TLS 证书和自定义域名来暴露 OpenFaaS,你可以按照这些说明来代替。
+
+```bash
+arkade install ingress-nginx
+arkade install cert-manager
+arkade install openfaas
+arkade install openfaas-ingress \
+ --email web@example.com \
+ --domain openfaas.example.com
+```
+
+然后创建一个 DNS A 记录,指向 ingress-nginx 的 IP 地址。
+
+```bash
+kubectl get svc ingress-nginx-controller
+NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
+NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
+ingress-nginx-controller LoadBalancer 10.96.179.20 80:30319/TCP,443:31591/TCP 20s
+ingress-nginx-controller LoadBalancer 10.96.179.20 209.97.135.63 80:30319/TCP,443:31591/TCP 52s
+```
+
+现在你可以为`https://openfaas.example.com`提供一个自定义的 TLS 记录。
+
+## 创建一个 webhook 接收器`issue-bot`
+
+```bash
+export OPENFAAS_PREFIX="docker.io/your-username"
+$ faas-cli new --lang python3 \
+ issue-bot
+```
+
+现在编辑该函数的 YAML 文件`issue-bot.yml`并添加一个环境变量`write_debug: true`。
+
+```yaml
+provider:
+ name: openfaas
+ gateway: http://127.0.0.1:8080
+
+functions:
+ issue-bot:
+ lang: python3
+ handler: ./issue-bot
+ image: docker.io/your-username/issue-bot
+ environment:
+ write_debug: true
+```
+
+* 构建、推送和部署该函数,使用
+
+```bash
+$ faas-cli up -f ./issue-bot.yml
+```
+
+## 从 GitHub 接收 webhooks
+
+重新登录 GitHub 并导航到你的仓库*bot-tester*。
+
+点击 *设置* -> *Webhooks* -> *添加 Webhook*
+
+![添加网络钩子](../../screenshot/add_github_webhook.png)
+
+现在输入你从 inlets 或你的自定义域中得到的 URL,在最后加上`/function/issue-bot`,例如。
+
+```plain
+https://openfaas.example.com
+```
+
+![添加webhook](../../screenshot/issue-bot-webhook.png)
+
+对于*Content-type*选择。*application/json*
+
+暂时将*Secret*留空。
+
+并选择 `让我选择个别事件`
+
+对于事件选择**事件**和**事件评论**。
+
+![设置事件](../../screenshot/WebhookEventsSettings.png)
+
+## 检查它是否有效
+
+现在去 GitHub,创建一个新问题。在标题和描述中输入 `test`。
+
+检查该函数被调用了多少次--这个数字至少应该是`1`。
+
+```sh
+$ faas-cli list
+Function Invocations
+issue-bot 2
+```
+
+每次你创建一个问题,由于 GitHub 的 API 调用了该函数,计数会增加。
+
+你可以通过输入`docker service logs -f issue-bot`(或`kubectl logs deployment/issue-bot -n openfaas-fn`)看到通过 GitHub 发送的有效载荷。
+
+GitHub 的 Webhooks 页面也会在 `Recent Deliveries`下显示每条发送的消息,你可以在这里重放一条消息,看看你的函数返回的响应。
+
+![Replaying an event](../../screenshot/github_replay.png)
+
+### 部署 SentimentAnalysis 函数
+
+为了使用这个 issue-bot 函数,你将需要首先部署 SentimentAnalysis 函数。
+这是一个 python 函数,为通过 TextBlob 项目输入的每个句子提供正/负(极性-1.0-1.0)和主观性的评级。
+
+如果你在[Lab 4](./lab4.md)中没有这样做,你可以从**函数商店**部署 `SentimentAnalysis`。
+
+```sh
+$ echo -n "I am really excited to participate in the OpenFaaS workshop." | faas-cli invoke sentimentanalysis
+Polarity: 0.375 Subjectivity: 0.75
+
+$ echo -n "The hotel was clean, but the area was terrible" | faas-cli invoke sentimentanalysis
+Polarity: -0.316666666667 Subjectivity: 0.85
+```
+
+### 更新`issue-bot`函数
+
+打开`issue-bot/handler.py`,用这段代码替换模板。
+
+```python
+import requests, json, os, sys
+
+def handle(req):
+
+ event_header = os.getenv("Http_X_Github_Event")
+
+ if not event_header == "issues":
+ sys.exit("Unable to handle X-GitHub-Event: " + event_header)
+ return
+
+ gateway_hostname = os.getenv("gateway_hostname", "gateway.openfaas")
+
+ payload = json.loads(req)
+
+ if not payload["action"] == "opened":
+ return
+
+ #sentimentanalysis
+ res = requests.post('http://' + gateway_hostname + ':8080/function/sentimentanalysis', data=payload["issue"]["title"]+" "+payload["issue"]["body"])
+
+ if res.status_code != 200:
+ sys.exit("Error with sentimentanalysis, expected: %d, got: %d\n" % (200, res.status_code))
+
+ return res.json()
+```
+
+用 HTTP/HTTPs 的请求模块更新你的`requirements.txt`文件。
+
+```plain
+requests
+```
+
+在`issue-bot.yml`文件中添加`gateway_hostname`环境变量,并将其值设置为`gateway.openfaas`。
+```plain
+ ...
+ environment:
+ gateway_hostname: "gateway.openfaas"
+ ...
+```
+
+上面代码中的下面一行将 GitHub 问题的标题和正文作为文本发布给`sentimentanalysis`函数。响应将是 JSON 格式。
+
+```python
+res = requests.post('http://' + gateway_hostname + ':8080/function/sentimentanalysis', data=payload["issue"]["title"]+" "+payload["issue"]["body"])
+```
+
+* 构建和部署
+
+使用 CLI 来构建和部署该函数。
+
+```plain
+$ faas-cli up -f issue-bot.yml
+```
+
+现在在`bot-test`仓库中创建一个新问题。GitHub 将通过我们之前配置的 Inlets 隧道向你的函数发送一个 JSON 有效载荷。
+
+你可以在 GitHub 上直接查看请求/响应--导航到*Settings* -> *Webhook*,如下所示。
+
+![](../../screenshot/WebhookResponse.png)
+
+## 回复到 GitHub
+
+下一步是让我们贴上 `正面` 或 `评论` 的标签,但由于这个动作涉及到向仓库写入内容,我们需要从 GitHub 获得一个*个人访问令牌*。
+
+### 为 GitHub 创建一个个人访问令牌
+
+进入你的*GitHub 配置文件* -> *设置/开发者设置* -> *个人访问令牌*,然后点击*生成新令牌*。
+
+![](../../screenshot/PersonalAccessTokens.png)
+
+勾选 `repo`的方框,允许访问你的存储库
+
+![](../../screenshot/NewPAT.png)
+
+点击页面底部的 `Generate Token`按钮
+
+在你的`issue-bot.yml`文件所在的目录中创建一个名为`env.yml`的文件,内容如下。
+
+```yaml
+environment:
+ auth_token:
+```
+
+用 GitHub 上的令牌更新`auth_token`变量。
+
+现在更新你的 issue-bot.yml 文件,告诉它使用`env.yml`文件。
+
+```yaml
+provider:
+ name: openfaas
+ gateway: http://127.0.0.1:8080
+
+functions:
+ issue-bot:
+ lang: python3
+ handler: ./issue-bot
+ image: /issue-bot
+ environment:
+ write_debug: true
+ gateway_hostname: "gateway.openfaas"
+ positive_threshold: 0.25
+ environment_file:
+ - env.yml
+```
+> `positive_threshold`环境变量用于微调一个问题是否获得`positive`或`review`标签。
+
+任何敏感信息都会被放在一个外部文件中(即`env.yml`),这样它就可以被包含在`.gitignore`文件中,这将有助于防止这些信息被存储在公共的 Git 仓库中。
+
+OpenFaaS 也支持使用原生的 Docker 和 Kubernetes 的secret,详情请见[Lab 10](lab10.md)
+
+### 通过 GitHub 的 API 应用标签
+
+你可以使用 API 来执行许多不同的任务,[文档在这里可以找到](https://github.com/PyGithub/PyGithub)。
+
+下面是一个 Python 代码的例子,我们可以用它来应用标签,但你先不要把它添加到你的函数中。
+
+```python
+issue_number = 1
+repo_name = "alexellis/issue_bot"
+auth_token = "xyz"
+
+g = Github(auth_token)
+repo = g.get_repo(repo_name)
+issue = repo.get_issue(issue_number)
+```
+
+这个用于 GitHub 的库是由社区提供的,不是官方的,但似乎很受欢迎。它可以通过我们的`requirements.txt`文件从`pip`调入。
+
+## 完成函数
+
+* 更新你的`issue-bot/requirements.txt`文件,为`PyGithub`添加一行内容
+
+```plain
+requests
+PyGithub
+```
+
+* 打开`issue-bot/handler.py`,将代码替换为以下内容。
+
+```python
+import requests, json, os, sys
+from github import Github
+
+def handle(req):
+ event_header = os.getenv("Http_X_Github_Event")
+
+ if not event_header == "issues":
+ sys.exit("Unable to handle X-GitHub-Event: " + event_header)
+ return
+
+ gateway_hostname = os.getenv("gateway_hostname", "gateway.openfaas")
+
+ payload = json.loads(req)
+
+ if not payload["action"] == "opened":
+ sys.exit("Action not supported: " + payload["action"])
+ return
+
+ # Call sentimentanalysis
+ res = requests.post('http://' + gateway_hostname + ':8080/function/sentimentanalysis',
+ data= payload["issue"]["title"]+" "+payload["issue"]["body"])
+
+ if res.status_code != 200:
+ sys.exit("Error with sentimentanalysis, expected: %d, got: %d\n" % (200, res.status_code))
+
+ # Read the positive_threshold from configuration
+ positive_threshold = float(os.getenv("positive_threshold", "0.2"))
+
+ polarity = res.json()['polarity']
+
+ # Call back to GitHub to apply a label
+ apply_label(polarity,
+ payload["issue"]["number"],
+ payload["repository"]["full_name"],
+ positive_threshold)
+
+ return "Repo: %s, issue: %s, polarity: %f" % (payload["repository"]["full_name"], payload["issue"]["number"], polarity)
+
+def apply_label(polarity, issue_number, repo, positive_threshold):
+ g = Github(os.getenv("auth_token"))
+ repo = g.get_repo(repo)
+ issue = repo.get_issue(issue_number)
+
+ has_label_positive = False
+ has_label_review = False
+ for label in issue.labels:
+ if label == "positive":
+ has_label_positive = True
+ if label == "review":
+ has_label_review = True
+
+ if polarity > positive_threshold and not has_label_positive:
+ issue.set_labels("positive")
+ elif not has_label_review:
+ issue.set_labels("review")
+```
+
+> 源代码也可在[issue-bot/bot-handler/handler.py](./issue-bot/bot-handler/handler.py)
+
+* 构建和部署
+
+使用 CLI 来构建和部署该函数。
+
+```plain
+$ faas-cli up -f issue-bot.yml
+```
+
+现在通过在`bot-test`仓库中创建一些新的问题来试试。检查 `正面`和 `评论`标签是否被正确应用,如果你不确定信息是否被传递或怀疑有错误被抛出,请查阅 GitHub Webhooks 页面。
+
+![](../../screenshot/bot_label_applied.png)
+
+> 注意:如果标签没有立即出现,请先尝试刷新页面。
+
+## 用 HMAC 验证有效载荷
+
+在[Lab 11](lab11.md)中,我们将学习如何通过使用 HMAC 保护无服务器函数不被篡改。
+
+现在转到[Lab 6](lab6.md)。
diff --git a/translations/cn/lab6.md b/translations/cn/lab6.md
new file mode 100644
index 0000000..727e20b
--- /dev/null
+++ b/translations/cn/lab6.md
@@ -0,0 +1,356 @@
+# 实验 6--你的函数的 HTML
+
+
+
+在开始这个实验之前,为你的文件创建一个新的文件夹。
+
+```plain
+$ mkdir -p lab6\
+ && cd lab6
+```
+
+## 从一个函数中生成并返回基本的 HTML
+
+函数可以返回 HTML,并将`Content-Type`设置为`text/html`。因此,函数返回的 HTML 可以通过浏览器进行渲染。让我们创建一个简单的函数,生成并返回一个基本的 HTML。
+
+```plain
+$ faas-cli new --lang python3 show-html --prefix=""
+```
+
+编辑`handler.py`。
+
+```python
+def handle(req):
+ """handle a request to the function
+ Args:
+ req (str): request body
+ """
+
+ html = 'Hi, from your function!
'
+
+ return html
+```
+
+这将返回 HTML 给调用者。 还有一件事我们应该做的是设置响应的`Content-Type'。我们100%确定这个函数将返回一个HTML,所以`Content-Type`应该总是`text/html`。我们可以利用`show-html.yml`文件中的`environment`部分来设置。
+
+编辑`show-html.yml`。
+
+```yaml
+provider:
+ name: openfaas
+ gateway: http://127.0.0.1:8080
+
+functions:
+ show-html:
+ lang: python3
+ handler: ./show-html
+ image: /show-html
+ environment:
+ content_type: text/html
+
+```
+
+`environment`中的`content_type`键将设置响应的`Content-Type`。
+
+现在构建、推送和部署该函数。
+
+```sh
+$ faas-cli up -f show-html.yml
+```
+
+运行以下程序以获得函数的 URL。
+
+```sh
+faas-cli describe -f show-html.yml show-html
+
+URL: http://127.0.0.1:8080/function/show-html
+```
+
+HTML 应该被正确渲染。
+
+## 从磁盘上读取并返回一个静态的 HTML 文件
+
+一般来说,当你提供 HTML 服务时,你有一个静态的 HTML 文件在前面。让我们看看我们如何在函数中打包 HTML 文件,并从 HTML 文件中提供内容。
+
+首先,让我们创建一个 HTML 文件。
+
+创建一个名为`html`的目录,并放置一个名为`new.html`的文件,使其结构看起来像下面这样。
+
+```plain
+├── show-html
+│ ├── __init__.py
+│ ├── handler.py
+│ ├── html
+│ │ └── new.html
+│ └── requirements.txt
+└── show-html.yml
+```
+
+Edit `new.html` :
+
+```html
+
+
+
+
+ OpenFaaS
+
+
+ Here's a new page!
+
+
+```
+
+现在把你的`handler.py`改为以下内容。
+
+```python
+import os
+
+def handle(req):
+ """handle a request to the function
+ Args:
+ req (str): request body
+ """
+
+ dirname = os.path.dirname(__file__)
+ path = os.path.join(dirname, 'html', 'new.html')
+
+ with(open(path, 'r')) as file:
+ html = file.read()
+
+ return html
+
+```
+
+现在构建、推送和部署该函数。
+
+```plain
+$ faas-cli up -f show-html.yml
+```
+
+打开你的浏览器,访问http://127.0.0.1:8080/function/show-html。你应该看到一个 `这里有一个新的页面!` 在浏览器中呈现的 HTML 页面。
+
+现在我们要为这个函数的 URL 添加一个路径。
+
+在`html`文件夹中添加新的`list.html`文件,内容如下。
+
+```html
+
+
+
+
+ OpenFaaS
+
+
+ This is a list!
+
+
+
+```
+
+将你的`handler.py`编辑成以下内容。
+
+```python
+import os
+
+def handle(req):
+
+ path = os.environ['Http_Path']
+ pathArr = path.split("/")
+ pageName = pathArr[1]
+
+ dirname = os.path.dirname(__file__)
+ page = os.path.join(dirname, 'html', pageName + '.html')
+
+ with(open(page, 'r')) as file:
+ html = file.read()
+
+ return html
+```
+
+构建、推送和部署该函数。
+
+```plain
+$ faas-cli up -f show-html.yml
+```
+
+现在在http://127.0.0.1:8080/function/show-html/new 或 http://127.0.0.1:8080/function/show-html/list 上打开你的网页。
+这将输出。
+```html
+Here's a new page!
+```
+and
+```html
+This is a list!
+
+```
+
+## 读取查询字符串并返回不同的 HTML
+
+现在我们已经了解了如何通过函数来提供 HTML,让我们动态地改变通过查询字符串提供的 HTML。正如我们在[实验室 4](./lab4.md)中学到的,查询字符串可以通过一个叫做`Http_Query`的环境变量来检索。假设我们做了一个看起来像这样的查询。
+
+ http://127.0.0.1:8080/function/show-html?action=new
+
+查询字符串是`action=new`,因此`Http_Query`的值将是`action=new`。我们也可以使用`urllib.parse`包中的`parse_qs`函数,轻松解析这个查询字符串。
+
+我们的函数的目录结构看起来是这样的。
+
+```plain
+├── show-html
+│ ├── __init__.py
+│ ├── handler.py
+│ ├── html
+│ │ ├── list.html
+│ │ └── new.html
+│ └── requirements.txt
+└── show-html.yml
+```
+
+
+改变你的`handler.py`。
+
+```python
+import os
+from urllib.parse import parse_qs
+
+def handle(req):
+ """handle a request to the function
+ Args:
+ req (str): request body
+ """
+
+ query = os.environ['Http_Query']
+ params = parse_qs(query)
+ action = params['action'][0]
+
+ dirname = os.path.dirname(__file__)
+ path = os.path.join(dirname, 'html', action + '.html')
+
+ with(open(path, 'r')) as file:
+ html = file.read()
+
+ return html
+```
+
+现在构建、推送和部署该函数。
+
+```plain
+$ faas-cli up -f show-html.yml
+```
+
+打开你的浏览器,首先访问。
+
+http://127.0.0.1:8080/function/show-html?action=new
+
+你应该看到 `这里有一个新的页面!`就像你在上一节看到的那样。现在访问。
+
+http://127.0.0.1:8080/function/show-html?action=list
+
+你应该看到一个显示列表的 HTML。
+
+## 与其他函数协作
+
+最后,让我们看看如何利用 JavaScript 和 Ajax 的优势,从 HTML 函数中与另一个函数(例如*figlet*函数)协作。
+
+首先,让我们再创建一个名为`figlet.html`的 HTML 文件。所以现在的结构应该是这样的。
+
+```plain
+├── show-html
+│ ├── __init__.py
+│ ├── handler.py
+│ ├── html
+│ │ ├── figlet.html
+│ │ ├── list.html
+│ │ └── new.html
+│ └── requirements.txt
+└── show-html.yml
+```
+
+编辑`figlet.html`。
+
+```html
+
+
+
+
+ OpenFaaS
+
+
+
+
+ Figlet
+
+ Text:
+
+
+
+
+
+
+
+
+```
+
+如果你不太了解 JavaScript,请不要担心。这个页面所做的就是。
+
+* 在 `input`中输入文字
+* 按下 `生成`按钮
+* 创建一个 Ajax 请求到*figlet*函数端点(`/function/figlet`)。
+* 将结果应用到 `textarea` 中。
+
+没有必要改变`handler.py`,因为它可以动态地提供上一节中的 HTML。尽管没有改变`handler.py`,我们还是需要构建和推送函数镜像,因为我们需要在函数容器中打包新的`figlet.html`。
+
+现在构建、推送和部署这个函数。
+
+```plain
+$ faas-cli up -f show-html.yml
+```
+
+本节假设你已经部署了[实验室 2](./lab2.md)中的*figlet*函数。
+
+打开你的浏览器,首先访问。
+
+http://127.0.0.1:8080/function/show-html?action=figlet
+
+你应该看到 `Figlet` 页面,并且应该看到一个输入。输入任何你想输入的文本,然后点击 `生成` 按钮。如果请求成功,`textarea` 应该包含你在 `input` 中输入的 figlet。这是一个微不足道的例子,但通过使用这种技术,你甚至可以用函数创建强大的 SPA(单页应用程序)。
+
+在这个实验室中,你学到了如何从你的函数中提供 HTML,并设置响应的`Content-Type`。此外,你还学会了如何用 HTML+JavaScript 调用其他函数,以及用函数创建动态页面。
+
+现在进入[实验室 7](lab7.md)
diff --git a/translations/cn/lab7.md b/translations/cn/lab7.md
new file mode 100644
index 0000000..887afa4
--- /dev/null
+++ b/translations/cn/lab7.md
@@ -0,0 +1,122 @@
+# 实验 7--异步函数
+
+
+
+在开始这个实验之前,为你的文件创建一个新的文件夹。
+
+```plain
+$ mkdir -p lab7 \
+ && cd lab7
+```
+
+## 同步与异步地调用一个函数
+
+当你同步调用一个函数时,一个连接会通过网关连接到你的函数,并且在整个执行过程中保持开放。同步调用是*阻塞的,所以你应该看到你的客户端暂停,变得不活跃,直到该函数完成其任务。
+
+* 网关使用的路由是。`/function/`。
+* 你必须等待,直到它完成
+* 你在调用后得到结果
+* 你知道它是通过还是失败
+
+异步任务以类似的方式运行,但有一些区别。
+
+* 网关使用不同的路由:`/async-function/`。
+* 客户端从网关得到一个立即的*202 接受*的响应。
+* 该函数稍后使用一个队列工作器来调用
+* 默认情况下,结果被丢弃
+
+让我们试一试快速演示。
+
+```plain
+$ faas-cli new --lang dockerfile long-task --prefix=""
+```
+
+编辑`long-task/Dockerfile`并将 fprocess 改为`sleep 1`。
+
+现在构建、部署并同步调用你的函数 10 次,像这样。
+
+```plain
+echo -n "" | faas-cli invoke long-task
+echo -n "" | faas-cli invoke long-task
+echo -n "" | faas-cli invoke long-task
+echo -n "" | faas-cli invoke long-task
+echo -n "" | faas-cli invoke long-task
+echo -n "" | faas-cli invoke long-task
+echo -n "" | faas-cli invoke long-task
+echo -n "" | faas-cli invoke long-task
+echo -n "" | faas-cli invoke long-task
+echo -n "" | faas-cli invoke long-task
+```
+
+现在异步调用该函数 10 次。
+
+```plain
+echo -n "" | faas-cli invoke long-task --async
+echo -n "" | faas-cli invoke long-task --async
+echo -n "" | faas-cli invoke long-task --async
+echo -n "" | faas-cli invoke long-task --async
+echo -n "" | faas-cli invoke long-task --async
+echo -n "" | faas-cli invoke long-task --async
+echo -n "" | faas-cli invoke long-task --async
+echo -n "" | faas-cli invoke long-task --async
+echo -n "" | faas-cli invoke long-task --async
+echo -n "" | faas-cli invoke long-task --async
+echo -n "" | faas-cli invoke long-task --async
+```
+
+你观察到了什么?第一个例子应该花了 10 秒,而第二个例子会在一秒或更短的时间内返回到你的提示。这项工作仍然需要 10x1 秒来完成,但现在要放在队列中延迟执行。
+
+异步函数调用非常适用于那些可以推迟到以后执行的任务,或者你不需要客户端上的结果。
+
+> 一个很好的例子是在接收 GitHub 的 webhooks 时--可能有一个最大的处理时间,GitHub 会允许你的连接保持开放,一个异步调用接受工作并立即返回。
+
+## 查看队列工作者的日志
+
+OpenFaaS 的默认栈使用 NATS 流来排队和延迟执行。你可以用以下命令查看日志。
+
+```plain
+kubectl logs deployment/queue-worker -n openfaas
+```
+
+## 使用一个`X-Callback-Url'与 requirebin
+
+如果你需要一个异步调用的函数的结果,你有两个选择。
+
+* 改变它的代码,用它的结果通知一个端点或消息系统
+
+这个选项可能不是在所有情况下都适用,并且需要编写额外的代码。
+
+* 利用回调的内置行为
+
+内置的回调允许对一个函数的调用提供一个 URL,队列工作器将自动报告函数的成功或失败,以及结果。
+一些额外的请求头被发送到回调,完整的列表见[回调请求头](https://docs.openfaas.com/reference/async/#callback-request-headers)
+
+前往 requestbin 并创建一个新的 `bin` --这将是公共互联网上的一个 URL,可以接收你的函数的结果。
+
+> 为了这个实验室的目的,一定要取消勾选 `私有` 复选框,这将使你不需要登录。
+
+https://requestbin.com/
+
+现在复制 "Bin URL "并将其粘贴在下面。
+
+例如(`http://requestbin.com/r/1i7i1we1`)
+
+```plain
+$ echo -n "LaterIsBetter" | faas-cli invoke figlet --async --header "X-Callback-Url http://requestbin.com/r/1i7i1we1"
+```
+
+现在刷新 requestbin 站点上的页面,你将看到来自 `figlet` 的结果。
+
+
+```plain
+ _ _ ___ ____ _ _
+| | __ _| |_ ___ _ _|_ _|___| __ ) ___| |_| |_ ___ _ __
+| | / _` | __/ _ \ '__| |/ __| _ \ / _ \ __| __/ _ \ '__|
+| |__| (_| | || __/ | | |\__ \ |_) | __/ |_| || __/ |
+|_____\__,_|\__\___|_| |___|___/____/ \___|\__|\__\___|_|
+
+```
+
+> 建议:也可以使用另一个函数作为 `X-Callback-Url` --这对于在异步工作负载被处理时通过 Slack 或 Email 通知自己是非常好的。要用结果调用另一个函数,将`X-Callback-Url`设置为`http://gateway:8080/function/`。
+
+现在进入[实验室 8](lab8.md)
diff --git a/translations/cn/lab8.md b/translations/cn/lab8.md
new file mode 100644
index 0000000..7db56b2
--- /dev/null
+++ b/translations/cn/lab8.md
@@ -0,0 +1,89 @@
+# Lab 8 - 高级函数 - 超时
+
+
+
+在开始这个实验之前,为你的文件创建一个新的文件夹。
+
+```plain
+$ mkdir -p lab8 \
+ && cd lab8
+```
+
+## 用`read_timeout`扩展超时时间
+
+*timeout*对应于一个函数可以运行多长时间,直到被执行。它对防止分布式系统中的误操作很重要。
+
+有几个地方可以为你的函数配置超时,在每个地方都可以通过使用环境变量来完成。
+
+* 函数超时
+
+* `read_timeout` - 允许函数通过 HTTP 读取一个请求的时间
+* `write_timeout` - 允许函数在 HTTP 上写一个响应的时间
+* `exec_timeout` - 一个函数在被终止前可以运行的最大时间。
+
+API 网关的默认时间是 20 秒,所以我们来测试一下在一个函数上设置一个更短的超时时间。
+
+```plain
+$ faas-cli new --lang python3 sleep-for --prefix=""
+```
+
+编辑`handler.py`。
+
+```python
+import time
+import os
+
+def handle(req):
+ """handle a request to the function
+ Args:
+ req (str): request body
+ """
+
+ sleep_duration = int(os.getenv("sleep_duration", "10"))
+ preSleep = "Starting to sleep for %d" % sleep_duration
+ time.sleep(sleep_duration) # Sleep for a number of seconds
+ postSleep = "Finished the sleep"
+ return preSleep + "\n" + postSleep
+```
+
+现在编辑`sleep-for.yml`文件,添加这些环境变量。
+
+```yaml
+provider:
+ name: openfaas
+ gateway: http://127.0.0.1:8080
+
+functions:
+ sleep-for:
+ lang: python3
+ handler: ./sleep-for
+ image: /sleep-for:0.1
+ environment:
+ sleep_duration: 10
+ read_timeout: "5s"
+ write_timeout: "5s"
+ exec_timeout: "5s"
+```
+
+使用 CLI 来构建、推送、部署和调用该函数。
+
+```sh
+$ echo | faas-cli invoke sleep-for
+Server returned unexpected status code: 502 -
+```
+
+你应该看到它没有打印消息就终止了,因为`sleep_duration`比超时值高。
+
+现在把`sleep_duration`设置为一个较低的数字,如`2`,然后再次运行`faas-cli deploy`。在编辑函数的 YAML 文件时,你不需要重建这个函数。
+
+```sh
+$ echo | faas-cli invoke sleep-for
+Starting to sleep for 2
+Finished the sleep
+```
+
+* API 网关
+
+要为你的函数设置超出默认限制的扩展超时,请遵循以下教程。[扩展的超时](https://docs.openfaas.com/tutorials/expanded-timeouts/)
+
+现在转到[实验室 9](lab9.md)
\ No newline at end of file
diff --git a/translations/cn/lab9.md b/translations/cn/lab9.md
new file mode 100644
index 0000000..23334b5
--- /dev/null
+++ b/translations/cn/lab9.md
@@ -0,0 +1,165 @@
+# Lab 9 - 高级函数 - 自动缩放
+
+
+
+## 自动缩放函数的应用
+
+正如[文档](http://docs.openfaas.com/architecture/autoscaling/)中描述的那样,OpenFaaS 带有自动扩展函数。在这个实验室中,我们将看看自动扩展是如何运作的。
+
+### 前提条件
+
+* 在完成了[Lab 1](./lab1.md)中对 OpenFaaS 的设置后,你将拥有触发自动扩展所需的一切。
+
+* 多个工具可以用来创建足够的流量来触发自动扩展 - 在这个例子中,`curl'将被使用,因为它很容易在 Mac 和 Linux 上使用,并在 Windows 上与 Git Bash 打包。
+
+### 自动扩展的背景
+
+开箱即用的 OpenFaaS 是这样配置的,它将根据 Prometheus 测量的 `每秒请求`指标进行自动扩展。 这个指标是在流量通过 API 网关的时候捕获的。如果超过了定义的 `每秒请求`的阈值,AlertManager 就会启动。这个阈值应该被重新配置为适合生产使用的水平,因为在这个例子中,为了演示,它被设置为一个低值。
+
+> 在[文档网站](http://docs.openfaas.com/architecture/autoscaling/)中找到更多关于自动缩放的信息。
+
+每次警报被 AlertManager 触发时,API 网关将把你的函数的一定数量的副本添加到集群中。OpenFaaS 有两个配置选项,允许指定副本的起始/最低数量,也允许停止副本的最大数量。
+
+你可以通过设置`com.openfaas.scale.min`来控制函数的最小副本量,目前默认值为`1`。
+
+你可以通过设置`com.openfaas.scale.max`来控制一个函数可以产生的最大副本量,目前默认值是`20`。
+
+> 注意: 如果你把`com.openfaas.scale.min`和`com.openfaas.scale.max`设置成相同的值,你就会禁用自动缩放函数。
+
+### 查看 Prometheus
+
+你需要运行这个端口转发命令,以便能够在`http://127.0.0.1:9090`访问 Prometheus。
+```plain
+$ kubectl port-forward deployment/prometheus 9090:9090 -n openfaas
+```
+
+现在添加一个所有成功调用部署的函数的图。我们可以通过执行`rate( gateway_function_invocation_total{code="200"} [20s])`作为查询来实现。导致一个看起来像这样的页面。
+
+ ![](../../screenshot/prometheus_graph.png)
+
+ 继续打开一个新的标签,在其中使用`http://127.0.0.1:9090/alerts`导航到警报部分。在这个页面上,你以后可以看到什么时候超过了 `每秒请求` 的阈值。
+
+ ![](../../screenshot/prometheus_alerts.png)
+
+### 触发缩放的 Go 函数
+
+首先是 Alex Ellis 的 `echo-fn`函数。
+
+```bash
+$ git clone https://github.com/alexellis/echo-fn \
+ && cd echo-fn \
+ && faas-cli template store pull golang-http \
+ && faas-cli deploy \
+ --label com.openfaas.scale.max=10 \
+ --label com.openfaas.scale.min=1
+```
+
+现在检查用户界面,看什么时候 `go-echo`函数从 `不准备`变成 `准备`。你也可以用`faas-cli describe go-echo`来检查。
+
+使用这个脚本反复调用 `go-echo` 函数,直到你看到副本数从 1 变成 5,以此类推。你可以在 Prometheus 中通过添加`gateway_service_count'的图表或在选择该函数的情况下查看 API 网关来监控这个值。
+
+```bash
+$ for i in {0..10000};
+do
+ echo -n "Post $i" | faas-cli invoke go-echo && echo;
+done;
+```
+
+> 注意:如果你在 Kubernetes 上运行,使用`$OPENFAAS_URL`而不是`http://127.0.0.1:8080`。
+
+### 监控警报
+
+现在你应该可以看到,在之前创建的图表中,`go-echo`函数的调用量有所增加。移动到你打开警报页面的标签。一段时间后,你应该开始看到 `APIHighInvocationRate`的状态(和颜色)变为 "待定",然后再次变为 "发射"。你也可以使用`$ faas-cli list`或通过[ui](http://127.0.0.1:8080)看到自动缩放的情况。
+
+ ![](../../screenshot/prometheus_firing.png)
+
+现在你可以使用`$ docker service ps go-echo`来验证`go-echo`的新副本是否已经启动。
+
+现在停止 bash 脚本,你会看到副本的数量在几秒钟后回到 1 个副本。
+
+### 疑难解答
+
+如果你认为你的自动扩展没有被触发,那么请检查以下内容。
+
+* 普罗米修斯中的警报页面 - 这应该是红色/粉色的,并显示 `FIRING` - 即在http://127.0.0.1:9090/alerts。
+* 检查核心服务的日志,即网关、Prometheus / AlertManager。
+
+为了获得核心服务的日志,运行`docker service ls`,然后`docker service logs `。
+
+### 负载测试(可选)
+
+需要注意的是,在受控环境中应用科学方法和工具与在你自己的笔记本电脑上运行拒绝服务攻击是有区别的。你的笔记本电脑不适合做负载测试,因为一般来说,你是在 Windows 或 Mac 主机上的 Linux 虚拟机中运行 OpenFaaS,而这也是一个单节点。这并不代表生产部署。
+
+请看[构建一个合适的性能测试](https://docs.openfaas.com/architecture/performance/)的文档。
+
+如果`curl`没有为你的测试产生足够的流量,或者你想获得一些关于事情如何分解的统计数据,那么你可以试试`hey`工具。`hey`可以通过每秒的请求或给定的持续时间产生结构化的负载。
+
+这里有一个在 1GHz 的 2016 年 12 英寸 MacBook 上运行的例子,带有 Docker Desktop。这是一台非常低功率的计算机,正如所描述的,不代表生产性能。
+
+```bash
+$ hey -z=30s -q 5 -c 2 -m POST -d=Test http://127.0.0.1:8080/function/go-echo
+Summary:
+ Total: 30.0203 secs
+ Slowest: 0.0967 secs
+ Fastest: 0.0057 secs
+ Average: 0.0135 secs
+ Requests/sec: 9.9932
+
+ Total data: 1200 bytes
+ Size/request: 4 bytes
+
+Response time histogram:
+ 0.006 [1] |
+ 0.015 [244] |■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
+ 0.024 [38] |■■■■■■
+ 0.033 [10] |■■
+ 0.042 [4] |■
+ 0.051 [1] |
+ 0.060 [0] |
+ 0.069 [0] |
+ 0.078 [0] |
+ 0.088 [0] |
+ 0.097 [2] |
+
+
+Latency distribution:
+ 10% in 0.0089 secs
+ 25% in 0.0101 secs
+ 50% in 0.0118 secs
+ 75% in 0.0139 secs
+ 90% in 0.0173 secs
+ 95% in 0.0265 secs
+ 99% in 0.0428 secs
+
+Details (average, fastest, slowest):
+ DNS+dialup: 0.0000 secs, 0.0057 secs, 0.0967 secs
+ DNS-lookup: 0.0000 secs, 0.0000 secs, 0.0000 secs
+ req write: 0.0001 secs, 0.0000 secs, 0.0016 secs
+ resp wait: 0.0131 secs, 0.0056 secs, 0.0936 secs
+ resp read: 0.0001 secs, 0.0000 secs, 0.0013 secs
+
+Status code distribution:
+ [200] 300 responses
+```
+
+以上模拟了两个活跃的用户`-c`,每秒 5 个请求`-q`,持续时间`-z`为 30 秒。
+
+要使用`hey`,你必须在本地计算机上安装 Golang。
+
+也请参见。[hey on GitHub](https://github.com/rakyll/hey)
+
+### 尝试从零开始扩展
+
+如果你把你的函数规模缩小到 0 个副本,你仍然可以调用它。该调用将触发网关将函数缩放到一个非零值。
+
+用下面的命令试试吧。
+
+```plain
+$ kubectl scale deployment --replicas=0 nodeinfo -n openfaas-fn
+```
+
+打开 OpenFaaS 用户界面,检查 nodeinfo 是否有 0 个副本,或者通过`kubectl get deployment nodeinfo -n openfaas-fn'。
+
+现在调用该函数并检查它是否扩展到 1 个副本。
+
+现在转到[Lab 10](lab10.md)。