인스턴스 메타데이터 액세스

Google Cloud Platform은 App Engine 인스턴스 세부정보(예: 포함하는 프로젝트의 ID, 서비스 계정, 서비스 계정에 사용되는 토큰)를 알고 있는 메타데이터 서버를 제공합니다. 간단한 HTTP 요청을 사용하여 이 데이터에 액세스할 수 있습니다. 이때 클라이언트 라이브러리는 필요하지 않습니다.

이 페이지에서는 적절한 메타데이터 서버 엔드포인트에 대한 HTTP 호출을 수행하여 배포된 자바 8 런타임 애플리케이션에서 인스턴스 메타데이터에 액세스하는 방법을 보여줍니다.

서비스 계정 토큰을 가져와서 이를 Google Cloud API 중 하나의 승인 헤더에 Bearer 토큰으로 제공하여 해당 API 서비스에 대해 애플리케이션을 인증하는 것도 이 API를 유용하게 사용하는 한 방법입니다. 이러한 Bearer 토큰을 사용하는 방법을 보여주는 예는 Google Cloud Translation API를 참조하세요.

사용할 메타데이터 엔드포인트 식별

다음 표에는 특정 메타데이터에 대해 HTTP 요청을 수행할 수 있는 엔드포인트가 나와 있습니다. 메타데이터 서버는 http://metadata.google.internal에서 액세스할 수 있습니다.

메타데이터 엔드포인트 설명
/computeMetadata/v1/project/numeric-project-id 프로젝트에 할당된 프로젝트 번호입니다.
/computeMetadata/v1/project/project-id 프로젝트에 할당된 프로젝트 ID입니다.
/computeMetadata/v1/instance/zone 인스턴스가 실행 중인 영역입니다.
/computeMetadata/v1/instance/service-accounts/default/aliases
/computeMetadata/v1/instance/service-accounts/default/email 프로젝트에 할당된 기본 서비스 계정 이메일입니다.
/computeMetadata/v1/instance/service-accounts/default/ 프로젝트의 모든 기본 서비스 계정이 나열됩니다.
/computeMetadata/v1/instance/service-accounts/default/scopes 기본 서비스 계정에 지원되는 모든 범위가 나열됩니다.
/computeMetadata/v1/instance/service-accounts/default/token 다른 Google Cloud API에 애플리케이션을 인증하기 위해 사용할 수 있는 인증 토큰을 반환합니다.

예를 들어 프로젝트 ID를 검색하려면 http://metadata.google.internal/computeMetadata/v1/project/project-id에 요청을 보냅니다.

메타데이터 요청

다음 샘플 코드에서는 서비스 계정 토큰을 제외하고 인스턴스에서 사용 가능한 모든 메타데이터를 가져와서 표시합니다.

@SuppressWarnings("serial")
// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required.
@WebServlet(name = "Metadata", description = "Metadata: Write info about GAE Standard",
    urlPatterns = "/metadata")
public class MetadataServlet extends HttpServlet {

  private final String[] metaPath = {
      "/computeMetadata/v1/project/numeric-project-id", //  (pending)
      "/computeMetadata/v1/project/project-id",
      "/computeMetadata/v1/instance/zone",
      "/computeMetadata/v1/instance/service-accounts/default/aliases",
      "/computeMetadata/v1/instance/service-accounts/default/email",
      "/computeMetadata/v1/instance/service-accounts/default/",
      "/computeMetadata/v1/instance/service-accounts/default/scopes",
      // Tokens work - but are a security risk to display
      //      "/computeMetadata/v1/instance/service-accounts/default/token"
  };

  final String[] metaServiceAcct = {
      "/computeMetadata/v1/instance/service-accounts/{account}/aliases",
      "/computeMetadata/v1/instance/service-accounts/{account}/email",
      "/computeMetadata/v1/instance/service-accounts/{account}/scopes",
      // Tokens work - but are a security risk to display
      //     "/computeMetadata/v1/instance/service-accounts/{account}/token"
  };

  private final String metadata = "http://metadata.google.internal";
  private TemplateEngine templateEngine;

