无需改动一行代码,为应用接入云存储
原文由 Michael Lynch 于 发布,订阅该博客
最近,我在一台服务器上安装了一款媒体分享应用。安装很简单,但它给长期维护埋下了一个棘手的陷阱。
每当用户上传文件时,这个 Web 应用都会把它保存到本地文件系统。如果哪天我重装服务器,就得手动备份和恢复所有文件。更合理的架构是让应用把文件写到独立的存储服务器上,但我不想为此花上几个月去重写应用。
借助 Docker、Google Cloud Storage 和 gcsfuse 工具,我在没有改动应用一行代码的情况下实现了这种分离。
在本教程中,我将展示如何在尽量少改动源码的前提下,改造老应用以适配 Google Cloud Storage。
术语说明
如果你对 Google Cloud Platform 不太熟悉,下面是本教程中会用到的几个缩写:
| 缩写 | 全称 | 是什么 | 类似于 |
|---|---|---|---|
| GCS | Google Cloud Storage | Google 的云存储服务 | Amazon S3 |
| GCE | Google Compute Engine | Google 的按需虚拟机服务 | Amazon EC2 |
| GCR | Google Container Registry | Google 的 Docker 镜像托管服务 | Docker Hub |
| GCP | Google Cloud Platform | Google 的云计算平台(GCS、GCE 和 GCR 都是 GCP 的组成部分) | Amazon Web Services 或 Microsoft Azure |
Docker 能让开发者为应用构建自包含的运行环境,并且在任何地方都能运行:
- Docker 镜像是运行应用所需全部文件的集合,包括操作系统和所有第三方依赖。
- Docker 容器是 Docker 镜像运行时的实例环境。
在本教程中,你可以把 Docker 容器理解为轻量级虚拟机,虽然从技术上说它们并不是虚拟机。
准备工作
要跟着本文的示例操作,你需要准备以下工具:
- Google Cloud SDK
- Docker(免费的社区版即可)
示例应用
我为本教程创建了一个示例项目。这是一个极其简单的 Web 应用,基于 Flask 框架的上传文档实现。
运行方式如下:
git clone https://github.com/mtlynch/flask_upload_demo.git
cd flask_upload_demo
sudo pip install -r requirements.txt
gunicorn \
demo.app:app \
--bind 0.0.0.0:5000这个应用可以让你选择文件并上传:

flask-upload-demo 的上传页面
随后,它会通过 URL http://[服务器地址]:5000/uploads/[文件名] 永久提供该文件的访问:

