Mengakses Metadata Instance

Google Cloud Platform menyediakan server metadata yang mengetahui detail instance App Engine Anda, seperti ID project yang berisi, akun layanan, dan token yang digunakan oleh akun layanan. Anda dapat mengakses data ini menggunakan permintaan HTTP sederhana: library klien tidak diperlukan.

Halaman ini menunjukkan cara mengakses metadata instance dari aplikasi runtime Java 8 yang di-deploy dengan melakukan panggilan HTTP ke endpoint server metadata yang sesuai.

Salah satu cara berguna untuk menggunakan API ini adalah dengan mendapatkan token akun layanan dan memberikannya sebagai token pemilik di header Otorisasi salah satu Google Cloud API, untuk mengautentikasi aplikasi Anda ke layanan API tertentu. Lihat dokumentasi Google Cloud Translation API untuk mengetahui contoh penggunaan token pemilik ini.

Mengidentifikasi endpoint metadata yang akan digunakan

Tabel berikut mencantumkan endpoint tempat Anda dapat membuat permintaan HTTP untuk metadata tertentu. Server metadata dapat diakses di http://metadata.google.internal.

Endpoint metadata Deskripsi
/computeMetadata/v1/project/numeric-project-id Nomor project yang ditetapkan ke project Anda.
/computeMetadata/v1/project/project-id ID project yang ditetapkan ke project Anda.
/computeMetadata/v1/instance/zone Zona tempat instance berjalan.
/computeMetadata/v1/instance/service-accounts/default/aliases
/computeMetadata/v1/instance/service-accounts/default/email Email akun layanan default yang ditetapkan ke project Anda.
/computeMetadata/v1/instance/service-accounts/default/ Mencantumkan semua akun layanan default untuk project Anda.
/computeMetadata/v1/instance/service-accounts/default/scopes Mencantumkan semua cakupan yang didukung untuk akun layanan default.
/computeMetadata/v1/instance/service-accounts/default/token Menampilkan token autentikasi yang dapat digunakan untuk mengautentikasi aplikasi Anda ke Google Cloud API lain.

Misalnya, untuk mengambil ID project Anda, kirim permintaan ke http://metadata.google.internal/computeMetadata/v1/project/project-id.

Membuat permintaan metadata

Kode contoh berikut mendapatkan semua metadata yang tersedia untuk instance dan menampilkannya, kecuali untuk token akun layanan.

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

  }
}

Dalam kode contoh, perhatikan pemeriksaan untuk memastikan aplikasi berjalan di produksi. Jika aplikasi berjalan secara lokal, tidak ada metadata yang akan ditampilkan dari permintaan.

Selain itu, perhatikan penggunaan serializer/deserializer Google Gson JSON, klien HTTP dan HTTP2 OkHttp, serta Thymeleaf pembuatan template. Hal ini tidak wajib, tetapi library ini berguna untuk project Anda sendiri.

Berjalan secara lokal

Server metadata tersedia untuk aplikasi yang di-deploy: berjalan secara lokal di server pengembangan tidak didukung. Anda dapat menambahkan pemeriksaan lingkungan ke kode Anda untuk mengharapkan hasil metadata hanya jika aplikasi berjalan dalam produksi, seperti yang ditunjukkan pada kode contoh yang diberikan di atas:

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
   }