简体   繁体   中英

How can I get PCF App Manager to display information about my detected as Spring Boot Application?

I have a Spring Boot application that is being deployed to Pivotal Cloud Foundry (PCF). PCF recognizes the application as a Spring Boot Application. However, the none of the information displays.

The application is running with a context path. A Configuration has been added to remap:

  • /cloudfoundryapplication
  • /actuator/prometheus
  • /health

To within the context path from without. I can open /actuator/prometheus and /health in my browser.
/cloudfoundryapplication returns an HTTP 401 error page.

I've added

management:
  cloudfoundry:
    enabled: true
    skip-ssl-validation: true

to application.yml .
Spring security is running but is configured to ignore all of the below URLS.

@Override
public void configure(WebSecurity web) throws Exception
{
   web.ignoring()
      .antMatchers("/health",
                   "/actuator/health",
                   "/actuator/prometheus",
                   "/cloudfoundryapplication",
                   "/actuator/cloudfoundryapplication",
                   "/cloudfoundryapplication/**",
                   //"/report/daily",
                   "/index.html",
                   "/index.html**",
                   "/favicon.ico");
}

So How can I get PCF to actually display the Health for my application, and treat it like the Spring Boot app it recognizes it to be?


UPDATE: The application is already serving URLS outside of Its context path. I've already listed the URLS it was serving. Here is the class that does that. PCF Recognizes the app as Spring Boot, but none of the forms or information is displayed.

@Configuration
public class CloudMetricsConfig
{
   @Bean
   public TomcatServletWebServerFactory servletWebServerFactory()
   {
      return new TomcatServletWebServerFactory()
      {
         @Override
         protected void prepareContext(Host host, ServletContextInitializer[] initializers)
         {
            super.prepareContext(host, initializers);

            addContext(host, "/cloudfoundryapplication", getContextPath(),
                       "cloudfoundry");
            addContext(host, "/actuator/prometheus", getContextPath(),
                       "prometheus");
            addContext(host, "/health", getContextPath(),
                       "health");
         }
      };
   }

   private void addContext(Host host, String path, String contextPath,
                           String servletName)
   {
      StandardContext child = new StandardContext();
      child.addLifecycleListener(new Tomcat.FixContextListener());
      child.setPath(path);
      ServletContainerInitializer initializer =
             getServletContextInitializer(contextPath, servletName, path);
      child.addServletContainerInitializer(initializer, Collections.emptySet());
      child.setCrossContext(true);
      host.addChild(child);
   }

   private ServletContainerInitializer getServletContextInitializer(String contextPath,
                                                                    String servletName,
                                                                    String path)
   {
      return (c, context) ->
      {
         Servlet servlet = new GenericServlet()
         {
            @Override
            public void service(ServletRequest req, ServletResponse res)
                   throws ServletException, IOException
            {
               ServletContext context = req.getServletContext().getContext(contextPath);
               context.getRequestDispatcher(path).forward(req, res);
            }
         };
         context.addServlet(servletName, servlet).addMapping("/*");
      };
   }
}

The three URLS remapped above are set to be ignored in by Spring-Security.

In PCF Apps Manager I can see the following Routes defined for my app.

Note: Only the second route has the context-path defined as part of it.


Further Update:

The App Manager is sending a Request to the First listed Route. It doesn't appear to get a response at all .

The Logs look it should be responding though:

2021-01-22T17:03:19.928-05:00 [RTR/9] [OUT] service-dev.apps.nonprod-mpn.ro11.company.com - [2021-01-22T22:03:19.918937681Z] "OPTIONS /exe/v2/cloudfoundryapplication/health HTTP/1.1" 200 0 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Safari/605.1.15" "10.204.253.14:53711" "10.195.121.117:61118" x_forwarded_for:"10.90.161.66, 10.204.253.14" x_forwarded_proto:"https" vcap_request_id:"0c6b3241-fdea-4e77-4f75-096a673a19a8" response_time:0.008875 gorouter_time:0.003056 app_id:"ea6df148-1809-4f02-87ac-39b4ea0ebeac" app_index:"0" x_cf_routererror:"-" x_b3_traceid:"9afab521ad427253" x_b3_spanid:"9afab521ad427253" x_b3_parentspanid:"-" b3:"9afab521ad427253-9afab521ad427253"

