Cloud Build에서 연결

이 페이지에는 Cloud Build에서 실행되는 서비스에서 Cloud SQL 인스턴스에 연결하는 방법에 대한 정보와 예시가 포함되어 있습니다.

Cloud SQL은 클라우드에서 관계형 데이터베이스를 설정, 유지, 관리할 수 있는 완전 관리형 데이터베이스 서비스입니다.

Cloud Build는 Google Cloud 인프라에서 빌드를 실행하는 서비스입니다.

Cloud SQL 인스턴스 설정

  1. 아직 사용 설정하지 않은 경우에는 연결 중인 Google Cloud 프로젝트에서 Cloud SQL Admin API를 사용 설정하세요.

    API 사용 설정

  2. SQL 서버용 Cloud SQL 인스턴스를 만듭니다. 지연 시간을 개선하고, 일부 네트워킹 비용을 방지하고, 리전 간 장애 위험을 줄이려면 Cloud Run 서비스와 동일한 리전에 있는 Cloud SQL 인스턴스 위치를 선택하는 것이 좋습니다.

    기본적으로 Cloud SQL은 공개 IP 주소를 새 인스턴스에 할당합니다. 비공개 IP 주소를 할당할 수도 있습니다. 두 옵션의 연결 옵션에 대한 자세한 내용은 연결 개요 페이지를 참조하세요.

Cloud Build 구성

Cloud Build를 구성하는 단계는 Cloud SQL 인스턴스에 할당된 IP 주소 유형에 따라 다릅니다.

공개 IP(기본값)

Cloud Build 서비스 계정Cloud SQL 인스턴스에 연결하는 데 필요한 IAM 역할과 권한이 있는지 확인합니다. Cloud Build 서비스 계정이 Google Cloud 콘솔 IAM 페이지에 주 구성원 [YOUR-PROJECT-NUMBER]@cloudbuild.gserviceaccount.com으로 나열됩니다.

Google Cloud 콘솔에서 이 서비스 계정을 보려면 Google 제공 역할 부여 포함 체크박스를 선택합니다.

Cloud Build 서비스 계정에 다음 IAM 역할 중 하나가 필요합니다.

  • Cloud SQL Client(권장)
  • Cloud SQL Admin
또는 다음 IAM 권한을 수동으로 할당할 수 있습니다.
  • cloudsql.instances.connect
  • cloudsql.instances.get

Cloud Build 서비스 계정이 Cloud SQL 인스턴스와 다른 프로젝트에 속하는 경우 두 프로젝트에 Cloud SQL Admin API와 IAM 권한을 추가해야 합니다.

비공개 IP

비공개 IP를 통해 Cloud SQL 인스턴스에 연결하려면 Cloud Build가 Cloud SQL 인스턴스와 동일한 VPC 네트워크에 있어야 합니다. 구성하려면 다음 안내를 따르세요.

  1. Cloud SQL 인스턴스의 VPC 네트워크와 서비스 프로듀서 네트워크 간에 비공개 연결을 설정합니다.
  2. Cloud Build 비공개 풀을 만듭니다.

구성된 후에는 빌드가 풀에서 실행될 때 애플리케이션이 인스턴스의 비공개 IP 주소 및 포트 1433을 사용하여 직접 연결할 수 있습니다.

Cloud SQL에 연결

Cloud Build를 구성한 후 Cloud SQL 인스턴스에 연결할 수 있습니다.

공개 IP(기본값)

공개 IP 경로의 경우 Cloud Build에서 TCP 소켓을 지원합니다.

Cloud Build 단계에서 Cloud SQL 인증 프록시를 사용하여 데이터베이스에 대한 연결을 허용할 수 있습니다. 이 구성은 다음을 수행합니다.

  1. 컨테이너를 빌드하여 Container Registry에 푸시합니다.
  2. Cloud SQL 인증 프록시 바이너리에 복사하는 두 번째 컨테이너를 빌드합니다.
    • Cloud Build에서 빌드한 컨테이너는 레지스트리로 푸시할 필요가 없으며 빌드 완료 시 삭제됩니다.
  3. 두 번째 컨테이너를 사용하여 Cloud SQL 인증 프록시를 시작하고 마이그레이션 명령어를 실행합니다.
