访问实例元数据

Google Cloud Platform 提供一个元数据服务器,让您可以了解有关 App Engine 实例的详细信息,例如其包含项目 ID、服务账号和服务账号使用的令牌。您可以使用简单的 HTTP 请求访问此数据:不需要客户端库。

此页面显示如何通过对适当的元数据服务器端点执行 HTTP 调用,从已部署的 Java 8 运行时应用中访问实例元数据。

此 API 的一种用途就是获取服务账号令牌,并在某个 Google Cloud API 的 Authorization 标头中将其作为不记名令牌提供,以便在您的应用访问该特定 API 服务时对您的应用执行身份验证。 有关如何使用这些不记名令牌的示例,请参阅 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
   }