Acceder a los metadatos de instancias

Google Cloud Platform proporciona un servidor de metadatos que conoce los detalles de tu instancia de App Engine, como el ID del proyecto que la contiene, las cuentas de servicio y los tokens que usan las cuentas de servicio. Puedes acceder a estos datos mediante solicitudes HTTP sencillas, sin necesidad de usar bibliotecas de cliente.

En esta página se muestra cómo acceder a los metadatos de la instancia desde tu aplicación de tiempo de ejecución de Java 8 desplegada haciendo llamadas HTTP a los endpoints del servidor de metadatos correspondientes.

Una forma útil de usar esta API es obtener el token de la cuenta de servicio y proporcionarlo como token de portador en el encabezado Authorization de una de las APIs de Google Cloud para autenticar tu aplicación en ese servicio de API concreto. Consulta la documentación de la API Translation de Google Cloud para ver un ejemplo de cómo se usan estos tokens de portador.

Identificar qué endpoint de metadatos se debe usar

En la siguiente tabla se enumeran los endpoints en los que puedes enviar solicitudes HTTP para obtener metadatos específicos. Se puede acceder al servidor de metadatos en http://metadata.google.internal.

Punto final de metadatos Descripción
/computeMetadata/v1/project/numeric-project-id El número de proyecto asignado a tu proyecto.
/computeMetadata/v1/project/project-id El ID del proyecto asignado a tu proyecto.
/computeMetadata/v1/instance/zone La zona en la que se está ejecutando la instancia.
/computeMetadata/v1/instance/service-accounts/default/aliases
/computeMetadata/v1/instance/service-accounts/default/email La dirección de correo de la cuenta de servicio predeterminada asignada a tu proyecto.
/computeMetadata/v1/instance/service-accounts/default/ Muestra todas las cuentas de servicio predeterminadas de tu proyecto.
/computeMetadata/v1/instance/service-accounts/default/scopes Enumera todos los ámbitos admitidos de las cuentas de servicio predeterminadas.
/computeMetadata/v1/instance/service-accounts/default/token Devuelve el token de autenticación que se puede usar para autenticar tu aplicación en otras APIs de Google Cloud.

Por ejemplo, para obtener tu ID de proyecto, envía una solicitud a http://metadata.google.internal/computeMetadata/v1/project/project-id.

Crear solicitudes de metadatos

El siguiente código de ejemplo obtiene todos los metadatos disponibles de la instancia y los muestra, excepto el token de la cuenta de servicio.

@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());

  }
}

En el código de muestra, fíjate en la comprobación para asegurarte de que la aplicación se está ejecutando en producción. Si la aplicación se ejecuta de forma local, las solicitudes no devolverán metadatos.

También se usa el serializador y deserializador Google Gson JSON, el cliente HTTP y HTTP2 OkHttp y el sistema de plantillas Thymeleaf. No son obligatorias, pero son bibliotecas útiles para tus propios proyectos.

Ejecutar localmente

El servidor de metadatos está disponible para las aplicaciones implementadas. No se admite la ejecución local en el servidor de desarrollo. Puedes añadir una comprobación del entorno al código para que solo se esperen resultados de metadatos si la aplicación se está ejecutando en producción, tal como se muestra en el código de ejemplo proporcionado anteriormente:

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
   }