  // Use OkHttp from Square as it's quite easy to use for simple fetches.
  private final OkHttpClient ok = new OkHttpClient.Builder()
      .readTimeout(500, TimeUnit.MILLISECONDS)  // Don't dawdle
      .writeTimeout(500, TimeUnit.MILLISECONDS)
      .build();

  // Setup to pretty print returned json
  private final Gson gson = new GsonBuilder()
      .setPrettyPrinting()
      .create();
  private final JsonParser jp = new JsonParser();

  // Fetch Metadata
  String fetchMetadata(String key) throws IOException {
    Request request = new Request.Builder()
        .url(metadata + key)
        .addHeader("Metadata-Flavor", "Google")
        .get()
        .build();

    Response response = ok.newCall(request).execute();
    return response.body().string();
  }

  String fetchJsonMetadata(String prefix) throws IOException {
    Request request = new Request.Builder()
        .url(metadata + prefix)
        .addHeader("Metadata-Flavor", "Google")
        .get()
        .build();

    Response response = ok.newCall(request).execute();

    // Convert json to prety json
    return gson.toJson(jp.parse(response.body().string()));
  }

  @Override
  public void init() {
    // Setup ThymeLeaf
    ServletContextTemplateResolver templateResolver =
        new ServletContextTemplateResolver(this.getServletContext());

    templateResolver.setPrefix("/WEB-INF/templates/");
    templateResolver.setSuffix(".html");
    templateResolver.setCacheTTLMs(Long.valueOf(1200000L)); // TTL=20m

    // Cache is set to true by default. Set to false if you want templates to
    // be automatically updated when modified.
    templateResolver.setCacheable(true);

    templateEngine = new TemplateEngine();
    templateEngine.setTemplateResolver(templateResolver);
  }

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    String defaultServiceAccount = "";
    WebContext ctx = new WebContext(req, resp, getServletContext(), req.getLocale());

    resp.setContentType("text/html");

    String environment =
        (String) System.getProperties().get("com.google.appengine.runtime.environment");
    ctx.setVariable("production", environment);

    // The metadata server is only on a production system
    if (environment.equals("Production")) {

      TreeMap<String, String> m = new TreeMap<>();

      for (String key : metaPath) {
        m.put(key, fetchMetadata(key));
        if (key.contains("default/email")) {
          defaultServiceAccount = m.get(key);
        }
      }

      ctx.setVariable("Metadata", m.descendingMap());

      m = new TreeMap<>();
      for (String key : metaServiceAcct) {
        // substitute a service account for {account}
        key = key.replace("{account}", defaultServiceAccount);
        m.put(key, fetchMetadata(key));
      }
      ctx.setVariable("sam", m.descendingMap());

      // Recursivly get all info about service accounts -- Note tokens are leftout by default.
      ctx.setVariable("rsa",
          fetchJsonMetadata("/computeMetadata/v1/instance/service-accounts/?recursive=true"));
      // Recursivly get all data on Metadata server.
      ctx.setVariable("ram", fetchJsonMetadata("/?recursive=true"));
    }

    templateEngine.process("index", ctx, resp.getWriter());

  }
}

샘플 코드에서 앱이 프로덕션 환경에서 실행 중인지 보는 검사를 확인합니다. 앱이 로컬에서 실행 중인 경우 요청에서 메타데이터가 반환되지 않습니다.

Google Gson JSON 직렬 변환기/역직렬 변환기, OkHttp HTTP 및 HTTP2 클라이언트, Thymeleaf 템플릿 시스템이 사용된 것도 확인하세요. 필수는 아니지만 프로젝트에 유용한 라이브러리입니다.

로컬에서 실행

배포된 애플리케이션에서 메타데이터 서버를 사용할 수 있습니다. 개발 서버에서 로컬로 실행하는 것은 지원되지 않습니다. 위에 제공된 샘플 코드와 같이 앱이 프로덕션 환경에서 실행 중인 경우에만 메타데이터 결과를 반환하도록 코드에 환경 검사를 추가할 수 있습니다.

String environment =
      (String) System.getProperties().get("com.google.appengine.runtime.environment");
  ctx.setVariable("production", environment);

  // The metadata server is only on a production system
  if (environment.equals("Production")) {
     ... //show metadata results
   }