{"id":15156,"date":"2023-12-14T01:00:00","date_gmt":"2023-12-14T09:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/ise\/?p=15156"},"modified":"2024-07-18T11:54:08","modified_gmt":"2024-07-18T18:54:08","slug":"debug-java-dapr-in-codespaces","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/ise\/debug-java-dapr-in-codespaces\/","title":{"rendered":"Debugging Java Dapr-enabled Apps in GitHub Codespaces"},"content":{"rendered":"<h2>Introduction<\/h2>\n<p>Building Java distributed applications leveraging Dapr provides a\npowerful yet simple way of harnessing your microservice solution with\nthe best and proven patterns, practices, and technologies around service\ndiscovery, message broker integration, encryption, observability, and\nsecret management. This allows you to focus on your business logic and\nkeep your code clean and tech-agnostic making use of the depth and\nbreadth of the Java ecosystem.<\/p>\n<p>While implementing a single application can be easily coded, tested, and\ndebugged locally or even using a single dev-container with all the\npreconditions already installed in it, modern cloud and serverless\narchitectures often require developing several decoupled independent\ncomponents\/applications that interact with each other applying pub\/sub,\nmessage-oriented, or event-driven patterns. Scaling your local dev\nenvironment accordingly so that you can spin up, debug, and test a\ncomplex solution of independent yet integrated applications speedily and\nreliably becomes an imperative issue to solve if you don&#8217;t want to spend\nhalf of the development time just configuring and setting up multiple\nrunning applications on your developer box.<\/p>\n<p>You will need to consider that each of these applications can be\ndeveloped utilizing different programming languages, will require the\nDapr &#8220;sidecar,&#8221; and can have unique backend integration dependencies, as\nshown in the following diagram:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/devblogs.microsoft.com\/ise\/wp-content\/uploads\/sites\/55\/2023\/12\/dapr-architecture.png\" alt=\"A diagram of a Dapr-enabled Microservice solution\" \/><\/p>\n<p>Dapr is a modern framework built to support cloud-native and serverless\ntechnologies. It is especially useful for deploying applications on top\nof Kubernetes or Azure Functions, acting like a pod sidecar\/agent. It\nbrings a highly modular and decoupled mindset that can be extended to\nthe Docker ecosystem. This key principle means that everything can be\ntranslated into a container, including your development environment.<\/p>\n<h2>Docker-compose vs Dapr Multi-App run<\/h2>\n<p>If your solution is composed of only Dapr applications with no dependent\ninfrastructure services, or if these dependencies are already deployed\nor accessible within your dev environment, or even if you just want to\nsplit the way you deploy things you may want to consider Dapr&#8217;s\n<a href=\"https:\/\/docs.dapr.io\/developing-applications\/local-development\/multi-app-dapr-run\/multi-app-overview\/\">Multi-App run template file<\/a>.\nWith Multi-App Run, you can start many applications in self-hosted mode\nexecuting a single\u00a0<code>Dapr run -f<\/code>\u00a0command employing a template file.\nThe template file describes how to start multiple applications as if you\nhad run separate CLI\u00a0run commands.<\/p>\n<p>Some possible tradeoffs applying this approach are:<\/p>\n<ul>\n<li>\n<p>You still need to install\/deploy all dependencies and services\nmanually, leveraging docker-compose or some other way.<\/p>\n<\/li>\n<li>\n<p>You can&#8217;t do network config, manage replicas, or set a devcontainer\nusing the Dapr multi-app run file.<\/p>\n<\/li>\n<li>\n<p>You may end up exploiting different deployment strategies which will\nmake it harder and awkward to maintain and more prone for errors\n(specially for new developers jumping into the code for the first\ntime)<\/p>\n<\/li>\n<\/ul>\n<p>The rest of this article focuses on the Docker compose approach\nexclusively.<\/p>\n<h2>Goals<\/h2>\n<ul>\n<li>\n<p>Being able to spin up a multi-application development environment in\na matter of minutes instead of hours\/days.<\/p>\n<\/li>\n<li>\n<p>The only precondition is having VS Code and Docker installed or\nhaving GitHub&#8217;s Codespaces available.<\/p>\n<\/li>\n<li>\n<p>Being able to automatically spin up all infrastructure services like\ndatabases, observability platforms, supporting APIs, or messaging\nservices as part of the environment.<\/p>\n<\/li>\n<li>\n<p>Being able to hit breakpoints on Java applications that depend on or\nintegrate with daprd or the Dapr Java SDK.<\/p>\n<\/li>\n<li>\n<p>Avoid paying IDE licensing.<\/p>\n<\/li>\n<\/ul>\n<h2>Detailed Approach<\/h2>\n<h3>High level approach<\/h3>\n<ul>\n<li>Use Docker Compose to declare all components in the solution.\n<ul>\n<li>Define the Dapr sidecar as an independent container for each Java\napplication and make sure they attach to their respective Java\nApp&#8217;s network namespace.<\/li>\n<li>Include all infrastructure services like messaging, telemetry,\ndatabase services, existing supporting APIs, token service and\nso on, each in its individual container.<\/li>\n<li>Define an <code>extra-host<\/code> property in each Java app docker-compose\ndefinition, allowing them to access resources at the root\nlocalhost container level.<\/li>\n<\/ul>\n<\/li>\n<li>Modify your Dapr Components URL routing to match the extra-host\ndefined in your Docker compose file. <code>127.0.0.1<\/code> or <code>localhost<\/code>\nwon&#8217;t work to reach resources at the host level.<\/li>\n<li>Use a VS Code devcontainer definition enabling the <strong>Docker-in-Docker<\/strong>\nfeature, allowing you to deploy a devcontainer with a Docker daemon\nand be able to spin up more containers within it.<\/p>\n<ul>\n<li>Enable all required Java extensions to be fully productive as a\nJava\/Dapr developer.<\/li>\n<li>Install Maven and OpenJDK at the root devcontainer to enable the\nfull VS Code Java debugging experience.<\/li>\n<\/ul>\n<\/li>\n<li>Use a Dockerfile to build and test each individual Java application\nand create the Docker image.<\/p>\n<ul>\n<li>In the container <code>ENTRYPOINT<\/code>, enable Java remote debugging.<\/li>\n<\/ul>\n<\/li>\n<li>Use VS Code to debug the Java applications, adding run and debug\nconfigurations to attach to a remote Java process.<\/li>\n<\/ul>\n<h3>Docker Compose<\/h3>\n<p>The main Docker compose settings revolve around properly configuring\nDaprd integration with each of the Java Apps. This includes:<\/p>\n<ul>\n<li>Make sure to follow all instructions on <a href=\"https:\/\/docs.dapr.io\/operations\/hosting\/self-hosted\/self-hosted-with-docker\/#run-using-docker-compose\">How-To: Run Dapr in\nself-hosted mode with Docker | Dapr\nDocs<\/a>.\nSpecially the one around the <strong><em>placement service<\/em><\/strong> definition, the\n<code>network_mode<\/code> settings matching the Java app network namespace on the\nDapr sidecar and checking that all ports match.<\/li>\n<li>Define the Dapr environment variables in each Java app:\n<code>DAPR_HTTP_PORT<\/code>, <code>APP_PROTOCOL<\/code>, <code>DAPR_GRPC_PORT<\/code>. Values will depend on\nyour specific Dapr configuration.<\/li>\n<li>Subject to your specific context you may want to expose the App HTTP\nport, DAPR HTTP and\/or GRPC ports and in this case the JAVA remote\ndebugging process port, to allow VS Code to connect to it. See how\nto define it in the Dockerfile settings below.<\/li>\n<li>When running inside a full Linux environment as Codespaces is,\nyou&#8217;ll need to set an extra-host as\n<code>\"host.docker.internal:host-gateway\"<\/code> in order for Dapr to be able\nto access services at other containers and the localhost root\ncontainer service. See: <a href=\"https:\/\/stackoverflow.com\/questions\/24319662\/from-inside-of-a-docker-container-how-do-i-connect-to-the-localhost-of-the-mach\/24326540#24326540\">How do I connect to the localhost of the machine? &#8211;\nStack Overflow<\/a>.<\/li>\n<li>Note that the Java app defines a build section, that includes\npointing to a specific <code>Dockerfile<\/code> within the context folder. This\n<code>Dockerfile<\/code> is explain in the next section.<\/li>\n<\/ul>\n<p>Below is an example of just the placement service, one Java App called\naccount-service and its correspondent Dapr sidecar:<\/p>\n<pre><code class=\"language-yaml\">  placement:\r\n    image: \"daprio\/dapr\"\r\n    command: [\".\/placement\", \"--port\", \"50006\"]\r\n    ports:\r\n      - \"50006:50006\"\r\n\r\n  account-service:\r\n    image: account-service:latest\r\n    build:\r\n      context: \".\/api\/account\"\r\n      dockerfile: Dockerfile.dev\r\n    environment:\r\n      - LOGGING_LEVEL_ROOT\r\n      - SERVER_PORT=8082\r\n      - DAPR_HTTP_PORT=3601\r\n      - APP_PROTOCOL=HTTP\r\n      - DAPR_GRPC_PORT=60001\r\n    depends_on:\r\n      - redis\r\n    ports:\r\n      - \"60001:60001\" # Dapr instances communicate over gRPC so we need to expose the gRPC port\r\n      - \"3601:3601\"\r\n      - \"8082:8082\"\r\n      - \"5005:5005\"\r\n    extra_hosts:\r\n      - \"host.docker.internal:host-gateway\"\r\n\r\n  account-service-dapr:\r\n    image: \"daprio\/daprd:1.11.2\"\r\n    command: [\r\n      \".\/daprd\",\r\n      \"--app-id\", \"account-service\",\r\n      \"--app-port\", \"8082\",\r\n      \"--dapr-grpc-port\", \"60001\",\r\n      \"--resources-path\", \".\/components\",\r\n      \"--placement-host-address\", \"placement:60001\" # Dapr's placement service can be reach via the docker DNS entry\r\n      ]\r\n    volumes:\r\n        - \".\/components\/:\/components\" # Mount our components folder for the runtime to use. The mounted location must match the --resources-path argument.\r\n    depends_on:\r\n      - redis\r\n    environment:\r\n      - SECRET_redis-password=\"\" # Using an empty password for this example, but you could set a password here and inject it into a .env file or a key-vault store\r\n    network_mode: \"service:account-service\" # Attach the account-service-dapr service to the account-service network namespace\r\n<\/code><\/pre>\n<h3>Modify your Dapr Components to route resources<\/h3>\n<p>Modify your Dapr Components URL routing to match the extra-host defined\nin your Docker compose file. <code>\"127.0.0.1\"<\/code> or <code>localhost<\/code> won&#8217;t work to\nreach resources at the host level in Linux environments like Codespaces.<\/p>\n<p>In the following example, we modified the <code>redisHost<\/code> value from\n<code>\"localhost:6379\"<\/code> to <code>\"0-initialize-redis-1:6379\"<\/code> mapping the DNS routing\nassigned to Docker to the <code>Redis<\/code> service. It does that by starting at the\nroot directory where the docker-compose.yaml file is located, named\n<strong>&#8220;0-initialize&#8221;<\/strong>, the name of the service <strong>&#8220;redis&#8221;<\/strong> and the replica, which\nis just <strong>&#8220;1&#8221;<\/strong> in this case.<\/p>\n<pre><code class=\"language-yaml\">apiVersion: dapr.io\/v1alpha1\r\nkind: Component\r\nmetadata:\r\n  name: account-database-binding\r\nspec:\r\n  type: bindings.redis\r\n  version: v1\r\n  metadata:\r\n  - name: redisHost\r\n    value: \"0-initialize-redis-1:6379\"\r\n....<\/code><\/pre>\n<h3>Java App Dockerfile<\/h3>\n<p>Each Java app has a <code>Dockerfile<\/code> based out on the Maven, OpenJDK18 image,\nthus allowing a <code>mvn clean install<\/code> to run seamlessly. Another significant\naspect is the <code>\"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005\"<\/code>\nthat enables Java to expose its remote debugging port for external\nprocesses to attach to. In this example is <strong>5005<\/strong> but you may need to make\nsure each App exposes a different port to avoid conflicts.<\/p>\n<pre><code class=\"language-dockerfile\">FROM maven:3.8.5-openjdk-18-slim\r\n\r\nWORKDIR \/src\r\nCOPY . .\r\n\r\nRUN mvn clean install -e -f \"pom.xml\"\r\n\r\nENTRYPOINT [\"java\",\"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005\", \"-Dspring.profiles.active=localdocker\",\"-jar\",\"target\/account-0.0.1-SNAPSHOT.jar\"]\r\n<\/code><\/pre>\n<h3>Devcontainer<\/h3>\n<p>We use the <code>docker-in-docker<\/code> feature. This will enable to create child\ncontainers inside a container, independent from the host&#8217;s docker\ninstance. For more information find the official <a href=\"https:\/\/github.com\/devcontainers\/features\/blob\/main\/src\/docker-in-docker\/README.md\">docker-in-docker documentation<\/a>.<\/p>\n<p>To enable it add this section to the devcontainer.json file:<\/p>\n<pre><code class=\"language-json\">\"features\": {\r\n    \"ghcr.io\/devcontainers\/features\/docker-in-docker:2\": {}\r\n}<\/code><\/pre>\n<p>Moreover, we included some extensions whose are helpful for developing\nJava applications. The one you really need to debug the app is the <strong>VS\nCode extension pack for java<\/strong>: <code>\"vscjava.vscode-java-pack\"<\/code>, including\nthe debugger for Java among other useful language tools.<\/p>\n<pre><code class=\"language-json\">    \"extensions\": [\r\n        \"ms-azuretools.vscode-docker\",\r\n        \"humao.rest-client\",\r\n        \"cweijan.vscode-mysql-client2\",\r\n        \"vscjava.vscode-java-pack\",\r\n        \"ms-azuretools.vscode-dapr\"\r\n    ]<\/code><\/pre>\n<p>Lastly, for VS Code to be able to debug it also needs a Java SDK\ninstalled at the root container. We install it with Maven and the <a href=\"https:\/\/v0-11.docs.dapr.io\/getting-started\/install-dapr-cli\/#:~:text=Install%20the%20Dapr%20CLI%201%20Step%201%3A%20Run,%7C%20%2Fbin%2Fbash%202%20Step%202%3A%20Verify%20the%20installation\">Dapr\nCLI<\/a>.<\/p>\n<pre><code class=\"language-dockerfile\">RUN apt-get update &amp;&amp; export DEBIAN_FRONTEND=noninteractive \\\\\r\n\r\n&amp;&amp; apt-get -y install \\--no-install-recommends build-essential\r\nopenjdk-11-jdk maven\r\n\r\nRUN wget -q\r\nhttps:\/\/raw.githubusercontent.com\/dapr\/cli\/master\/install\/install.sh\r\n-O - \\| \/bin\/bash<\/code><\/pre>\n<h3>VS Code Run and Debug configuration<\/h3>\n<p>Finally, you must set a <strong>run and debug<\/strong> configuration for each Java\napplication you want to attach to debug remotely. To do so, you need to\ncreate <code>launch.json<\/code> file within the <code>.vscode<\/code> directory, usually not\nversioned in your Git repository but could be a good exception to\nconsider. These are the settings we configured for the two Java\napplications in our example:<\/p>\n<pre><code class=\"language-json\">{\r\n    \"version\": \"0.2.0\",\r\n    \"configurations\": [\r\n        {\r\n            \"type\": \"java\",\r\n            \"name\": \"Account in Docker\",\r\n            \"request\": \"attach\",\r\n            \"hostName\": \"localhost\",\r\n            \"port\": \"5005\"\r\n        },\r\n        {\r\n            \"type\": \"java\",\r\n            \"name\": \"Transaction in Docker\",\r\n            \"request\": \"attach\",\r\n            \"hostName\": \"localhost\",\r\n            \"port\": \"5006\"\r\n        }\r\n    ]\r\n}<\/code><\/pre>\n<h2>Conclusion<\/h2>\n<p>There are various aspects to consider when enabling a complete developer\nexperience building complex Java and Dapr solutions. It&#8217;s time well\nspent to make sure you have a consistent and reliable dev environment\nready to hit the ground running &#8212; Including hitting debugging\nbreakpoints, which undoubtedly will speed up the focus on what is more\nrelevant: coding the business logic.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>To simplify the development and debugging of complex Java Dapr-enabled microservices we recommend leveraging Docker Compose to define all services including the Java Apps, each with its own Dapr sidecar, plus infrastructure\/dependent services all these running in individual containers. Using a VS Code devcontainer with Docker-in-Docker support, enabling Java extensions, and installing Maven and OpenJDK. Creating a new GitHub Codespaces instance and finally employing VS Code to attach to a remote Java process for debugging purposes.<\/p>\n","protected":false},"author":118855,"featured_media":15157,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[15,1,17],"tags":[3481,3443,3480,3479,156,219,3384],"class_list":["post-15156","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-containers","category-cse","category-frameworks","tag-codespaces","tag-dapr","tag-debug","tag-devcontainer","tag-docker","tag-java","tag-vscode"],"acf":[],"blog_post_summary":"<p>To simplify the development and debugging of complex Java Dapr-enabled microservices we recommend leveraging Docker Compose to define all services including the Java Apps, each with its own Dapr sidecar, plus infrastructure\/dependent services all these running in individual containers. Using a VS Code devcontainer with Docker-in-Docker support, enabling Java extensions, and installing Maven and OpenJDK. Creating a new GitHub Codespaces instance and finally employing VS Code to attach to a remote Java process for debugging purposes.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/15156","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/users\/118855"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/comments?post=15156"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/posts\/15156\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media\/15157"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/media?parent=15156"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/categories?post=15156"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/ise\/wp-json\/wp\/v2\/tags?post=15156"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}