flask-upload-demo 展示已上传的图片
在服务器内部可以看到,应用已将文件保存到了本地文件系统的 demo/uploads 目录下:
$ ls -l demo/uploads/
-rw-rw-r-- 1 mike mike 230720 Nov 24 21:45 Space_Duck_Desktop_RGB_PNG.png将示例应用容器化
由于这个应用只有几个简单的依赖,为它创建 Docker 镜像非常容易:
Dockerfile
FROM debian:stretch
RUN apt-get update
RUN apt-get install --yes \
git-core \
python \
python-pip \
python-virtualenv
# Create demo user system account.
ARG APP_USER="demo-user"
ARG APP_GROUP="demo-user"
ARG APP_HOME_DIR="/home/demo-user"
RUN set -x && \
groupadd "$APP_GROUP" && \
useradd \
--comment "Demo app system account" \
--home-dir "$APP_HOME_DIR" \
--create-home \
--system \
--gid "$APP_GROUP" \
"$APP_USER"
# Create directory for app source code.
ARG APP_ROOT="/srv/demo-app"
RUN mkdir --parents "$APP_ROOT" && \
chown \
--no-dereference \
--recursive \
"${APP_USER}:${APP_GROUP}" "$APP_ROOT"
USER "$APP_USER"
WORKDIR "$APP_ROOT"
# Install demo app.
ARG DEMO_APP_REPO="https://github.com/mtlynch/flask_upload_demo"
RUN set -x && \
git clone "$DEMO_APP_REPO" . && \
virtualenv VIRTUAL && \
. VIRTUAL/bin/activate && \
pip install --requirement requirements.txt
EXPOSE 5000
# Run demo app.
ENV FLASK_APP "demo/app.py"
CMD virtualenv VIRTUAL && \
. VIRTUAL/bin/activate && \
gunicorn \
demo.app:app \
--bind 0.0.0.0:5000 \
--log-level info上面的 Dockerfile 主要完成了几项任务来准备镜像:
- 安装 Git、Python 及相关软件包
- 创建一个用于以受限权限运行应用的系统账号(
demo-user) - 在本地克隆应用源码仓库
- 添加
CMD指令,在 5000 端口启动示例应用
你可以通过克隆我的仓库并在本地构建 Docker 容器来测试这个 Dockerfile:
cd ~
git clone https://github.com/mtlynch/docker-flask-upload-demo.git
cd docker-flask-upload-demo
docker build \
--tag demo-app-image \
.
docker run \
--detach \
--publish 80:5000 \
--name demo-app \
demo-app-image在浏览器中访问 http://localhost/,就能看到示例应用。
更贴近实际的 Docker 镜像
大多数 Web 应用不会直接接收来自浏览器的流量,而是使用 Nginx 或 Apache 这类 HTTP 服务器来处理通用任务(如负载均衡、静态文件服务),再由后端处理应用本身的逻辑。
我在 Docker 仓库中添加了 nginx 分支,以展示一个更接近真实应用场景的 Docker 镜像。相关改动如下:
# Install nginx.
ARG NGINX_GROUP="www-data"
COPY nginx.conf /etc/nginx/sites-enabled/nginx.conf
RUN set -x && \
apt-get install --yes \
nginx \
sudo && \
rm /etc/nginx/sites-enabled/default && \
usermod --append --groups "$NGINX_GROUP" "$APP_USER" && \
echo "$APP_USER ALL=(ALL:ALL) NOPASSWD: /usr/sbin/nginx" >> /etc/sudoersNginx 通常以 root 身份运行,以便监听 80 和 443 等特权 HTTP 端口。因此,Dockerfile 通过 sudo 允许示例应用的用户以 root 身份启动 Nginx,而其他操作仍保持受限权限。
最后,它将 Nginx 配置文件复制到容器中:
nginx.conf
server {
listen 80;
server_name example.org; # Replace with your server's domain name
client_max_body_size 20m;
# Serve static resources directly (bypass backend).
location /uploads/ {
alias /srv/demo-app/demo/uploads/;
}
# Forward all other requests to the application backend.
location / {
proxy_pass http://127.0.0.1:5000;
}
}location /uploads { ... } 配置块让 Nginx 直接从 /uploads 目录提供文件,而无需将请求转发给 flask-upload-demo 后端。这是常见的做法,因为 Nginx 处理静态文件的速度和效率都比应用后端更高。
你可以用以下命令运行 flask-upload-demo 的 Nginx 版本:
cd ~/docker-flask-upload-demo
git checkout nginx
docker build \
--tag demo-app-image \
.
docker run \
--detach \
--publish 80:80 \
--name demo-app \
demo-app-image应用同样会出现在 http://localhost/。行为与之前版本完全一致,只是在提供上传文件时占用的资源更少。
准备 GCP 项目
在本地部署应用很有意思,但发布到云端、让所有用户都能访问则更令人兴奋。要部署到 GCP,你需要先做几步配置。
首先,在 gcloud 中指定你的 GCP 项目名称:
PROJECT_ID="ENTER-YOUR-PROJECT-ID-HERE"
gcloud config set project "$PROJECT_ID"接着,通过 GCP 网页控制台创建一个具有 Owner 角色的服务账号:
避坑提醒:由于 GCP 一个疑似的 Bug,如果你使用根账号(例如你的 @gmail.com 账号)或通过 gcloud 创建的服务账号,向 gcr.io 推送 Docker 镜像(见下一节)会失败。

在 GCP 网页控制台中创建新的服务账号

为服务账号分配角色
将私钥下载为 key.json:

