amazon-web-services - 使用 CloudFormation 禁用 AWS ECS Fargate 中的 Spring Boot Data MongoDB 可重试写入

标签 amazon-web-services spring-boot aws-cloudformation aws-fargate aws-documentdb

我正在使用 CloudFormation 从 Docker 镜像在 ECS Fargate 中部署 Spring Boot 应用程序。它使用 Spring Boot Data MongoDB 连接到 AWS DocumentDB。由于 CloudFormation 模板中的所有内容都是独立的(即,所有内容都是通过模板部署的,无需复制和粘贴或手动配置),因此我将数据库集群以及来自 Secrets Manager 的用户名+密码注入(inject)到任务容器定义中:

          Environment:
            - Name: SPRING_DATA_MONGODB_HOST
              Value: !GetAtt DbCluster.Endpoint
            - Name: SPRING_DATA_MONGODB_PORT
              Value: !GetAtt DbCluster.Port
          Secrets:
            - Name: SPRING_DATA_MONGODB_USERNAME
              ValueFrom: !Sub "${DbCredentials}:username::"
            - Name: SPRING_DATA_MONGODB_PASSWORD
              ValueFrom: !Sub "${DbCredentials}:password::"

我刚刚发现 DocumentDB doesn't support retryable writes 。因此,当我的 ECS 任务启动时,它会显示:

com.mongodb.MongoCommandException: Command failed with error 301: 'Retryable writes are not supported' on server my-db-cluster.cluster-xxxxxxxxxxxx.us-east-1.docdb.amazonaws.com:27017. The full response is {"ok": 0.0, "code": 301, "errmsg": "Retryable writes are not supported", "operationTime": {"$timestamp": {"t": xxxxxxxxxx, "i": 1}}}

AWS 文档和其他各种答案都说设置 retryWrites=False切换到完整SPRING_DATA_MONGODB_URIconnection URI format像这样:

mongodb://<username>:<password>@my-db-cluster.cluster-xxxxxxxxxxxx.us-east-1.docdb.amazonaws.com:27017/database?retryWrites=False

在 CloudFormation 中我的任务容器定义中会是这样的:

            - Name: SPRING_DATA_MONGODB_URI
              Value: !Sub "mongodb://${DbCluster.Endpoint}:${DbCluster.Port}/?retryWrites=False"

但定义连接 URI 似乎会覆盖用户名和密码环境变量。那么现在如何将 Secrets Manager 中的用户名+密码注入(inject)到 CloudFormation 中的连接 URI 中?

或者是否有其他方法可以禁用 Spring Boot Data MongoDB 中的可重试写入,但仍允许我从 Secrets Manager 注入(inject)凭据?

最佳答案

每当您指定单独的 Spring Boot Data MongoDB 环境变量(例如 SPRING_DATA_MONGODB_PASSWORD)(可通过配置属性(例如 spring.data.mongodb.password)在内部访问) ,Spring会动态构造连接URL。但是,如果您指定连接 URI 环境变量 SPRING_DATA_MONGODB_URI,Spring 将忽略各个组件,包括 ECS 从 Secrets Manager 注入(inject)的用户名和密码,如问题中所述。

但是有一种解决方法可以强制 Spring 提取您通过连接 URI 注入(inject)的 secret 凭据。 Spring Boot 提供了一种使用 property placeholders 动态插入配置属性的工具,类似于 CloudFormation 引用,只不过这些引用在运行时被替换。因此可以继续注入(inject)用户名和密码,然后在实际连接 URI 中使用占位符,如下所示:

          Environment:
            - Name: SPRING_DATA_MONGODB_HOST
              Value: !GetAtt DbCluster.Endpoint
            - Name: SPRING_DATA_MONGODB_PORT
              Value: !GetAtt DbCluster.Port
            - Name: SPRING_DATA_MONGODB_URI
              Value: mongodb://${spring.data.mongodb.username}:${spring.data.mongodb.passsword}@${spring.data.mongodb.host}:${spring.data.mongodb.port}/?retryWrites=False
          Secrets:
            - Name: SPRING_DATA_MONGODB_USERNAME
              ValueFrom: !Sub "${DbCredentials}:username::"
            - Name: SPRING_DATA_MONGODB_PASSWORD
              ValueFrom: !Sub "${DbCredentials}:password::"

根据 AWS DocumentDB 控制台提供的建议,您甚至可能希望使用此扩展连接 URI:

mongodb://${spring.data.mongodb.username}:${spring.data.mongodb.password}@${spring.data.mongodb.host}:${spring.data.mongodb .port}/?replicaSet=rs0&readPreference=secondaryPreferred&retryWrites=false

现在,用户名和密码会在部署时注入(inject)到环境变量中,Spring Boot 本身会在运行时将这些值插入到连接 URI 中。

有一个小警告需要提及,但否则无关紧要。如果密码包含纯 @: 字符,则会使 URI 的 username:password@... 部分不明确,因此MongoDB 要求对它们进行 URI 编码。此外,如果要包含 %,则该字符也需要进行编码,因为它是 URI 编码序列中的第一个字符。由于 Spring Boot 无法对配置属性引用进行动态 URI 编码,因此可以很容易地告诉 AWS Secrets Manager 在生成密码时排除这三个字符。 AWS::DocDB::DBCluster已经禁止使用 /"@ 字符,因此实际上您只需从可用字符中删除两个额外的字符。

因此您可能会生成 AWS::SecretsManager::Secret在 CloudFormation 中就像这样:

  DbCredentials:
    Type: AWS::SecretsManager::Secret
    Properties:
      Name: db-creds
      GenerateSecretString:
        SecretStringTemplate: '{"username": "somebody"}'
        GenerateStringKey: "password"
        PasswordLength: 99
        ExcludeCharacters: '/"@%:'

(提示:虽然 DocumentDB 声称支持 100 个字符的密码,但实际上如果密码长度超过 99 个字符,底层 RDS 仍然会报错。)

Spring Boot Data MongoDB documentation说:

Username and password credentials used in XML-based configuration must be URL-encoded when these contain reserved characters, such as :, %, @, or ,. The following example shows encoded credentials: m0ng0@dmin:mo_res:bw6},Qsdxx@admin@databasem0ng0%40dmin:mo_res%3Abw6%7D%2CQsdxx%40admin@database See section 2.2 of RFC 3986 for further details.

但是RFC 3986 § 3.2.1. User Information并没有如此限制,实际上可以包含:第 2.2 节中的 sub-delims

userinfo = *( unreserved / pct-encoded / sub-delims / ":" )

因此,如果我们从第 2.2 节中排除剩余的分隔符,即 gen-delims,就会得到以下结果,这可能是最正确和最安全的:

        # exclude `/"@` as per DocumentDB restrictions
        # exclude `%`, the URI encoding delimiter, so the password can be interpolated into a connection URI
        # exclude `:/?#[]@` `gen-delims` from [RFC 3986 § 2.2](https://www.rfc-editor.org/rfc/rfc3986.html#section-2.2)
        ExcludeCharacters: "/\"@%:?#[]"

关于amazon-web-services - 使用 CloudFormation 禁用 AWS ECS Fargate 中的 Spring Boot Data MongoDB 可重试写入,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75997367/

相关文章:

spring-boot - Spring Boot bootRun 持续构建

amazon-web-services - 如何从 CloudFormation AWS::Lambda::Alias 获取函数名称和别名?

amazon-web-services - 在亚马逊服务器中收到错误“[BucketAlreadyOwnedByYou]您先前创建命名存储桶的请求成功,并且您已经拥有它”

docker - 用于 Spring Boot 构建错误的 Maven docker 插件

java - 获取 java.net.UnknownHostException : hostname: Name or service not known while using spring-data-redis-starter

amazon-web-services - 如何根据 CloudFormation 上的条件更改属性?

amazon-web-services - AWS cloudformation可选线

ruby-on-rails - Rails secrets.yml VS Dotenv VS Figaro with Capistrano on AWS

PHP 5.5 找不到 MySQLi 构造函数

java - 有没有办法在 S3 中连续写入流(逐行)