首页 > 解决方案 > Google Kubernetes Engine 到 Cloud SQL

问题描述

我目前正在尝试学习使用 Google Kubernetes Engine 和 Google Cloud SQL。为此,我在我的 Visual Studio 2019 中创建了一个 AspnetCore 3.1 Web Api 项目,用作培训项目。

目前,我目前能够执行以下操作。

我想要做的如下。

根据我对 Cloud SQL 的理解,最好始终通过代理访问它,因为它更安全,这就是我想要 sidecar 的原因。然而,为了让代理工作,我需要保存在 GKE 中我的秘密中的凭据文件。我还有一些与数据库相关的变量需要作为环境变量传入,同样来自 GKE 中的秘密。

目前在我的解决方案中,在我的 api 项目文件旁边,我有一个 Dockerfile,如下所示。

FROM gcr.io/google-appengine/aspnetcore:3.1
COPY . /app
WORKDIR /app
ENTRYPOINT ["dotnet", "HelloCloud.Api.dll"]

#FROM gcr.io/cloudsql-docker/gce-proxy
#COPY . /app
#WORKDIR /app
#CMD ["/cloud_sql_proxy -instances=noble-cubist-294511:europe-west2:helloclouddb=tcp:1433 -credential_file=/app/secrets/cloudsql/key.json"]

如您所见,Dockerfile 的第二部分被注释掉了。这样做是因为它会导致 GKE 上的 Pod 崩溃,因为它缺少需要从 Secret 挂载的凭证文件。

在 Dockerfile 之外,还有一个名为 deployment.yaml 的文件,内容如下。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hellocloud
spec:
  selector:
    matchLabels:
      app: hellocloud
  template:
    metadata:
      labels:
        app: hellocloud
    spec:
      containers:
      - name: hellocloud
        image: gcr.io/noble-cubist-294511/hello-cloud-api
        env:
            - name: DB_USER
              valueFrom:
                secretKeyRef:
                  name: helloclouddb-db-credentials
                  key: username
            - name: DB_PASS
              valueFrom:
                secretKeyRef:
                  name: helloclouddb-db-credentials
                  key: password
            - name: DB_NAME
              valueFrom:
                secretKeyRef:
                  name: helloclouddb-db-credentials
                  key: database
        resources:
          limits:
            memory: "128Mi"
            cpu: "500m"
        ports:
        - containerPort: 80

      - name: cloudsql-proxy
        image: gcr.io/cloudsql-docker/gce-proxy
        command: ["/cloud_sql_proxy",
                    "-instances=noble-cubist-294511:europe-west2:helloclouddb=tcp:1433",
                    "-credential_file=/secrets/cloudsql/key.json"]
        resources:
            limits:
              memory: "128Mi"
              cpu: "500m"
        volumeMounts:
            - name: credentials-volumn
              mountPath: /secrets/cloudsql
              readOnly: true

    volumes:
        - name: credentials-volumn
          secret:
            secretName: helloclouddb-instance-credentials

我按照本网站上的指南创建了上述deployment.yaml:连接云SQL

通过研究这个,我发现Google Cloud Tools for Visual Studio会对 Dockerfile 做出反应,这也是我尝试注释掉的部分的原因。我一直在试图弄清楚我是否可以通过 Dockerfile 指示 GKE 使用 deployment.yaml 文件,因为据我了解这应该可以解决问题。

我喜欢 DRY(不要重复自己)的开发概念,这是它希望能够通过Google Cloud Tools for Visual Studio做到这一点的另一个原因。我曾尝试直接在 GKE 上创建部署,这花了我大约 10 分钟的时间,结果甚至没有工作。当然,如果我更习惯于在 GKE 上创建部署,它会减少时间,最终也可以工作,但这将是一种 WET(每次写入)方式。

在把我的头撞在桌子上两天之后,我没有靠近,这就是我写这个 Stackoverflow 任务的原因,希望对 Docker、GKE 和 Cloud SQL 更有经验的人可以给我一些指示。

如果我可能错过了一些重要的事情,请随意询问更多细节。

[编辑 1]

作为一种解决方法,我试图将文件放在我的驱动器上,至少从我的理解来看,Dockerfile 中的副本将从那里获取它。下面是我在 Visual Studio 中的项目图像,后面是我更新的 Dockerfile。

Web API 内容