But in Safari it says:

Response
no response headers

That's specifically for the /cloudfoundryapplication/health endpoint.
/cloudfoundryapplication returns a response successfully.

The application is running with a context path.

This requires special handling to make it work. By using a context path the /cloudfoundryapplication endpoint is no longer available. It's /your-context-path/cloudfoundryapplication . This means Apps Manager won't find it.

See...

  1. If a custom servlet context path has been configured, the Spring Boot Actuator endpoints will no longer be at the /cloudfoundryapplication path.

From this KB article .

There is a workaround, which is documented here (as I write this the link in the KB is wrong, but I'll get that fixed).

Quoting:

The configuration will differ depending on the web server in use. For Tomcat, the following configuration can be added:

@Bean
public TomcatServletWebServerFactory servletWebServerFactory() {
    return new TomcatServletWebServerFactory() {

        @Override
        protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
            super.prepareContext(host, initializers);
            StandardContext child = new StandardContext();
            child.addLifecycleListener(new Tomcat.FixContextListener());
            child.setPath("/cloudfoundryapplication");
            ServletContainerInitializer initializer = getServletContextInitializer(getContextPath());
            child.addServletContainerInitializer(initializer, Collections.emptySet());
            child.setCrossContext(true);
            host.addChild(child);
        }

    };
}

private ServletContainerInitializer getServletContextInitializer(String contextPath) {
    return (c, context) -> {
        Servlet servlet = new GenericServlet() {

            @Override
            public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
                ServletContext context = req.getServletContext().getContext(contextPath);
                context.getRequestDispatcher("/cloudfoundryapplication").forward(req, res);
            }

        };
        context.addServlet("cloudfoundry", servlet).addMapping("/*");
    };
}

What this does is to set up a second context in your embedded Tomcat that will look for /cloudfoundryapplication (from the root) and forward those requests to /cloudfoundryapplication under your actual application context (it uses cross context support to do this).

Thus when Apps Manager sends the request to your- https://<host>.apps.example.com/cloudfoundryapplication , it is forwarded to https://<host>.apps.example.com/<context-path>/cloudfoundryapplication and it arrives where it needs to be.

That is only half the battle though.

It's important to understand that for this to work, you still need a route mapped in Cloud Foundry for this traffic. Otherwise, traffic from Apps Manager won't make it to your app. If you are mapping a route with --path , that won't be sufficient as that will only send traffic for that path to your application, and the /cloudfoundryapplication request, which is a different path, will never end up at your application to use the workaround above.

For example, if you've mapped the route https://<host>.apps.example.com/<context-path> , the platform is only going to send traffic for that exact host & path to your app. Since https://<host>.apps.example.com/cloudfoundryapplication is not under that path, traffic to it will never get to your app (it may go to some other app though, so you have to be careful).

To make this workaround function, you'd need to have either a route without a path or two routes, one with your context path and one with /cloudfoundryapplication .

For example:

a.) https://<host>.apps.example.com

b.) https://<host>.apps.example.com/<context-path> + https://<host>.apps.example.com/cloudfoundryapplication .

That in essence means you can only use this workaround with one Spring Boot application per hostname, since all Spring Boot apps will need to receive requests on the same /cloudfoundryapplication path and only one app can be mapped to that route (well technically you can map a route to multiple apps, but that results in traffic split across all the apps round-robin, which isn't what you want here).

One final note, you mentioned...

A Configuration has been added to remap:

I don't see that in the information you posted. You have some Spring Security code, but that is just going to disable Spring Security for those URLs, it's not remapping/redirecting the URLs. If you're remapping URLs elsewhere, then ignore this note. Just wanted to point that out. If you are expecting it to remap/redirect them, it's not going to.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM