Google Kubernetes Engine 到云端 SQL

Google Kubernetes Engine to Cloud SQL

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

目前我可以执行以下操作。

我想做的事情如下。

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

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

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"]

如您所见,Docker文件的第二部分被注释掉了。这样做是因为它使 GKE 上的 pods 崩溃,因为它缺少需要从 secrets 挂载的凭证文件。

除了Docker文件外,还有一个名为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:Connecting Cloud SQL

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

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

在我的头撞到 table 两天后,我没有接近,这就是我写这个 Whosebug 查询的原因,希望有人对 Docker、GKE 更有经验和 Cloud SQL,可以给我一些指示。

如果我可能遗漏了一些重要的东西,请随意询问更多细节。

[编辑 1]

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

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"]

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

我设法通过在我的项目中创建自己的 class 来让它工作,给定代理文件的路径将 运行 它带有所需的参数,我也将其提供给通过我的 class。为了将代理文件与我自己的代码一起获取,我使用了以下 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' class 启动了代理。

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; }

        ...
    }

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

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