Cześć, dziś krótko chciałbym podzielić się z wami małym programem, który pomógł mi w eksplorowaniu tego, co znajduje się w obrazie Docker. Niedawno miałem za zadanie dodać agenta Open Telemetry do obrazu OCI/dockerowego, który budujemy za pomocą narzędzia Jib przy użyciu Gradle.

Konfiguracja pluginu

Stworzyłem plugin, który:

  • Wykorzystuje możliwości gradle do ściągnięcia opentelemetry-javaagent z repozytorium paczek ( np. z mavencentral)
  • Dodaje pobranego agenta do obrazu dockerowego wykorzystując extraDirectories
  • Ustawia parametry jvmFlags oraz Zmienną Środowiskową, żeby agent nie uruchamiał się automatycznie
  • Usuwa pobrane pliki po budowaniu
class JibWithOpenTelemetryAgentPlugin implements Plugin {  
   
    public static final String PLUGIN_ID = "com.pl.cupofcodes.gradle.open-telemetry-agent"  
    public static final String OPEN_TELEMETRY_AGENT_ARGS = "-javaagent:/app/WEB-INF/agent/otel-javaagent.jar"  
    public static final String AGENT_FOLDER_LOCATION = "src/main/jib/app/WEB-INF/agent"  
  
    private Project project    
  
    @Override  
    void apply(Project project) {  
        this.project = project   
        applyOpenTelemetryAgentConfiguration()  
        applyJibConfiguration()
    }  
  
	private void applyOpenTelemetryAgentConfiguration() {  
	    def otelJavaAgentVersion = project.findProperty("otelJavaAgentVersion") ?: "1.32.0"  
	    project.configurations {  
	        agent  
	    }  
	  
	    project.dependencies {  
	        agent "io.opentelemetry.javaagent:opentelemetry-javaagent:$otelJavaAgentVersion"  
	    }  
	  
	    project.task("copyAgentJar", type: Copy) {  
	        from project.configurations.agent  
	        into AGENT_FOLDER_LOCATION  
	        rename { String fileName -> "otel-javaagent.jar" }  
	    }  
	    project.tasks.jib.dependsOn "copyAgentJar"  
        project.tasks.jibDockerBuild.dependsOn "copyAgentJar"  
	} 

	private void applyJibConfiguration() {  
	    project.with {  
	        pluginManager.apply("com.google.cloud.tools.jib")  
	  
	        jib {  
	            from {  
	                image = "amazoncorretto:21"
	            }  
	            container {  
	                jvmFlags = [OPEN_TELEMETRY_AGENT_ARGS]  
	                  
	                //default value false to not starting agent always, we can start it manually if needed from container env  
	                environment = ['OTEL_JAVAAGENT_ENABLED': "false"]  
	            }  
	            extraDirectories {  
	                paths {  
	                    path {  
	                        from = AGENT_FOLDER_LOCATION  
	                        into = '/app/WEB-INF/agent'  
	                    }  
	                }            
				} 
			    containerizingMode = "packaged"  
	        }  
	    }  
	    project.tasks.jib.doLast {  
	        project.delete AGENT_FOLDER_LOCATION  
	    }  
	  
	    project.tasks.jibDockerBuild.doLast {  
	        project.delete AGENT_FOLDER_LOCATION  
	    }  
	}
}

Jednak obraz nie chciał się uruchomić lokalnie (działa na K8S i wymaga masy zmiennych, których nie było sensu ustawiać lokalnie), przez co nie mogłem zweryfikować, czy jar z agentem dodał się do obrazu OCI. Z pomocą przyszło mi narzędzie Dive.

Dive

Jest to niewielkie świetne narzędzie do eksplorowania obrazu. Pozwala sprawdzić, jak zbudowany jest obraz, ile ma warstw, a dodatkowo pokazuje również, co się zmienia w każdej z warstw i jakie pliki tam się znajdują razem z ich ścieżką.

Zainstalować można je z poziomu terminala

# macbook
brew install dive

# ubuntu
export DIVE_VERSION=$(curl -sL "https://api.github.com/repos/wagoodman/dive/releases/latest" | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/')
curl -OL https://github.com/wagoodman/dive/releases/download/v${DIVE_VERSION}/dive_${DIVE_VERSION}_linux_amd64.deb
sudo apt install ./dive_${DIVE_VERSION}_linux_amd64.deb

Więcej sposobów instalacji oraz informacji o narzędziu Dive można znaleźć tutaj:
wagoodman/dive: A tool for exploring each layer in a docker image (github.com)

To co chciałem osiągnąć to, sprawdzenie czy, i gdzie został skopiowany jar z agentem javowym. po zbudowaniu aplikacji.
Do zbudowania obrazu wykorzystałem polecenie.

# macbook albo wsl2 (docker desktop)
gradle jibDockerBuild -Djib.dockerClient.executable=$(which docker)  --image imageName

# linux albo wsl2 ( docker zainstalowany w wsl2 ) 
gradle jibDockerBuild --image imageName

Po zbudowaniu aplikacji mogłem sprawdzić, czy agent faktycznie trafił do obrazu i gdzie konkretnie się znajduje. Wynik polecenia:

dive docker-sample-java-app

prezentuje poniższy obraz:

Dive działa w okienku terminala, jest interaktywny, a nawigacja odbywa się za pomocą strzałek oraz przycisku tab.

Po lewej stronie widzimy:

  • Wszystkie warstwy obrazu,
  • Szczegóły poszczególnych warstw,
  • Informacje ogólne dotyczące całego obrazu. Dodatkowo dostajemy informacje o scoringu obrazu i potencjalnej wielkości, jaką można zaoszczędzić w obrazie. Działa to na podstawie analizy duplikowanych plików pomiędzy warstwami, przenoszenia plików pomiędzy warstwami oraz na podstawie nie do końca usuniętych plików.

Prawa strona zmienia się w zależności od wybranej warstwy po lewej stronie i widzimy tam:

  • Listę plików z możliwością przechodzenia/rozwijania/zwijania folderów i filtrowania plików po nazwie.

Powyższy screen jest wynikiem końcowym mojej konfiguracji. Pokazuje, że w ostatniej warstwie dodanej przez Jib plugin, agent faktycznie znajduje się w lokalizacji /app/WEB-INF/agent . Dość szybko doszedłem do tego, jak dodać agenta do obrazu, jednak nie wiedziałem, do jakiej lokalizacji jib kopiuje wskazane przeze mnie pliki, czy nie dodaje czegoś od siebie. Dzięki narzędziu Dive mogłem sprawdzić, czy agent został skopiowany i jaką dokładnie ma lokalizację w file systemie obrazu. Na koniec nie zostało mi nic innego, jak skonfigurować jvmFlags i dodać do nich parametr do uruchomienia agenta przy starcie aplikacji: -javaagent:/app/WEB-INF/agent/otel-javaagent.jar