FROM gcr.io/google-appengine/aspnetcore:3.1
COPY . /app
WORKDIR /app
ENTRYPOINT ["dotnet", "HelloCloud.Api.dll"]

FROM gcr.io/cloudsql-docker/gce-proxy
COPY . /app
WORKDIR /app/Secrets/CloudSQL
CMD ["/cloud_sql_proxy -instances=noble-cubist-294511:europe-west2:helloclouddb=tcp:1433 -credential_file=key.json"]

在我的计算机上构建 Dockerfile 并使用Dive命令查看图像的内容,它在指定位置包含“key.json”。即使这样,当部署到 GKE 时,Cloud Build 构建它也很好,但是当启动一个 pod 时,它会抛出一个 RunContainerError,抱怨“没有这样的文件或目录”。完整错误的图像如下所示。

Pod RunContainerError

标签: dockerasp.net-coregoogle-kubernetes-enginegoogle-cloud-sql

解决方案


我设法通过在我的项目中创建自己的类来让它工作,给定代理文件的路径将使用所需的参数运行它,我也通过我的类提供给它。为了将代理文件与我自己的代码一起获取,我使用了以下 Dockerfile 来构建我的图像。

FROM gcr.io/cloudsql-docker/gce-proxy as proxy
COPY . /app

FROM gcr.io/google-appengine/aspnetcore:3.1
Copy --from=proxy . /app
WORKDIR /app/app
ENTRYPOINT ["dotnet", "HelloCloud.Api.dll"]

给定代理文件的路径以及凭据和数据库文件,我的“CloudSQLInitializer”类将启动代理。

public Startup(IConfiguration configuration)
        {
            ...
            try
            {
                CloudSQLInitializer cloud = null;

                // Currently running the proxy from either a bat or windows service.
                // As such will not have it start the proxy
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                    cloud = new CloudSQLInitializer($"Secrets/CloudSQL/database.json");
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
                    cloud = new CloudSQLInitializer($"../cloud_sql_proxy", $"Secrets/CloudSQL/database.json", $"Secrets/CloudSQL/key.json");

                ...
            }
            catch (Exception e)
            {
                ...
            }
        }

public class CloudSQLInitializer
    {
        public string ConnectionString { get; private set; }

        ...
        public CloudSQLInitializer(string databaseInformationFilePath)
        {
            ...

            var information =
                JsonConvert.DeserializeObject<CloudDatabase>(File.ReadAllText(databaseInformationFilePath));

            ConstructConnectionString(information);
        }

        ...
        public CloudSQLInitializer(string cloudSQLProxyFilePath, string databaseInformationFilePath, string cloudCredentialsFilePath)
        {
            ...

            var information =
                JsonConvert.DeserializeObject<CloudDatabase>(File.ReadAllText(databaseInformationFilePath));

            ...

            ConstructConnectionString(information);
            RunCloudSQLProxy(information, cloudSQLProxyFilePath, cloudCredentialsFilePath);
        }

        private void ConstructConnectionString(CloudDatabase information)
        {
            var cs = new StringBuilder();

            cs.Append($"Data Source=127.0.0.1;");
            cs.Append($"Initial Catalog={information.Database};");
            cs.Append($"Persist Security Info=True;");
            cs.Append($"User ID={information.Username};");
            cs.Append($"Password={information.Password}");

            ConnectionString = cs.ToString();
        }

        private void RunCloudSQLProxy(CloudDatabase information, string cloudSQLProxyFilePath, string cloudCredentialsFilePath)
        {
            var cmd = new StringBuilder();

            cmd.Append($" -instances={information.InstancesToOneString()}");
            cmd.Append($" -credential_file={cloudCredentialsFilePath}");

            var proxy = new ProcessStartInfo {FileName = cloudSQLProxyFilePath, Arguments = cmd.ToString()};
            Process.Start(proxy);
        }
    }
public class CloudDatabase
    {
        [JsonProperty("database")]
        public string Database { get; set; }

        [JsonProperty("username")]
        public string Username { get; set; }

        [JsonProperty("password")]
        public string Password { get; set; }

        [JsonProperty("instances")]
        public List<string> Instances { get; set; }

        ...
    }

在上面的代码片段中,我删除了方法的摘要,以及检查以确保文件存在并同样检查的代码。还删除了许多与本案例无关的代码。

希望这在将来的某个时候对某人有用。


推荐阅读