下载服务账号的私钥
最后,使用 gcloud 以刚刚创建的服务账号进行认证:
gcloud auth activate-service-account --key-file key.json完成认证后,你需要运行几条命令,为本教程的后续步骤准备好项目:
# Enable APIs you'll need for this workflow.
gcloud services enable \
cloudresourcemanager.googleapis.com \
compute.googleapis.com \
containerregistry.googleapis.com \
iam.googleapis.com
# Enable gcloud to provide credentials for Docker image pushes.
gcloud auth configure-docker --quiet将镜像上传到 Google Container Registry
在将 Docker 镜像部署到 GCE 之前,你需要先把它发布到 Docker 镜像托管服务。GCR 是 Google Cloud 集成的 Docker 镜像托管服务,因此是最简便的选择。
要将 Docker 镜像上传到 GCR,先检出我示例仓库的 nginx 分支:
cd ~/docker-flask-upload-demo
git checkout nginx然后,在本地构建 Docker 镜像,并推送到 GCR:
LOCAL_IMAGE_NAME="flask-upload-demo-image"
docker build --tag "$LOCAL_IMAGE_NAME" .
GCR_HOSTNAME="gcr.io"
GCR_IMAGE_PATH="${GCR_HOSTNAME}/${PROJECT_ID}/flask-demo-app"
docker tag "$LOCAL_IMAGE_NAME" "$GCR_IMAGE_PATH"
docker push "$GCR_IMAGE_PATH"部署 Docker 容器
部署容器需要一点间接操作。GCP 不允许直接部署 Docker 镜像,而是需要通过 GCE 启动一台完整的虚拟机(VM),在该虚拟机上运行 Docker,再由 GCE 在其中的容器里运行你的 Docker 镜像。好在 GCP 的工具让这个过程变得简单。
GCE 虚拟机默认禁止入站 HTTP 流量。要允许访问,需要创建一条防火墙规则,放行 80 端口(明文 HTTP 流量的标准端口)的 TCP 连接。
一个简便的方法是将这条规则应用到所有带有 http-server 标签的虚拟机:
VM_TAGS="http-server"
gcloud compute \
--project="$PROJECT_ID" \
firewall-rules create default-allow-http \
--direction=INGRESS \
--action=ALLOW \
--rules=tcp:80 \
--target-tags="$VM_TAGS"然后,使用 http-server 标签部署你的 GCE 虚拟机:
VM_NAME="flask-demo-app-vm"
MACHINE_TYPE="n1-standard-1"
ZONE=us-east1-b
gcloud compute \
--project="$PROJECT_ID" \
instances create-with-container "$VM_NAME" \
--zone="$ZONE" \
--machine-type="$MACHINE_TYPE" \
--network-tier=STANDARD \
--metadata=google-logging-enabled=true \
--maintenance-policy=MIGRATE \
--scopes=https://www.googleapis.com/auth/cloud-platform \
--tags="$VM_TAGS" \
--image-family=cos-stable \
--image-project=cos-cloud \
--boot-disk-size=10GB \
--boot-disk-type=pd-standard \
--boot-disk-device-name="$VM_NAME" \
--container-image="$GCR_IMAGE_PATH" \
--container-restart-policy=on-failure下面是几个关键参数:
--tags="$VM_TAGS" \--tags 标志会使用你为防火墙规则创建的标签来启动虚拟机,确保它能在 80 端口接收 HTTP 流量。
--image-family=cos-stable \
--image-project=cos-cloud \这些标志告诉 GCE 在 Container-Optimized OS 下运行容器,这是 Google 为运行 Docker 容器而打造的精简版 Linux 系统。--image-family=cos-stable 表示让 gcloud 使用 Container-Optimized OS 的最新稳定版。
--container-image="$GCR_IMAGE_PATH" \上面的标志告诉 GCE 要在 GCE 虚拟机中运行哪个 Docker 镜像,使用的是你之前创建的 GCR 地址。
命令执行完成后,你会看到类似如下的输出:
Created [https://www.googleapis.com/compute/v1/projects/flask-upload-demo-2018-11-26/zones/us-east1-b/instances/flask-demo-app-vm].
NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS
flask-demo-app-vm us-east1-b n1-standard-1 10.142.0.2 35.211.106.214 RUNNING在浏览器中输入 EXTERNAL_IP 列中的地址,效果和本地版本的应用完全一样:

在 GCE 上运行的 flask-upload-demo

flask-upload-demo 在 GCE 上提供图片文件
问题在于,如果你销毁这台虚拟机,再用同一个 Docker 镜像启动一台新的,之前上传的文件就不见了:

新的 GCE 虚拟机无法提供该文件,因为文件保存在了上一台虚拟机上
当然,这正是催生本教程的问题。容器将文件保存在其内部文件系统上,一旦终止宿主机虚拟机,所有文件都会丢失。
要解决这个问题,你需要配置 Docker 容器,将所有持久化数据都存储到 Google Cloud Storage(GCS)存储桶中。
规划支持 GCS 的架构
下面是解决该问题的目标架构:

在 Google Cloud Platform 上部署 Flask 应用的架构
- Web 浏览器只与 Web 服务器 Nginx 通信,Nginx 作为所有前端请求的调度器。
- 如果浏览器请求文件,Nginx 会通过名为 gcsfuse 的工具从 GCS 获取文件,该工具能将 GCS 存储桶挂载为文件系统中的文件夹。
- 对于其他所有请求,Nginx 会将其转发给 flask-upload-demo 应用。
- flask-upload-demo 同样通过 gcsfuse 工具将新文件写入 GCS。
这一架构满足了我在文章开头提出的目标。虚拟机中的一切都是可丢弃的,因为所有持久状态都由 GCS 保存。所有应用代码都在 Docker 容器中,这让部署变得简单而原子化。
创建 GCS 存储桶(可选)
如果你还没有 GCS 存储桶,可以用以下 gcloud 命令创建一个:
STORAGE_LOCATION="us-east1"
GCS_BUCKET="${PROJECT_ID}-storage"
gsutil mb \
-p "$PROJECT_ID" \
-l "$STORAGE_LOCATION" \
"gs://${GCS_BUCKET}"否则,请将 GCS_BUCKET 环境变量设置为已有 GCS 存储桶的名称。
让 Docker 容器访问 GCS
你需要修改 Docker 镜像以集成 gcsfuse 工具。好在我已经帮你做好了。完整的 Dockerfile 可在我的 GitHub 仓库的 gcsfuse 分支中找到,下面是主要的改动:
# Install gcsfuse.
ARG GCSFUSE_REPO="gcsfuse-stretch"
ARG GCS_MOUNT_ROOT="/mnt/gcsfuse"
RUN set -x && \
apt-get install --yes --no-install-recommends \
ca-certificates \
curl && \
echo "deb http://packages.cloud.google.com/apt $GCSFUSE_REPO main" \
| tee /etc/apt/sources.list.d/gcsfuse.list && \
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg \
| apt-key add -
RUN apt-get update
RUN set -x && \
apt-get install --yes gcsfuse && \
echo 'user_allow_other' > /etc/fuse.conf && \
mkdir --parents "$GCS_MOUNT_ROOT" && \
chown \
--no-dereference \
"${APP_USER}:${NGINX_GROUP}" "$GCS_MOUNT_ROOT"这里有两处值得说明:
echo 'user_allow_other' > /etc/fuse.conf上面这行让 gcsfuse 的 -o allow_other 选项得以生效。这是必需的,因为应用的系统账号和 Nginx 的系统账号都需要访问 GCS 目录。如果配置文件中没有 user_allow_other 这一行,就只有单个账号能访问 GCS 目录。
避坑提醒:如果有多个系统账号需要访问 GCS 存储桶,/etc/fuse.conf 文件中必须包含 user_allow_other 这一行。
mkdir --parents "$GCS_MOUNT_ROOT" && \
chown \
--no-dereference \
"${APP_USER}:${NGINX_GROUP}" "$GCS_MOUNT_ROOT"gcsfuse 要求挂载点是一个已存在且启动用户拥有写权限的目录。普通用户对 /mnt 目录没有写权限,因此 Dockerfile 以 root 身份创建了 /mnt/gcsfuse 目录,并通过 chown 将其所有权赋予 Nginx 和示例应用的系统账号。
另一处有意思的改动在 Dockerfile 的 CMD 部分,它定义了容器的运行时行为:
ENV GCS_BUCKET "REPLACE-WITH-YOUR-GCS-BUCKET-NAME"
ENV GCS_MOUNT_ROOT "$GCS_MOUNT_ROOT"
ENV APP_UPLOADS_DIR "${APP_ROOT}/demo/uploads"
CMD set -x && \
sudo nginx && \
gcsfuse \
-o nonempty \
-o allow_other \
--implicit-dirs \
"$GCS_BUCKET" "$GCS_MOUNT_ROOT" && \
if [ ! -d "$APP_UPLOADS_DIR" ]; then \
ln --symbolic "$GCS_MOUNT_ROOT" "$APP_UPLOADS_DIR"; \
fi && \
virtualenv VIRTUAL && \
. VIRTUAL/bin/activate && \
gunicorn \
demo.app:app \
--bind 127.0.0.1:5000 \
--log-level info同样,我们来逐段解析:
gcsfuse \
-o nonempty \
-o allow_other \
--implicit-dirs \
"$GCS_BUCKET" "$GCS_MOUNT_ROOT"与上面的 user_allow_other 选项类似,-o allow_other 让多个用户可以访问挂载的文件夹,因为 nginx(以 www-data 用户运行)和示例应用(以 demo-user 运行)都需要访问。
避坑提醒:如果多个用户账号需要访问 GCS 挂载点,gcsfuse 需要加上 -o allow_other 标志。
如果没有 --implicit-dirs 标志,gcsfuse 将无法访问 GCS 存储桶子文件夹中的文件。
避坑提醒:如果 GCS 存储桶中包含子文件夹,gcsfuse 需要加上 --implicit-dirs 标志。
if [ ! -d "$APP_UPLOADS_DIR" ]; then \
ln --symbolic "$GCS_MOUNT_ROOT" "$APP_UPLOADS_DIR"; \
fi应用会将上传的文件写入 demo/uploads 目录。上面的代码块创建了一个从 demo/uploads 到 /mnt/gcsfuse 的符号链接。这样,当应用以为自己在向 demo/uploads 路径写入时,实际上是在写入你的 GCS 存储桶。if/then 块则防止容器重启时重复执行该操作。
创建拥有 GCS 访问权限的服务账号
在将这个镜像部署到 GCE 之前,还有一个额外步骤。默认情况下,GCE 实例在标准 GCE 服务账号的上下文中运行。该账号对 GCS 只有只读权限,因此应用无法向 GCS 写入新文件。
为了解决这个问题,需要创建一个拥有以下两个角色的自定义服务账号:
storage.objectAdmin:允许虚拟机中的进程对 GCS 进行读写。logging.logWriter:允许应用的日志输出显示在 GCP 的日志界面中。
避坑提醒:除非使用自定义服务账号启动,否则 GCE 实例无法向 GCS 存储桶写入数据。
以下命令将创建一个具有所需权限的服务账号:
SERVICE_ACCOUNT_NAME=flask-demo-app-service-account
SERVICE_ACCOUNT_EMAIL="${SERVICE_ACCOUNT_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"
gcloud iam service-accounts create "$SERVICE_ACCOUNT_NAME"
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member "serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
--role roles/storage.objectAdmin
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member "serviceAccount:${SERVICE_ACCOUNT_EMAIL}" \
--role roles/logging.logWriter部署支持 GCS 的容器
回到你克隆的 docker-flask-upload-demo 仓库,检出 gcsfuse 分支:
cd ~/docker-flask-upload-demo
git checkout gcsfuse现在,重新构建 Docker 镜像并推送到 GCR:
LOCAL_IMAGE_NAME="flask-upload-demo-image"
docker build --tag "$LOCAL_IMAGE_NAME" .
GCR_HOSTNAME="gcr.io"
GCR_IMAGE_PATH="${GCR_HOSTNAME}/${PROJECT_ID}/flask-demo-app"
docker tag "$LOCAL_IMAGE_NAME" "$GCR_IMAGE_PATH"
docker push "$GCR_IMAGE_PATH"注意:如果你尝试在本地运行该镜像,它会失败。gcsfuse 版本的 Dockerfile 假定其运行环境已经通过 gcloud 完成了认证(在 GCE 上运行的容器确实如此)。要让 Dockerfile 在 GCE 之外也能挂载 GCS 存储桶是可行的,但这留给读者自行练习。
有了新的自定义 GCE 服务账号,你就可以部署支持 GCS 的容器了:
VM_NAME="flask-demo-app-vm-gcsfuse"
MACHINE_TYPE="n1-standard-1"
ZONE=us-east1-b
gcloud compute \
--project="$PROJECT_ID" \
instances create-with-container "$VM_NAME" \
--zone="$ZONE" \
--machine-type="$MACHINE_TYPE" \
--network-tier=STANDARD \
--metadata=google-logging-enabled=true \
--maintenance-policy=MIGRATE \
--scopes=https://www.googleapis.com/auth/cloud-platform \
--service-account="$SERVICE_ACCOUNT_EMAIL" \
--tags="$VM_TAGS" \
--image-family=cos-stable \
--image-project=cos-cloud \
--boot-disk-size=10GB \
--boot-disk-type=pd-standard \
--boot-disk-device-name="$VM_NAME" \
--container-image="$GCR_IMAGE_PATH" \
--container-restart-policy=on-failure \
--container-privileged \
--container-env="GCS_BUCKET=$GCS_BUCKET"这条命令与之前的部署命令相同,只是多了两个标志:
--container-privileged \--container-privileged 标志对于让 Docker 容器挂载 FUSE 文件系统是必需的(Docker 提供了更细粒度的实现方式,但 GCE 尚不支持)。
避坑提醒:除非使用 --container-privileged 标志部署虚拟机,否则 gcsfuse 无法在 GCE 上挂载 GCS 存储桶。
--container-env="GCS_BUCKET=$GCS_BUCKET"我有意将 Docker 镜像设计为在运行时之前不依赖具体的 GCS 存储桶名称。--container-env 标志让你可以在部署时指定 GCS_BUCKET 环境变量。
持久化的回报,正是持久化
你终于得到了一个能将状态持久化到 GCS 的 Docker 容器。你可以通过向已部署的应用上传文件来测试:

flask-upload-demo 从 GCS 上的永久存储提供文件
如果你查看 GCS 存储桶,就会看到刚才上传的文件:

GCS 存储桶中的图片文件
真正的考验是这种状态能否在不同的虚拟机之间保持。你可以通过彻底销毁虚拟机并重新部署来验证。之前虚拟机上的图片 URL 在新服务器上依然可以访问:

即使虚拟机已被销毁并重建,flask-upload-demo 仍能继续提供该文件
番外:日志界面
这个方案还有一个不错的附加好处:GCP 提供了一个精美的网页界面来查看应用日志。
你随时可以通过 SSH 登录虚拟机,然后运行 docker logs 手动查看日志:
$ docker logs klt-flask-demo-app-vm-gcsfuse-qnnf
...
[2018-11-26 21:28:54 +0000] [33] [INFO] Starting gunicorn 19.9.0
[2018-11-26 21:28:54 +0000] [33] [INFO] Listening at: http://127.0.0.1:5000 (33)
[2018-11-26 21:28:54 +0000] [33] [INFO] Using worker: sync
[2018-11-26 21:28:54 +0000] [37] [INFO] Booting worker with pid: 37
[2018-11-26 21:32:59 +0000] [37] [INFO] Saving uploaded file "zestful-logo.png" to "/srv/demo-app/demo/uploads/zestful-logo.png"更简便的方法是打开 GCP 的 Stackdriver 日志界面。在那里,你会在一个功能丰富的网页界面中找到所有日志:

GCP 的 Stackdriver 界面显示了应用的日志输出。
发布新版本
这种架构让发布新版本变得非常容易。每当你想更新应用或其依赖时,只需构建新镜像并推送到 GCR:
docker build --tag "$LOCAL_IMAGE_NAME" .
docker tag "$LOCAL_IMAGE_NAME" "$GCR_IMAGE_PATH"
docker push "$GCR_IMAGE_PATH"然后,使用 update-container 命令来更新正在运行的 GCE 实例上的 Docker 镜像:
gcloud compute \
--project="$PROJECT_ID" \
instances update-container "$VM_NAME" \
--container-image="$GCR_IMAGE_PATH"避坑提醒:除非你分配了静态 IP,否则执行此命令后虚拟机的外部 IP 地址将会改变。
如果你推送了一个有问题的版本,update-container 命令还能让你回滚到之前可用的旧镜像。
局限性
该方案与 gcsfuse 工具及 GCS 本身具有相同的局限性。gcsfuse 竭力让 GCS 存储桶看起来像普通的文件系统文件夹,但在两方面这种抽象会失效:
- GCS 不支持文件锁,因此如果应用尝试获取文件锁,就会出现异常。
- 延迟较高,尤其是在对大文件进行小的随机读写时。
特别是,我发现如果将 sqlite 指向位于 gcsfuse 挂载点上的数据库,它很快就会失败。
总结
在本教程中,你学会了如何在不改动应用本身的情况下,将应用的数据重定向到云存储。示例应用完全不需要感知 Google Cloud Platform,你却能将它部署到 Google Compute Engine,并将其持久化数据重定向到 Google Cloud Storage。
源代码
- flask-upload-demo:在本地文件系统上保存状态的 Flask 示例应用。
- docker-flask-upload-demo:flask-upload-demo 的 Docker 配置,共有三种变体:
- master 分支 - 展示应用的基本打包方式
- nginx 分支 - 展示更贴近真实场景的架构,其中由 nginx 为应用代理流量
- gcsfuse 分支 - 展示如何在 Docker 容器内挂载 Google Cloud Storage 存储桶(假定容器运行在具有 Google Cloud Storage 读写权限的 Google Compute Engine 虚拟机中)。
- mediagoblin-docker(gcsfuse 分支) - 一个真实媒体分享应用的 Docker 配置,我在其中使用了同样的技术将应用的持久化数据重定向到 Google Cloud Storage。
软件架构图由 Loraine Yow 绘制
随机一篇博客


评论
登录后参与讨论