steps:
  - id: "docker-build"
    name: "gcr.io/cloud-builders/docker"
    args: ["build", "-t", "${_IMAGE_NAME}", "sql-proxy/."]

  - id: "docker-push"
    name: "gcr.io/cloud-builders/docker"
    args: ["push", "${_IMAGE_NAME}"]

  - id: "docker-layer"
    name: "gcr.io/cloud-builders/docker"
    entrypoint: /bin/bash
    args:
      - '-c'
      - |
        echo "FROM $_IMAGE_NAME
        COPY --from=gcr.io/cloud-sql-connectors/cloud-sql-proxy /cloud-sql-proxy /cloudsql/cloud-sql-proxy" > Dockerfile-proxy;

        docker build -f Dockerfile-proxy -t ${_IMAGE_NAME}-proxy .

  # For TCP connections
  - id: "migrate-tcp"
    name: "${_IMAGE_NAME}-proxy"
    dir: sql-proxy
    env:
      - "DATABASE_NAME=${_DATABASE_NAME}"
      - "DATABASE_USER=${_DATABASE_USER}"
      - "DATABASE_HOST=127.0.0.1"
      - "DATABASE_PORT=${_DATABASE_PORT}"
      - "DATABASE_TYPE=${_DATABASE_TYPE}"
    secretEnv:
      - DATABASE_PASS
    entrypoint: /bin/bash
    args:
      - '-c'
      - |
        /cloudsql/cloud-sql-proxy --port ${_DATABASE_PORT} ${_INSTANCE_CONNECTION_NAME} & sleep 2;
        python migrate.py # for example

  # For Unix Socket connections
  - id: "migrate-socket"
    name: "${_IMAGE_NAME}-proxy"
    dir: sql-proxy
    env:
      - "DATABASE_NAME=${_DATABASE_NAME}"
      - "DATABASE_USER=${_DATABASE_USER}"
      - "INSTANCE_CONNECTION_NAME=${_INSTANCE_CONNECTION_NAME}"
      - "DATABASE_TYPE=${_DATABASE_TYPE}"
    secretEnv:
      - DATABASE_PASS
    entrypoint: /bin/bash
    args:
      - '-c'
      - |
        /cloudsql/cloud-sql-proxy --unix-socket /cloudsql ${_INSTANCE_CONNECTION_NAME} & sleep 2;
        if [ $_DATABASE_TYPE = 'mssql' ]; then echo "MSSQL doesn't support Unix Sockets. Skippng."; exit 0; fi;
        python migrate.py # for example.

options:
  dynamic_substitutions: true

substitutions:
  _DATABASE_USER: myuser
  _DATABASE_NAME: mydatabase
  _INSTANCE_CONNECTION_NAME: ${PROJECT_ID}:us-central1:myinstance
  _DATABASE_PORT: '5432'
  _DATABASE_TYPE: postgres
  _DATABASE_PASSWORD_KEY: database_password
  _IMAGE_NAME: gcr.io/${PROJECT_ID}/sample-sql-proxy

availableSecrets:
  secretManager:
    - versionName: projects/$PROJECT_ID/secrets/${_DATABASE_PASSWORD_KEY}/versions/latest
      env: DATABASE_PASS

