Vážení kolegové programátoři, ruku na srdce. Pro kolik z nás končí svět tím, že commit-ujeme své grandiózní dílo do Gitu. Vždyť dále se to jenom sestaví Jenkinsem, JAR se někam zkopíruje a spustí. On ale ten svět po commit-u je daleko košatější a barvitější, než by na první pohled mohlo vypadat. Pokud se zeptáte kolegů ze support oddělení, budou vám jistě vyprávět hrůzostrašné historky z provozu aplikaci (samozřejmě že ne těch vašich).
Pokud vás to bude zajímat, pokusil bych se podívat na některé aspekty vývoje aplikace z pohledu jejího budoucího provozu.
Takto formulované zadání by bylo jistě hodně široké, takže si vyberu pouze úzkou část. Podívám se na podporu, kterou mně poskytuje framework Spring Boot v oblasti provozu aplikace.
Framework tuto podporu skrývá pod označení Spring Boot Actuator, nebo také „Production-ready Features“.
Jedná se o sadu nástrojů a rozšíření, které by vám měly pomoci při zjišťování informací o běžící aplikaci, stavu aplikace a jejich komponent, využití jejich služeb, monitorování provozu a podobně.
Zdrojové texty ke všem níže uvedeným příkladům najdete v Git repository: jraska1/jv-actuator-examples
Framework Spring Boot poskytuje volitelnou podporu pro správu a monitorování aplikace za jejího běhu – Actuator. Jedná se o sadu nástrojů přístupných jako koncové body prostřednictvím rozhraní JMX nebo HTTP.
Vzhledem k tomu, že nejsem velkým příznivcem JMX, zaměřím se v tomto článku pouze na přístup ke koncovým bodům prostřednictvím HTTP. Nicméně je dobré vědět, že tato možnost tady je.
Příklady pro tuto kapitolu najdete v jraska1/jv-actuator-examples/example-00.
Abyste podporu mohli využít, musíte přidat do Maven projektu závislosti:
<dependency>
<groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
To je vše, co potřebujete udělat, aby se vám Actuator přidal do aplikace, inicioval po startu, a poskytl rozhraní přes HTTP.
Pokud si aplikaci spustíte, pak byste měli vidět něco takového:
[raska@fedora example-00]$ java -jar target/example-00-1.0.0.jar . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.6.1) 2022-01-16 11:38:39.083 INFO 7602 --- [ main] c.d.a.example00.Application : Starting Application v1.0.0 using Java 11.0.13 on fedora with PID 7602 (/home/raska/IdeaProjects/jv-actuator-examples/example-00/target/example-00-1.0.0.jar started by raska in /home/raska/IdeaProjects/jv-actuator-examples/example-00) 2022-01-16 11:38:39.091 INFO 7602 --- [ main] c.d.a.example00.Application : No active profile set, falling back to default profiles: default 2022-01-16 11:38:41.182 INFO 7602 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2022-01-16 11:38:41.206 INFO 7602 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2022-01-16 11:38:41.207 INFO 7602 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.55] 2022-01-16 11:38:41.336 INFO 7602 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2022-01-16 11:38:41.337 INFO 7602 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2073 ms 2022-01-16 11:38:42.285 INFO 7602 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 1 endpoint(s) beneath base path '/actuator' 2022-01-16 11:38:42.359 INFO 7602 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2022-01-16 11:38:42.399 INFO 7602 --- [ main] c.d.a.example00.Application : Started Application in 4.345 seconds (JVM running for 5.024) 2022-01-16 11:38:42.536 INFO 7602 --- [ main] c.d.a.example00.Application : *** Hello World, greetings from Dwarf ***
Z výpisu můžete vidět, že se nám něco rozjelo na URL /actuator. To je přístupový bod do HTTP rozhraní Actuator.
Vše, co Actuator umí, poskytuje prostřednictvím tzv. koncových bodů.
Pokud se bavíme s nějakým koncovým bodem Actuator, pak je výsledkem vždy JSON objekt. Proto používám přehlednější formátování výstupu přes nástroj jq.
Nyní si již můžete zkusit s ním popovídat (dostaneme přehled dostupných koncových bodů):
[raska@fedora ~]$ curl -s http://localhost:8080/actuator | jq . { "_links": { "self": { "href": "http://localhost:8080/actuator", "templated": false }, "health-path": { "href": "http://localhost:8080/actuator/health/{*path}", "templated": true }, "health": { "href": "http://localhost:8080/actuator/health", "templated": false } } }
Aby bylo možné využívat služeb koncových bodů, je potřeba povolit jejich spuštění a následně také zpřístupnit je přes HTTP rozhraní.
Implicitní nastavení je takové, že všechny koncové body Actuator jsou při startu povoleny až na jednu výjimku, a tou je shutdown.
Současně však žádný koncový bod není zpřístupněn přes HTTP opět s jednou výjimkou, a tou je health.
Takže si můžete vyzkoušet:
[raska@fedora ~]$ curl -s http://localhost:8080/actuator/health | jq . { "status": "UP" }
Povolení a zpřístupnění koncového bodu se provádí nastavením parametrů v application.yaml:
management.endpoint.<id>.enabled – nastavením parametru na true/false
management.endpoints.web.exposure.include – seznamu koncových bodů, které mají být přístupny přes webové rozhraní
Vše bude zřejmější, pokud si ukážeme nastavení pro jednotlivé komponenty dále.
Ještě pro doplnění uvádím, že zde najdete přehled koncových bodů, které jsou k dispozici v rámci frameworku.
Detailní popis vlastností Spring Boot Actuator pro tuto oblast najdete v kapitole: 2.10. Application Information.
Příklady pro tuto kapitolu najdete v jraska1/jv-actuator-examples/example-01.
První oblastí, kde můžete služby Actuator použít, je zjišťování informací o aplikaci za jejího běhu.
Jedná se o koncový bod info, který poskytuje informace ze čtyř oblastí, a sice:
build – informace o sestavení aplikace, zdrojem je META-INF/build-info.properties
Tento soubor se vám do aplikačního balíku nepřidá sám, musíte v pom.xml doplnit plugin:
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <goals> <goal>build-info</goal> </goals> </execution> </executions> </plugin>
git – informace o projektu v GIT repository, zdrojem je git.properties
Aby se vám přidal tento soubor do aplikačního balíku, musíte v pom.xml doplnit plugin:
<plugin> <groupId>pl.project13.maven</groupId> <artifactId>git-commit-id-plugin</artifactId> </plugin>
java – informace o běhovém prostředí Java
env – informace o aplikaci specifikované v properties
Poskytuje všechny informace z properties, které jsou ve skupině info.*.
Pokud chceme do těchto parametrů zařadit i informace z pom.xml, pak musíme ještě doplnit resource:
<resource> <directory>${basedir}/src/main/resources</directory> <filtering>true</filtering> <includes> <include>**/application*.yml</include> <include>**/application*.yaml</include> <include>**/application*.properties</include> </includes> </resource>
Všechna potřebná doplnění do pom.xml najdete v příkladech k této kapitole.
Co je potřeba nastavit v application.yaml, aby to fungovalo:
management: endpoints: web: exposure: include: - info info: build: enabled: true env: enabled: true git: enabled: true java: enabled: true info: app: name: "@project.name@" description: "@project.description@" version: "@project.version@" produced-by: "Dwarf's Software Workshop, 2022" source: target: "@java.version@" encoding: "@project.build.sourceEncoding@"
V první části parametrů je povolen koncový bod info. A následně v části parametrů management.info jsou povoleny všechny čtyři oblasti informací.
Nakonec mám sekci parametrů začínajících na info.*. Jejich hodnoty jsou nastaveny při sestavení aplikace z properties definovaných pro Maven nebo rovnou jako text.
Nejdříve spustit aplikaci:
[raska@fedora example-01]$ java -jar target/example-01-1.0.0.jar
A takto vypadá výsledek:
[raska@fedora ~]$ curl -s http://localhost:8080/actuator/info | jq . { "app": { "name": "Spring Boot Actuator Example 01", "description": "Spring Boot Actuator Examples in Java and Spring Boot - Information about Application", "version": "1.0.0", "produced-by": "Dwarf's Software Workshop, 2022" }, "source": { "target": "11.0.13", "encoding": "UTF-8" }, "git": { "branch": "main", "commit": { "id": "fd4f80c", "time": "2022-01-11T16:46:31Z" } }, "build": { "artifact": "example-01", "name": "Spring Boot Actuator Example 01", "time": "2022-01-11T17:18:31.436Z", "version": "1.0.0", "group": "cz.dsw.actuator-examples" }, "java": { "vendor": "Red Hat, Inc.", "version": "11.0.13", "runtime": { "name": "OpenJDK Runtime Environment", "version": "11.0.13+8" }, "jvm": { "name": "OpenJDK 64-Bit Server VM", "vendor": "Red Hat, Inc.", "version": "11.0.13+8" } } }
Je ještě jedna možnost, na kterou bych rád upozornil. Vzhledem k tomu, že oblast env načítá informace z properties, můžeme je také nastavit při spuštění aplikace. To se může hodit, pokud si nějak chceme označit jednotlivé instance spuštění, třeba takto:
[raska@fedora example-01]$ java -jar target/example-01-1.0.0.jar --info.instance.id=inst-1234 --info.instance.name=my-application
No a pak informace o běžící instanci dostaneme přes koncový bod info (zkrácený výpis):
[raska@fedora ~]$ curl -s http://localhost:8080/actuator/info | jq . { "instance": { "name": "my-application", "id": "inst-1234" }, "app": { "name": "Spring Boot Actuator Example 01", "description": "Spring Boot Actuator Examples in Java and Spring Boot - Information about Application", "version": "1.0.0", "produced-by": "Dwarf's Software Workshop, 2022" }, ... }
Dalším zdrojem informací o běžící aplikaci, je koncový bod env.
Aby byl přístupný, tak toto musí být nastaveno v application.yaml:
management: endpoints: web: exposure: include: - env
Po jeho vyvolání dostanete přehled nastavení proměnných prostředí (je to hodně dlouhý přehled, proto jsem některé sekce zkrátil tak abyste měli přehled):
[raska@fedora ~]$ curl -s http://localhost:8080/actuator/env | jq . { "activeProfiles": [], "propertySources": [ { "name": "server.ports", "properties": { "local.server.port": { "value": 8080 } } }, { "name": "commandLineArgs", "properties": { "info.instance.name": { "value": "my-application" }, "info.instance.id": { "value": "inst-1234" } } }, { "name": "servletContextInitParams", "properties": {} }, { "name": "systemProperties", "properties": { "sun.desktop": { "value": "gnome" }, "awt.toolkit": { "value": "sun.awt.X11.XToolkit" }, ... "sun.io.unicode.encoding": { "value": "UnicodeLittle" }, "java.class.version": { "value": "55.0" } } }, { "name": "systemEnvironment", "properties": { "PATH": { "value": "/home/raska/.local/bin:/home/raska/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin", "origin": "System Environment Property \"PATH\"" }, "XAUTHORITY": { "value": "/run/user/1000/.mutter-Xwaylandauth.ODRPF1", "origin": "System Environment Property \"XAUTHORITY\"" }, ... "SHLVL": { "value": "1", "origin": "System Environment Property \"SHLVL\"" }, "HOME": { "value": "/home/raska", "origin": "System Environment Property \"HOME\"" } } }, { "name": "Config resource 'class path resource [application.yaml]' via location 'optional:classpath:/'", "properties": { "management.endpoints.web.exposure.include[0]": { "value": "info", "origin": "class path resource [application.yaml] from example-01-1.0.0.jar - 7:13" }, "management.endpoints.web.exposure.include[1]": { "value": "env", "origin": "class path resource [application.yaml] from example-01-1.0.0.jar - 8:13" }, ... "node.name": { "value": "node01", "origin": "class path resource [application.yaml] from example-01-1.0.0.jar - 31:9" }, "node.id": { "value": "local:node01", "origin": "class path resource [application.yaml] from example-01-1.0.0.jar - 32:7" } } } ] }
Posledním zdrojem informací, který by se vám jako programátorům mohl hodit, je přehled instancí ve Spring Context po spuštění aplikace – koncový bod beans.
Zde si můžete ověřit, zda framework po startu obsahuje instance tříd, které potřebujete a můžete je tedy odkazovat přes @Autowired.
Aby byl přístupný, tak toto musí být nastaveno v application.yaml:
management: endpoints: web: exposure: include: - beans
A takto to vypadá, pokud si jej zavoláte (opět zkrácený výpis pro přehlednost):
[raska@fedora ~]$ curl -s http://localhost:8080/actuator/beans | jq . { "contexts": { "application": { "beans": { "endpointCachingOperationInvokerAdvisor": { "aliases": [], "scope": "singleton", "type": "org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor", "resource": "class path resource [org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.class]", "dependencies": [ "org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration", "environment" ] }, "defaultServletHandlerMapping": { "aliases": [], "scope": "singleton", "type": "org.springframework.web.servlet.HandlerMapping", "resource": "class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]", "dependencies": [ "org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration" ] }, "applicationTaskExecutor": { "aliases": [ "taskExecutor" ], "scope": "singleton", "type": "org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor", "resource": "class path resource [org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.class]", "dependencies": [ "org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration", "taskExecutorBuilder" ] }, ... "application": { "aliases": [], "scope": "singleton", "type": "cz.dsw.actuator_examples.example01.Application$$EnhancerBySpringCGLIB$$c68d1643", "resource": null, "dependencies": [] }, "taskExecutorBuilder": { "aliases": [], "scope": "singleton", "type": "org.springframework.boot.task.TaskExecutorBuilder", "resource": "class path resource [org/springframework/boot/autoconfigure/task/TaskExecutionAutoConfiguration.class]", "dependencies": [ "org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration", "spring.task.execution-org.springframework.boot.autoconfigure.task.TaskExecutionProperties" ] } }, "parentId": null } } }
Dohledáte zde nejenom jméno instance a třídu, která jí implementuje, ale také její závislosti na dalších instancích. Přehled je dost velký, ale grep je mocný nástroj, takže není třeba se toho bát.
Prozatím jsem doloval informace o aplikaci z jejího okolí, ať již to bylo nastavení prostředí, Java virtuálního stroje, properties a podobně.
Mám ale ještě jednu možnost, a tou je generování informací rovnou z aplikačního kódu.
Je to docela jednoduché. Každá instance třídy registrovaná ve Spring Context, která implementuje rozhraní InfoContributor, přidává informace do koncového bodu info.
Abych mohl něco ukázat, tak jsem si převzal implementace aplikačních služeb, které jsem používal v článcích o aplikačních událostech (ony jsou triviální, takže je popisovat nebudu, bude jistě stačit krátký pohled do zdrojových kódů).
To, co jsem ale doplnil navíc, je komponenta:
@Component @Profile("service-info") public class ServiceInfo implements InfoContributor { @Autowired(required = false) List<ServiceProvider> allProviders; @Override public void contribute(Info.Builder builder) { builder.withDetail("service-providers", (allProviders != null) ? allProviders.stream().map(ServiceProvider::getInstanceName).collect(Collectors.toList()) : Collections.emptyList()); } }
Je sice krátká, ale jistě bude stát za komentář:
Atribut allProviders je injektován z kontextu Spring jako seznam objektů implementujících rozhraní ServiceProvider. Takových poskytovatelů služeb tam může být několik (nebo také žádný).
Implementace rozhraní InfoContributor vyžaduje implementaci metody contribute(Info.Builder).
Pokud mám neprázdný seznam poskytovatelů, vytvořím informaci s označením „service-providers“, ve které jsou názvy instancí poskytovatelů služby.
Komponenta se startuje, pokud mám aktivován profil service-info.
Takže si vyvolám aplikaci s aktivním profilem service-info:
[raska@fedora example-01]$ java -jar target/example-01-1.0.0.jar --info.instance.id=inst-1234 --info.instance.name=my-application --spring.profiles.active=service-info
Pokud se nyní podívám na koncový bod info, pak bych měl vidět něco takového (výpis opět zkrácen):
[raska@fedora ~]$ curl -s http://localhost:8080/actuator/info | jq . { "instance": { "name": "my-application", "id": "inst-1234" }, "app": { "name": "Spring Boot Actuator Example 01", "description": "Spring Boot Actuator Examples in Java and Spring Boot - Information about Application", "version": "1.0.0", "produced-by": "Dwarf's Software Workshop, 2022" }, ... "service-providers": [ "Provider B", "Provider A" ] }
Berte to prosím jako jednoduchý příklad. Využití pro vaše potřeby je potřeba vždy zvážit.
A to je dnes vše. Příště se podívám na sledování stavu aplikace a jednotlivých komponent.
pracuje na pozici IT architekta. Poslední roky se zaměřuje na integrační a komunikační projekty ve zdravotnictví. Mezi jeho koníčky patří také paragliding a jízda na horském kole.
Přečteno 27 672×
Přečteno 27 339×
Přečteno 25 868×
Přečteno 23 739×
Přečteno 19 479×