위 Cloud Build 코드 샘플에서는 위 샘플 앱을 배포한 후에 Cloud SQL 인증 프록시와 Cloud Build를 사용하여 Cloud SQL 데이터베이스를 업데이트하도록 가상의 migrate 스크립트를 실행하는 방법을 보여줍니다. 이 Cloud Build 코드 샘플을 실행하려면 다음과 같은 설정 단계를 수행해야 합니다.

  1. 폴더 이름 sql-proxy을 만듭니다.
  2. 파일 콘텐츠에 대해 다음 코드 한 줄을 사용하여 sql-proxy 폴더에 Dockerfile 파일을 만듭니다.

    FROM gcr.io/gcp-runtimes/ubuntu_20_0_4

  3. sql-proxy 폴더에 cloudbuild.yaml 파일을 만듭니다.
  4. cloudbuild.yaml 파일을 업데이트합니다.
    1. 위 샘플 Cloud Build 코드를 복사하여 cloudbuild.yaml 파일에 붙여넣습니다.
    2. substitutions: 블록 내 예시 코드 _DATABASE_TYPEmssql으로 업데이트합니다.
    3. substitutions: 블록 내의 예시 코드 _DATABASE_PORT를 SQL Server에서 사용하는 포트인 1433로 업데이트합니다.
    4. 다음 자리표시자 값을 프로젝트에 사용된 값으로 바꿉니다.
      • mydatabase
      • myuser
      • myinstance
  5. Secret Manager에서 database_password라는 보안 비밀을 만듭니다.
  6. sql-proxy 폴더에 migrate.py 스크립트 파일을 만듭니다.
    • 이 스크립트는 다음 예시를 사용하여 cloudbuild.yaml 파일에서 만든 다음 환경 변수와 보안 비밀을 참조할 수 있습니다.
      • os.getenv('DATABASE_NAME')
      • os.getenv('DATABASE_USER')
      • os.getenv('DATABASE_PASS')
      • os.getenv('INSTANCE_CONNECTION_NAME')
    • Bash 스크립트에서 동일한 변수(예: migrate.sh)를 참조하려면 다음 예시를 사용합니다.
      • $DATABASE_NAME
      • $DATABASE_USER
      • $DATABASE_PASS
      • $INSTANCE_CONNECTION_NAME
  7. 다음 gcloud builds submit 명령어를 실행하여 Cloud SQL 인증 프록시가 있는 컨테이너를 빌드하고 Cloud SQL 인증 프록시를 시작한 후 migrate.py 스크립트를 실행합니다.

    gcloud builds submit --config cloudbuild.yaml

비공개 IP

비공개 IP 경로의 경우 애플리케이션은 비공개 풀을 통해 인스턴스에 직접 연결됩니다. 이 메서드는 TCP를 사용하여 Cloud SQL 인증 프록시를 사용하지 않고 Cloud SQL 인스턴스에 직접 연결합니다.

TCP로 연결

Cloud SQL 인스턴스의 비공개 IP 주소를 호스트 및 포트 1433로 사용하여 연결합니다.

Python

웹 애플리케이션의 컨텍스트에서 이 스니펫을 보려면 GitHub의 README를 참조하세요.

import os

import sqlalchemy

def connect_tcp_socket() -> sqlalchemy.engine.base.Engine:
    """Initializes a TCP connection pool for a Cloud SQL instance of SQL Server."""
    # Note: Saving credentials in environment variables is convenient, but not
    # secure - consider a more secure solution such as
    # Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
    # keep secrets safe.
    db_host = os.environ[
        "INSTANCE_HOST"
    ]  # e.g. '127.0.0.1' ('172.17.0.1' if deployed to GAE Flex)
    db_user = os.environ["DB_USER"]  # e.g. 'my-db-user'
    db_pass = os.environ["DB_PASS"]  # e.g. 'my-db-password'
    db_name = os.environ["DB_NAME"]  # e.g. 'my-database'
    db_port = os.environ["DB_PORT"]  # e.g. 1433

    pool = sqlalchemy.create_engine(
        # Equivalent URL:
        # mssql+pytds://<db_user>:<db_pass>@<db_host>:<db_port>/<db_name>
        sqlalchemy.engine.url.URL.create(
            drivername="mssql+pytds",
            username=db_user,
            password=db_pass,
            database=db_name,
            host=db_host,
            port=db_port,
        ),
        # ...
    )

    return pool

Java

웹 애플리케이션의 컨텍스트에서 이 스니펫을 보려면 GitHub의 README를 참조하세요.

참고:

  • CLOUD_SQL_CONNECTION_NAME은 <MY-PROJECT>:<INSTANCE-REGION>:<INSTANCE-NAME>으로 표현되어야 합니다.
  • 인수 ipTypes=PRIVATE을 사용하면 SocketFactory가 인스턴스의 연결된 비공개 IP와 강제로 연결됩니다.
  • pom.xml 파일에 대한 JDBC 소켓 팩토리 버전 요구사항은 여기를 참조하세요.


import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import javax.sql.DataSource;

public class TcpConnectionPoolFactory extends ConnectionPoolFactory {

  // Note: Saving credentials in environment variables is convenient, but not
  // secure - consider a more secure solution such as
  // Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
  // keep secrets safe.
  private static final String DB_USER = System.getenv("DB_USER");
  private static final String DB_PASS = System.getenv("DB_PASS");
  private static final String DB_NAME = System.getenv("DB_NAME");

  private static final String INSTANCE_HOST = System.getenv("INSTANCE_HOST");
  private static final String DB_PORT = System.getenv("DB_PORT");

  public static DataSource createConnectionPool() {
    // The configuration object specifies behaviors for the connection pool.
    HikariConfig config = new HikariConfig();

    // Configure which instance and what database user to connect with.
    config.setJdbcUrl(
        String.format("jdbc:sqlserver://%s:%s;databaseName=%s", INSTANCE_HOST, DB_PORT, DB_NAME));
    config.setUsername(DB_USER); // e.g. "root", "sqlserver"
    config.setPassword(DB_PASS); // e.g. "my-password"

    // ... Specify additional connection properties here.
    // ...

    // Initialize the connection pool using the configuration object.
    return new HikariDataSource(config);
  }
}

Node.js

웹 애플리케이션의 컨텍스트에서 이 스니펫을 보려면 GitHub의 README를 참조하세요.

const mssql = require('mssql');

// createTcpPool initializes a TCP connection pool for a Cloud SQL
// instance of SQL Server.
const createTcpPool = async config => {
  // Note: Saving credentials in environment variables is convenient, but not
  // secure - consider a more secure solution such as
  // Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
  // keep secrets safe.
  const dbConfig = {
    server: process.env.INSTANCE_HOST, // e.g. '127.0.0.1'
    port: parseInt(process.env.DB_PORT), // e.g. 1433
    user: process.env.DB_USER, // e.g. 'my-db-user'
    password: process.env.DB_PASS, // e.g. 'my-db-password'
    database: process.env.DB_NAME, // e.g. 'my-database'
    options: {
      trustServerCertificate: true,
    },
    // ... Specify additional properties here.
    ...config,
  };
  // Establish a connection to the database.
  return mssql.connect(dbConfig);
};

Go

웹 애플리케이션의 컨텍스트에서 이 스니펫을 보려면 GitHub의 README를 참조하세요.

package cloudsql

import (
	"database/sql"
	"fmt"
	"log"
	"os"
	"strings"

	_ "github.com/denisenkom/go-mssqldb"
)

// connectTCPSocket initializes a TCP connection pool for a Cloud SQL
// instance of SQL Server.
func connectTCPSocket() (*sql.DB, error) {
	mustGetenv := func(k string) string {
		v := os.Getenv(k)
		if v == "" {
			log.Fatalf("Fatal Error in connect_tcp.go: %s environment variable not set.\n", k)
		}
		return v
	}
	// Note: Saving credentials in environment variables is convenient, but not
	// secure - consider a more secure solution such as
	// Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
	// keep secrets safe.
	var (
		dbUser    = mustGetenv("DB_USER")       // e.g. 'my-db-user'
		dbPwd     = mustGetenv("DB_PASS")       // e.g. 'my-db-password'
		dbTCPHost = mustGetenv("INSTANCE_HOST") // e.g. '127.0.0.1' ('172.17.0.1' if deployed to GAE Flex)
		dbPort    = mustGetenv("DB_PORT")       // e.g. '1433'
		dbName    = mustGetenv("DB_NAME")       // e.g. 'my-database'
	)

	dbURI := fmt.Sprintf("server=%s;user id=%s;password=%s;port=%s;database=%s;",
		dbTCPHost, dbUser, dbPwd, dbPort, dbName)

	// dbPool is the pool of database connections.
	dbPool, err := sql.Open("sqlserver", dbURI)
	if err != nil {
		return nil, fmt.Errorf("sql.Open: %w", err)
	}

	// ...

	return dbPool, nil
}

C#

웹 애플리케이션의 컨텍스트에서 이 스니펫을 보려면 GitHub의 README를 참조하세요.

using Microsoft.Data.SqlClient;
using System;

namespace CloudSql
{
    public class SqlServerTcp
    {
        public static SqlConnectionStringBuilder NewSqlServerTCPConnectionString()
        {
            // Equivalent connection string:
            // "User Id=<DB_USER>;Password=<DB_PASS>;Server=<INSTANCE_HOST>;Database=<DB_NAME>;"
            var connectionString = new SqlConnectionStringBuilder()
            {
                // Note: Saving credentials in environment variables is convenient, but not
                // secure - consider a more secure solution such as
                // Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
                // keep secrets safe.
                DataSource = Environment.GetEnvironmentVariable("INSTANCE_HOST"), // e.g. '127.0.0.1'
                // Set Host to 'cloudsql' when deploying to App Engine Flexible environment
                UserID = Environment.GetEnvironmentVariable("DB_USER"),         // e.g. 'my-db-user'
                Password = Environment.GetEnvironmentVariable("DB_PASS"),       // e.g. 'my-db-password'
                InitialCatalog = Environment.GetEnvironmentVariable("DB_NAME"), // e.g. 'my-database'

                // The Cloud SQL proxy provides encryption between the proxy and instance
                Encrypt = false,
            };
            connectionString.Pooling = true;
            // Specify additional properties here.
            return connectionString;
        }
    }
}

Ruby

웹 애플리케이션의 컨텍스트에서 이 스니펫을 보려면 GitHub의 README를 참조하세요.

tcp: &tcp
  adapter: sqlserver
  # Configure additional properties here.
  # Note: Saving credentials in environment variables is convenient, but not
  # secure - consider a more secure solution such as
  # Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
  # keep secrets safe.
  username: <%= ENV["DB_USER"] %>  # e.g. "my-database-user"
  password: <%= ENV["DB_PASS"] %> # e.g. "my-database-password"
  database: <%= ENV.fetch("DB_NAME") { "vote_development" } %>
  host: <%= ENV.fetch("INSTANCE_HOST") { "127.0.0.1" }%> # '172.17.0.1' if deployed to GAE Flex
  port: <%= ENV.fetch("DB_PORT") { 1433 }%> 

PHP

웹 애플리케이션의 컨텍스트에서 이 스니펫을 보려면 GitHub의 README를 참조하세요.

namespace Google\Cloud\Samples\CloudSQL\SQLServer;

use PDO;
use PDOException;
use RuntimeException;
use TypeError;

class DatabaseTcp
{
    public static function initTcpDatabaseConnection(): PDO
    {
        try {
            // Note: Saving credentials in environment variables is convenient, but not
            // secure - consider a more secure solution such as
            // Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
            // keep secrets safe.
            $username = getenv('DB_USER'); // e.g. 'your_db_user'
            $password = getenv('DB_PASS'); // e.g. 'your_db_password'
            $dbName = getenv('DB_NAME'); // e.g. 'your_db_name'
            $instanceHost = getenv('INSTANCE_HOST'); // e.g. '127.0.0.1' ('172.17.0.1' for GAE Flex)

            // Connect using TCP
            $dsn = sprintf(
                'sqlsrv:server=%s;Database=%s',
                $instanceHost,
                $dbName
            );

            // Connect to the database
            $conn = new PDO(
                $dsn,
                $username,
                $password,
                # ...
            );
        } catch (TypeError $e) {
            throw new RuntimeException(
                sprintf(
                    'Invalid or missing configuration! Make sure you have set ' .
                        '$username, $password, $dbName, and $instanceHost (for TCP mode). ' .
                        'The PHP error was %s',
                    $e->getMessage()
                ),
                $e->getCode(),
                $e
            );
        } catch (PDOException $e) {
            throw new RuntimeException(
                sprintf(
                    'Could not connect to the Cloud SQL Database. Check that ' .
                        'your username and password are correct, that the Cloud SQL ' .
                        'proxy is running, and that the database exists and is ready ' .
                        'for use. For more assistance, refer to %s. The PDO error was %s',
                    'https://cloud.google.com/sql/docs/sqlserver/connect-external-app',
                    $e->getMessage()
                ),
                (int) $e->getCode(),
                $e
            );
        }

        return $conn;
    }
}

그런 다음 코드를 직접 실행하도록 Cloud Build 단계를 만들 수 있습니다.

steps:
  - id: "docker-build"
    name: "gcr.io/cloud-builders/docker"
    args: ["build", "-t", "${_IMAGE_NAME}", "sql-private-pool/."]

  - id: "docker-push"
    name: "gcr.io/cloud-builders/docker"
    args: ["push", "${_IMAGE_NAME}"]

  - id: "migration"
    name: "${_IMAGE_NAME}"
    dir: sql-private-pool
    env:
      - "DATABASE_NAME=mydatabase"
      - "DATABASE_USER=myuser"
      - "DATABASE_HOST=${_DATABASE_HOST}"
      - "DATABASE_TYPE=${_DATABASE_TYPE}"
    secretEnv:
      - DATABASE_PASS
    entrypoint: python   # for example
    args: ["migrate.py"] # for example

options:
  pool:
    name: projects/$PROJECT_ID/locations/us-central1/workerPools/private-pool
  dynamic_substitutions: true

substitutions:
  _DATABASE_PASSWORD_KEY: database_password
  _DATABASE_TYPE: postgres
  _IMAGE_NAME: gcr.io/${PROJECT_ID}/sample-private-pool

availableSecrets:
  secretManager:
    - versionName: projects/$PROJECT_ID/secrets/${_DATABASE_PASSWORD_KEY}/versions/latest
      env: DATABASE_PASS

위 Cloud Build 코드 샘플에서는 위 샘플 앱을 배포한 후에 Cloud Build를 사용하여 Cloud SQL 데이터베이스를 업데이트하도록 가상의 migrate 스크립트를 실행하는 방법을 보여줍니다. 이 Cloud Build 코드 샘플을 실행하려면 다음과 같은 설정 단계를 수행해야 합니다.

  1. 폴더 이름 sql-private-pool을 만듭니다.
  2. 파일 콘텐츠에 대해 다음 코드 한 줄을 사용하여 sql-private-pool 폴더에 Dockerfile 파일을 만듭니다.

    FROM gcr.io/gcp-runtimes/ubuntu_20_0_4

  3. sql-private-pool 폴더에 cloudbuild.yaml 파일을 만듭니다.
  4. cloudbuild.yaml 파일을 업데이트합니다.
    1. 위 샘플 Cloud Build 코드를 복사하여 cloudbuild.yaml 파일에 붙여넣습니다.
    2. 다음 자리표시자 값을 프로젝트에 사용된 값으로 바꿉니다.
      • mydatabase
      • myuser
      • host:port 양식의 databasehost.
  5. Secret Manager에서 database_password라는 보안 비밀을 만듭니다.
  6. sql-proxy 폴더에 migrate.py 스크립트 파일을 만듭니다.
    • 이 스크립트는 다음 예시를 사용하여 cloudbuild.yaml 파일에서 만든 다음 환경 변수와 보안 비밀을 참조할 수 있습니다.
      • os.getenv('DATABASE_NAME')
      • os.getenv('DATABASE_USER')
      • os.getenv('DATABASE_PASS')
      • os.getenv('DATABASE_HOST')
    • Bash 스크립트에서 동일한 변수(예: migrate.sh)를 참조하려면 다음 예시를 사용합니다.
      • $DATABASE_NAME
      • $DATABASE_USER
      • $DATABASE_PASS
      • $DATABASE_HOST
  7. 다음 gcloud builds submit 명령어를 실행하여 Cloud SQL 인증 프록시가 있는 컨테이너를 빌드하고 Cloud SQL 인증 프록시를 시작한 후 migrate.py 스크립트를 실행합니다.

    gcloud builds submit --config cloudbuild.yaml

권장사항 및 기타 정보

로컬에서 애플리케이션을 테스트할 때 Cloud SQL 인증 프록시를 사용할 수 있습니다. 자세한 내용은 빠른 시작: Cloud SQL 인증 프록시 사용을 참조하세요.

Docker 컨테이너를 통해 Cloud SQL 프록시를 사용하여 테스트할 수도 있습니다.

데이터베이스 스키마 마이그레이션

Cloud SQL에 연결하도록 Cloud Build를 구성하면 다른 서버리스 플랫폼에 배포하는 것과 동일한 코드를 사용하여 Cloud Build에서 데이터베이스 스키마 마이그레이션 태스크를 실행할 수 있습니다.

Secret Manager 사용

Secret Manager를 사용하여 빌드에 민감한 정보를 포함할 수 있습니다.