[{"data":1,"prerenderedAt":3702},["ShallowReactive",2],{"/blog/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes":3,"navigation-en-us":2946,"banner-en-us":3374,"footer-en-us":3384,"blog-post-authors-en-us-Marco Lenzo":3643,"blog-related-posts-en-us-continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes":3657,"blog-share-wrapper-en-us":3682,"next-steps-en-us":3692},{"id":4,"title":5,"authors":6,"body":8,"category":2917,"date":2918,"description":2919,"extension":2920,"externalUrl":2921,"faq":2921,"featured":2922,"heroImage":2923,"meta":2924,"navigation":316,"path":2936,"seo":2937,"slug":2942,"stem":2943,"tags":2921,"template":2944,"updatedDate":2921,"__hash__":2945},"blogPosts/en-us/blog/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes.md","Continuous delivery of a Spring Boot application with GitLab CI and Kubernetes",[7],"Marco Lenzo",{"type":9,"value":10,"toc":2905},"minimark",[11,20,29,49,54,81,90,94,108,193,200,204,263,270,276,301,561,570,638,651,667,702,706,715,727,746,749,754,761,822,863,866,901,905,911,1089,1127,1134,1137,1172,1176,1202,1519,1522,1527,1552,1581,1584,1611,1652,1655,1684,1698,1704,1757,1768,1779,1792,1847,1856,1898,1909,1915,1920,1963,1977,1986,1992,2000,2005,2023,2039,2045,2065,2068,2160,2176,2203,2208,2235,2296,2299,2412,2417,2420,2424,2432,2438,2702,2742,2771,2821,2827,2836,2842,2845,2851,2855,2884,2887,2891,2898,2902],[12,13,14,19],"p",{},[15,16,18],"a",{"href":17},"/topics/ci-cd/","Continuous integration, continuous deployment and continuous delivery"," are increasingly popular topics among modern development teams. Together they enable a team to build, test and deploy the source code at any commit. The main benefit of these approaches is the ability to release more quality code more frequently through the means of automated pipelines. The tough part is building such pipelines. There is a myriad of tools available which we would need to choose, learn, install, integrate, and maintain.",[12,21,22,23,28],{},"Recently, I literally fell in love with ",[15,24,27],{"href":25,"rel":26},"https://gitlab.com/",[],"GitLab","! It offers a fully featured ecosystem of tools which enable us to create an automated pipeline in minutes! From source control to issue tracking and CI, we find everything under one roof, fully integrated and ready to use.",[12,30,31,32,37,38,42,43,48],{},"In this tutorial, we will create a ",[15,33,36],{"href":34,"rel":35},"https://projects.spring.io/spring-boot/",[],"Spring Boot"," application built, tested, and deployed with ",[15,39,41],{"href":40},"/solutions/continuous-integration/","GitLab CI"," on a ",[15,44,47],{"href":45,"rel":46},"http://kubernetes.io/",[],"Kubernetes"," cluster.",[50,51,53],"h2",{"id":52},"what-are-spring-boot-and-kubernetes","What are Spring Boot and Kubernetes?",[12,55,56,57,62,63,68,69,74,75,80],{},"Spring Boot (sometimes called Java Spring Boot) is the leading ",[15,58,61],{"href":59,"rel":60},"http://microservices.io/patterns/microservice-chassis.html",[],"microservice chassis"," for Java. It allows a developer to build a production-grade stand-alone application, like a typical ",[15,64,67],{"href":65,"rel":66},"https://en.wikipedia.org/wiki/Create,_read,_update_and_delete",[],"CRUD"," application exposing a ",[15,70,73],{"href":71,"rel":72},"https://en.wikipedia.org/wiki/Representational_state_transfer",[],"RESTful API",", with minimal configuration, reducing the learning curve required for using the ",[15,76,79],{"href":77,"rel":78},"https://spring.io/",[],"Spring Framework"," drastically.",[12,82,83,84,89],{},"Kubernetes is an open-source container orchestrator inspired by ",[15,85,88],{"href":86,"rel":87},"http://static.googleusercontent.com/media/research.google.com/en//pubs/archive/43438.pdf",[],"Google Borg"," that schedules, scales and manages containerized applications.",[50,91,93],{"id":92},"create-a-gitlab-project","Create a GitLab project",[12,95,96,97,102,103,107],{},"Let's start by ",[15,98,101],{"href":99,"rel":100},"https://gitlab.com/projects/new",[],"creating a new project"," in GitLab named ",[104,105,106],"code",{},"actuator-sample",". Then we follow the command line instructions displayed in the project's home page to clone the repository on our machine and perform the first commit.",[109,110,115],"pre",{"className":111,"code":112,"language":113,"meta":114,"style":114},"language-shell shiki shiki-themes github-light","git clone git@gitlab.com:marcolenzo/actuator-sample.git\ncd actuator-sample\ntouch README.md\ngit add README.md\ngit commit -m \"add README\"\ngit push -u origin master\n","shell","",[104,116,117,133,143,152,162,176],{"__ignoreMap":114},[118,119,122,126,130],"span",{"class":120,"line":121},"line",1,[118,123,125],{"class":124},"s7eDp","git",[118,127,129],{"class":128},"sYBdl"," clone",[118,131,132],{"class":128}," git@gitlab.com:marcolenzo/actuator-sample.git\n",[118,134,136,140],{"class":120,"line":135},2,[118,137,139],{"class":138},"sYu0t","cd",[118,141,142],{"class":128}," actuator-sample\n",[118,144,146,149],{"class":120,"line":145},3,[118,147,148],{"class":124},"touch",[118,150,151],{"class":128}," README.md\n",[118,153,155,157,160],{"class":120,"line":154},4,[118,156,125],{"class":124},[118,158,159],{"class":128}," add",[118,161,151],{"class":128},[118,163,165,167,170,173],{"class":120,"line":164},5,[118,166,125],{"class":124},[118,168,169],{"class":128}," commit",[118,171,172],{"class":138}," -m",[118,174,175],{"class":128}," \"add README\"\n",[118,177,179,181,184,187,190],{"class":120,"line":178},6,[118,180,125],{"class":124},[118,182,183],{"class":128}," push",[118,185,186],{"class":138}," -u",[118,188,189],{"class":128}," origin",[118,191,192],{"class":128}," master\n",[12,194,195,196,199],{},"Always replace ",[104,197,198],{},"marcolenzo"," with your own GitLab username whenever copying a snippet of code from this tutorial.",[50,201,203],{"id":202},"create-a-spring-boot-application","Create a Spring Boot application",[12,205,206,207,212,213,217,218,221,222,227,228,231,232,235,236,238,239,242,243,246,247,252,253,258,259,262],{},"To bootstrap the Spring Boot application we navigate to the ",[15,208,211],{"href":209,"rel":210},"https://start.spring.io",[],"Spring Initializr"," web page and generate a ",[214,215,216],"strong",{},"Maven Project"," with the pre-selected Spring Boot ",[214,219,220],{},"Version",". ",[15,223,226],{"href":224,"rel":225},"https://maven.apache.org/index.html",[],"Maven"," is a project management tool commonly used in Java projects to define dependencies and the build lifecycle. We leave ",[104,229,230],{},"com.example"," as ",[214,233,234],{},"Group"," and set ",[104,237,106],{}," as the ",[214,240,241],{},"Artifact"," name. We select the ",[104,244,245],{},"Web"," dependency, which supports full stack web development with ",[15,248,251],{"href":249,"rel":250},"http://tomcat.apache.org/",[],"Tomcat"," and ",[15,254,257],{"href":255,"rel":256},"http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html",[],"Spring MVC",", and the ",[104,260,261],{},"Actuator"," dependency which implements some production-grade features useful for monitoring and managing our application like health-checks and HTTP requests traces.",[12,264,265,266,269],{},"Finally, we generate the project and a Zip file named ",[104,267,268],{},"actuator-sample.zip"," will be downloaded to our machine.",[12,271,272],{},[273,274],"img",{"alt":211,"src":275},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398115/blog/Content%20Images/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/initializr.png",[12,277,278,279,284,285,288,289,294,295,300],{},"We can now unzip the archive and launch the application immediately. Spring Initializr has already created everything for us. We just need to have a ",[15,280,283],{"href":281,"rel":282},"http://openjdk.java.net/install/",[],"Java JDK"," 1.7 or later installed on our machine and the ",[104,286,287],{},"JAVA_HOME"," environment variable set accordingly. ",[15,290,293],{"href":291,"rel":292},"http://openjdk.java.net/",[],"OpenJDK"," is the preferred option for most Linux distributions since it is readily available on their repositories. You can alternatively install ",[15,296,299],{"href":297,"rel":298},"http://www.oracle.com/technetwork/java/javase/downloads/index.html",[],"Oracle JDK"," if it is a strict requirement for your team.",[109,302,304],{"className":111,"code":303,"language":113,"meta":114,"style":114},"### Installing OpenJDK 8 on Debian, Ubuntu, etc.\n\nsudo apt-get install openjdk-8-jre\n\n### Installing OpenJDK 8 on Fedora, Oracle Linux, Red Hat Enteprise, CentOS, etc.\n\nsu -c \"yum install java-1.8.0-openjdk\"\n\n### Setting the JAVA_HOME environment variable\n\nexport JAVA_HOME=/path/to/your/java/home # e.g. /usr/lib/jvm/java-8-openjdk-amd64/\n\n### Extracting and launching the application\n\n~/git/actuator-sample$ unzip ~/Downloads/actuator-sample.zip -d ../\n~/git/actuator-sample$ ./mvnw spring-boot:run\n\n[...]\n\n2016-12-02 22:41:14.376  INFO 10882 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)\n2016-12-02 22:41:14.420  INFO 10882 --- [           main] com.example.ActuatorSampleApplication    : Started ActuatorSampleApplication in 17.924 seconds (JVM running for 87.495)\n",[104,305,306,312,318,332,336,341,345,357,362,368,373,393,398,404,409,423,431,436,442,447,507],{"__ignoreMap":114},[118,307,308],{"class":120,"line":121},[118,309,311],{"class":310},"sAwPA","### Installing OpenJDK 8 on Debian, Ubuntu, etc.\n",[118,313,314],{"class":120,"line":135},[118,315,317],{"emptyLinePlaceholder":316},true,"\n",[118,319,320,323,326,329],{"class":120,"line":145},[118,321,322],{"class":124},"sudo",[118,324,325],{"class":128}," apt-get",[118,327,328],{"class":128}," install",[118,330,331],{"class":128}," openjdk-8-jre\n",[118,333,334],{"class":120,"line":154},[118,335,317],{"emptyLinePlaceholder":316},[118,337,338],{"class":120,"line":164},[118,339,340],{"class":310},"### Installing OpenJDK 8 on Fedora, Oracle Linux, Red Hat Enteprise, CentOS, etc.\n",[118,342,343],{"class":120,"line":178},[118,344,317],{"emptyLinePlaceholder":316},[118,346,348,351,354],{"class":120,"line":347},7,[118,349,350],{"class":124},"su",[118,352,353],{"class":138}," -c",[118,355,356],{"class":128}," \"yum install java-1.8.0-openjdk\"\n",[118,358,360],{"class":120,"line":359},8,[118,361,317],{"emptyLinePlaceholder":316},[118,363,365],{"class":120,"line":364},9,[118,366,367],{"class":310},"### Setting the JAVA_HOME environment variable\n",[118,369,371],{"class":120,"line":370},10,[118,372,317],{"emptyLinePlaceholder":316},[118,374,376,380,384,387,390],{"class":120,"line":375},11,[118,377,379],{"class":378},"sD7c4","export",[118,381,383],{"class":382},"sgsFI"," JAVA_HOME",[118,385,386],{"class":378},"=",[118,388,389],{"class":382},"/path/to/your/java/home ",[118,391,392],{"class":310},"# e.g. /usr/lib/jvm/java-8-openjdk-amd64/\n",[118,394,396],{"class":120,"line":395},12,[118,397,317],{"emptyLinePlaceholder":316},[118,399,401],{"class":120,"line":400},13,[118,402,403],{"class":310},"### Extracting and launching the application\n",[118,405,407],{"class":120,"line":406},14,[118,408,317],{"emptyLinePlaceholder":316},[118,410,412,415,418,420],{"class":120,"line":411},15,[118,413,414],{"class":378},"~",[118,416,417],{"class":382},"/git/actuator-sample$ unzip ",[118,419,414],{"class":378},[118,421,422],{"class":382},"/Downloads/actuator-sample.zip -d ../\n",[118,424,426,428],{"class":120,"line":425},16,[118,427,414],{"class":378},[118,429,430],{"class":382},"/git/actuator-sample$ ./mvnw spring-boot:run\n",[118,432,434],{"class":120,"line":433},17,[118,435,317],{"emptyLinePlaceholder":316},[118,437,439],{"class":120,"line":438},18,[118,440,441],{"class":382},"[...]\n",[118,443,445],{"class":120,"line":444},19,[118,446,317],{"emptyLinePlaceholder":316},[118,448,450,453,456,459,462,465,468,471,474,477,480,483,486,489,492,495,498,501,504],{"class":120,"line":449},20,[118,451,452],{"class":124},"2016-12-02",[118,454,455],{"class":128}," 22:41:14.376",[118,457,458],{"class":128},"  INFO",[118,460,461],{"class":138}," 10882",[118,463,464],{"class":138}," ---",[118,466,467],{"class":382}," [           ",[118,469,470],{"class":128},"main]",[118,472,473],{"class":128}," s.b.c.e.t.TomcatEmbeddedServletContainer",[118,475,476],{"class":128}," :",[118,478,479],{"class":128}," Tomcat",[118,481,482],{"class":128}," started",[118,484,485],{"class":128}," on",[118,487,488],{"class":128}," port",[118,490,491],{"class":382},"(",[118,493,494],{"class":124},"s",[118,496,497],{"class":382},")",[118,499,500],{"class":128},":",[118,502,503],{"class":138}," 8080",[118,505,506],{"class":382}," (http)\n",[118,508,510,512,515,517,519,521,523,525,528,531,534,537,540,543,546,549,552,555,558],{"class":120,"line":509},21,[118,511,452],{"class":124},[118,513,514],{"class":128}," 22:41:14.420",[118,516,458],{"class":128},[118,518,461],{"class":138},[118,520,464],{"class":138},[118,522,467],{"class":382},[118,524,470],{"class":128},[118,526,527],{"class":128}," com.example.ActuatorSampleApplication",[118,529,530],{"class":128},"    :",[118,532,533],{"class":128}," Started",[118,535,536],{"class":128}," ActuatorSampleApplication",[118,538,539],{"class":128}," in",[118,541,542],{"class":138}," 17.924",[118,544,545],{"class":128}," seconds",[118,547,548],{"class":382}," (JVM ",[118,550,551],{"class":128},"running",[118,553,554],{"class":128}," for",[118,556,557],{"class":138}," 87.495",[118,559,560],{"class":382},")\n",[12,562,563,564,569],{},"The application is up and running and we did not write one line of code! Spring Boot is opinionated and auto-configures the application with sane default values and beans. It also scans the classpath for known dependencies and initializes them. In our case, we immediately enjoy all the production-grade services offered by ",[15,565,568],{"href":566,"rel":567},"http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-endpoints.html",[],"Spring Actuator",".",[109,571,573],{"className":111,"code":572,"language":113,"meta":114,"style":114},"~$ curl http://localhost:8080/health\n{\"status\":\"UP\",\"diskSpace\":{\"status\":\"UP\",\"total\":981190307840,\"free\":744776503296,\"threshold\":10485760}}\n",[104,574,575,582],{"__ignoreMap":114},[118,576,577,579],{"class":120,"line":121},[118,578,414],{"class":378},[118,580,581],{"class":382},"$ curl http://localhost:8080/health\n",[118,583,584,587,590,592,595,598,601,603,605,608,611,614,617,620,623,626,629,632,635],{"class":120,"line":135},[118,585,586],{"class":382},"{",[118,588,589],{"class":124},"\"status\"",[118,591,500],{"class":138},[118,593,594],{"class":124},"\"UP\"",[118,596,597],{"class":124},",",[118,599,600],{"class":124},"\"diskSpace\"",[118,602,500],{"class":138},[118,604,586],{"class":128},[118,606,607],{"class":382},"\"",[118,609,610],{"class":124},"status",[118,612,613],{"class":124},"\":\"",[118,615,616],{"class":124},"UP",[118,618,619],{"class":124},"\",\"",[118,621,622],{"class":124},"total",[118,624,625],{"class":124},"\":981190307840,\"",[118,627,628],{"class":124},"free",[118,630,631],{"class":124},"\":744776503296,\"",[118,633,634],{"class":124},"threshold",[118,636,637],{"class":124},"\":10485760}}\n",[12,639,640,641,252,646,569],{},"If you wish to learn Spring Boot in greater detail, have a look at their ",[15,642,645],{"href":643,"rel":644},"http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/",[],"reference documentation",[15,647,650],{"href":648,"rel":649},"https://spring.io/guides",[],"guides",[12,652,653,654,657,658,661,662,666],{},"It is time to commit our changes and push them to ",[104,655,656],{},"origin",". To simplify things a bit, we commit directly on ",[104,659,660],{},"master"," without using feature branches since collaboration is not the focus of this tutorial. Later, we will use environment branches as specified in the GitLab Flow to deploy to different environments selectively, e.g. staging and production. If you are not familiar with the ",[15,663,665],{"href":664},"/topics/version-control/what-are-gitlab-flow-best-practices/","GitLab Flow",", I strongly recommend you to read its documentation.",[109,668,670],{"className":111,"code":669,"language":113,"meta":114,"style":114},"git add --all\ngit commit -m \"Creates actuator-example application\"\ngit push origin master\n",[104,671,672,681,692],{"__ignoreMap":114},[118,673,674,676,678],{"class":120,"line":121},[118,675,125],{"class":124},[118,677,159],{"class":128},[118,679,680],{"class":138}," --all\n",[118,682,683,685,687,689],{"class":120,"line":135},[118,684,125],{"class":124},[118,686,169],{"class":128},[118,688,172],{"class":138},[118,690,691],{"class":128}," \"Creates actuator-example application\"\n",[118,693,694,696,698,700],{"class":120,"line":145},[118,695,125],{"class":124},[118,697,183],{"class":128},[118,699,189],{"class":128},[118,701,192],{"class":128},[50,703,705],{"id":704},"creating-a-continuous-delivery-pipeline-with-gitlab-ci","Creating a continuous delivery pipeline with GitLab CI",[12,707,708,709,714],{},"While our code is now safe on GitLab, we still need to automate its integration and deployment. We need to verify each commit with an automated build and set of tests in order to discover issues as early as possible and, if the build is successful, deploy to a target environment. A few years ago, our only option was to install, configure and maintain a CI Server like ",[15,710,713],{"href":711,"rel":712},"https://jenkins.io/",[],"Jenkins"," and possibly automate our deployment with a set of bash scripts. While the number of options has grown significantly, whether hosted or on the cloud, we still need to find a way to integrate our source control system with the CI Server of our choice.",[12,716,717,718,721,722,726],{},"Not anymore though! GitLab has ",[15,719,720],{"href":17},"fully integrated CI and CD Pipelines"," in its offering, allowing us to ",[15,723,725],{"href":724},"/topics/version-control/what-is-gitlab-flow/","build, test and deploy"," our code with ease.",[12,728,729,730,735,736,739,740,745],{},"For the purpose of this tutorial we will deploy to the ",[15,731,734],{"href":732,"rel":733},"https://cloud.google.com/container-engine/",[],"Google Cloud Container Engine"," which is a cluster management and orchestration system built on the open source ",[15,737,47],{"href":45,"rel":738},[],". Kubernetes is supported by all main cloud providers and can be ",[15,741,744],{"href":742,"rel":743},"http://kubernetes.io/docs/getting-started-guides/kubeadm/",[],"easily installed on any Linux server"," in minutes. That said, we will be able to re-use this configuration virtually on any environment running Kubernetes.",[12,747,748],{},"Before we can proceed to the creation of the pipeline, we need to add a couple of files to our repository to package our application as a Docker container and to describe the target deployment in Kubernetes terms.",[750,751,753],"h3",{"id":752},"packaging-a-spring-boot-application-as-a-docker-container","Packaging a Spring Boot application as a Docker container",[12,755,756,757,760],{},"Let's start by creating the ",[104,758,759],{},"Dockerfile"," in the root directory of our project.",[109,762,764],{"className":111,"code":763,"language":113,"meta":114,"style":114},"FROM openjdk:8u111-jdk-alpine\nVOLUME /tmp\nADD /target/actuator-sample-0.0.1-SNAPSHOT.jar app.jar\nENTRYPOINT [\"java\",\"-Djava.security.egd=file:/dev/./urandom\",\"-jar\",\"/app.jar\"]\n",[104,765,766,774,782,793],{"__ignoreMap":114},[118,767,768,771],{"class":120,"line":121},[118,769,770],{"class":124},"FROM",[118,772,773],{"class":128}," openjdk:8u111-jdk-alpine\n",[118,775,776,779],{"class":120,"line":135},[118,777,778],{"class":124},"VOLUME",[118,780,781],{"class":128}," /tmp\n",[118,783,784,787,790],{"class":120,"line":145},[118,785,786],{"class":124},"ADD",[118,788,789],{"class":128}," /target/actuator-sample-0.0.1-SNAPSHOT.jar",[118,791,792],{"class":128}," app.jar\n",[118,794,795,798,801,804,806,809,811,814,816,819],{"class":120,"line":154},[118,796,797],{"class":124},"ENTRYPOINT",[118,799,800],{"class":382}," [",[118,802,803],{"class":128},"\"java\"",[118,805,597],{"class":382},[118,807,808],{"class":128},"\"-Djava.security.egd=file:/dev/./urandom\"",[118,810,597],{"class":382},[118,812,813],{"class":128},"\"-jar\"",[118,815,597],{"class":382},[118,817,818],{"class":128},"\"/app.jar\"",[118,820,821],{"class":382},"]\n",[12,823,824,825,827,828,831,832,837,838,840,841,843,844,846,847,850,851,854,855,858,859,862],{},"The ",[104,826,770],{}," keyword defines the base Docker image of our container. We chose ",[15,829,293],{"href":291,"rel":830},[]," installed on ",[15,833,836],{"href":834,"rel":835},"https://alpinelinux.org/",[],"Alpine Linux"," which is a lightweight Linux distribution. The ",[104,839,778],{}," instruction creates a mount point with the specified name and marks it as holding externally mounted volumes from the native host or other containers. ",[104,842,786],{}," copies the executable JAR generated during the build to the container root directory. Finally ",[104,845,797],{}," defines the command to execute when the container is started. Since Spring Boot produces an executable JAR with embedded Tomcat, the command to execute is simply ",[104,848,849],{},"java -jar app.jar",". The additional flag ",[104,852,853],{},"java.security.edg=file:/dev/./urandom"," is used to speed up the application start-up and avoid possible freezes. By default, Java uses ",[104,856,857],{},"/dev/random"," to seed its ",[104,860,861],{},"SecureRandom"," class which is known to block if its entropy pool is empty.",[12,864,865],{},"Time to commit.",[109,867,869],{"className":111,"code":868,"language":113,"meta":114,"style":114},"git add Dockerfile\ngit commit -m \"Adds Dockerfile\"\ngit push origin master\n",[104,870,871,880,891],{"__ignoreMap":114},[118,872,873,875,877],{"class":120,"line":121},[118,874,125],{"class":124},[118,876,159],{"class":128},[118,878,879],{"class":128}," Dockerfile\n",[118,881,882,884,886,888],{"class":120,"line":135},[118,883,125],{"class":124},[118,885,169],{"class":128},[118,887,172],{"class":138},[118,889,890],{"class":128}," \"Adds Dockerfile\"\n",[118,892,893,895,897,899],{"class":120,"line":145},[118,894,125],{"class":124},[118,896,183],{"class":128},[118,898,189],{"class":128},[118,900,192],{"class":128},[750,902,904],{"id":903},"define-the-kubernetes-deployment","Define the Kubernetes deployment",[12,906,907,908,760],{},"Let's create a file named ",[104,909,910],{},"deployment.yml",[109,912,916],{"className":913,"code":914,"language":915,"meta":114,"style":114},"language-yml shiki shiki-themes github-light","apiVersion: extensions/v1beta1\nkind: Deployment\nmetadata:\n  name: actuator-sample\nspec:\n  replicas: 2\n  template:\n    metadata:\n      labels:\n        app: actuator-sample\n    spec:\n      containers:\n      - name: actuator-sample\n        image: registry.gitlab.com/marcolenzo/actuator-sample\n        imagePullPolicy: Always\n        ports:\n        - containerPort: 8080\n      imagePullSecrets:\n        - name: registry.gitlab.com\n\n","yml",[104,917,918,930,940,948,958,965,975,982,989,996,1005,1012,1019,1031,1041,1051,1058,1071,1078],{"__ignoreMap":114},[118,919,920,924,927],{"class":120,"line":121},[118,921,923],{"class":922},"shJU0","apiVersion",[118,925,926],{"class":382},": ",[118,928,929],{"class":128},"extensions/v1beta1\n",[118,931,932,935,937],{"class":120,"line":135},[118,933,934],{"class":922},"kind",[118,936,926],{"class":382},[118,938,939],{"class":128},"Deployment\n",[118,941,942,945],{"class":120,"line":145},[118,943,944],{"class":922},"metadata",[118,946,947],{"class":382},":\n",[118,949,950,953,955],{"class":120,"line":154},[118,951,952],{"class":922},"  name",[118,954,926],{"class":382},[118,956,957],{"class":128},"actuator-sample\n",[118,959,960,963],{"class":120,"line":164},[118,961,962],{"class":922},"spec",[118,964,947],{"class":382},[118,966,967,970,972],{"class":120,"line":178},[118,968,969],{"class":922},"  replicas",[118,971,926],{"class":382},[118,973,974],{"class":138},"2\n",[118,976,977,980],{"class":120,"line":347},[118,978,979],{"class":922},"  template",[118,981,947],{"class":382},[118,983,984,987],{"class":120,"line":359},[118,985,986],{"class":922},"    metadata",[118,988,947],{"class":382},[118,990,991,994],{"class":120,"line":364},[118,992,993],{"class":922},"      labels",[118,995,947],{"class":382},[118,997,998,1001,1003],{"class":120,"line":370},[118,999,1000],{"class":922},"        app",[118,1002,926],{"class":382},[118,1004,957],{"class":128},[118,1006,1007,1010],{"class":120,"line":375},[118,1008,1009],{"class":922},"    spec",[118,1011,947],{"class":382},[118,1013,1014,1017],{"class":120,"line":395},[118,1015,1016],{"class":922},"      containers",[118,1018,947],{"class":382},[118,1020,1021,1024,1027,1029],{"class":120,"line":400},[118,1022,1023],{"class":382},"      - ",[118,1025,1026],{"class":922},"name",[118,1028,926],{"class":382},[118,1030,957],{"class":128},[118,1032,1033,1036,1038],{"class":120,"line":406},[118,1034,1035],{"class":922},"        image",[118,1037,926],{"class":382},[118,1039,1040],{"class":128},"registry.gitlab.com/marcolenzo/actuator-sample\n",[118,1042,1043,1046,1048],{"class":120,"line":411},[118,1044,1045],{"class":922},"        imagePullPolicy",[118,1047,926],{"class":382},[118,1049,1050],{"class":128},"Always\n",[118,1052,1053,1056],{"class":120,"line":425},[118,1054,1055],{"class":922},"        ports",[118,1057,947],{"class":382},[118,1059,1060,1063,1066,1068],{"class":120,"line":433},[118,1061,1062],{"class":382},"        - ",[118,1064,1065],{"class":922},"containerPort",[118,1067,926],{"class":382},[118,1069,1070],{"class":138},"8080\n",[118,1072,1073,1076],{"class":120,"line":438},[118,1074,1075],{"class":922},"      imagePullSecrets",[118,1077,947],{"class":382},[118,1079,1080,1082,1084,1086],{"class":120,"line":444},[118,1081,1062],{"class":382},[118,1083,1026],{"class":922},[118,1085,926],{"class":382},[118,1087,1088],{"class":128},"registry.gitlab.com\n",[12,1090,1091,1092,1099,1100,1102,1103,1106,1107,1114,1115,1117,1118,1122,1123,1126],{},"This is the definition of a Kubernetes ",[15,1093,1096],{"href":1094,"rel":1095},"http://kubernetes.io/docs/user-guide/deployments/",[],[104,1097,1098],{},"Deployment"," named ",[104,1101,106],{},". The ",[104,1104,1105],{},"replicas"," element defines the target number of ",[15,1108,1111],{"href":1109,"rel":1110},"http://kubernetes.io/docs/user-guide/pods/",[],[104,1112,1113],{},"Pods",". Kubernetes performs automated binpacking and self-healing of the system to comply with the deployment specifications while achieving optimal utilization of compute resources. A Pod can be composed of multiple containers. In this scenario, we only include the ",[104,1116,106],{}," image stored on our private ",[15,1119,1121],{"href":1120},"/blog/gitlab-container-registry/","GitLab Container Registry",". For this reason, we need to set an entry under the ",[104,1124,1125],{},"imagePullSecrets"," which is used to authenticate to the GitLab Container Registry.",[12,1128,1129,1130,569],{},"For a detailed explanation of Kubernetes resources and concepts refer to the ",[15,1131,1133],{"href":45,"rel":1132},[],"official documentation",[12,1135,1136],{},"Time to commit again and we are ready to define our GitLab CI pipeline.",[109,1138,1140],{"className":111,"code":1139,"language":113,"meta":114,"style":114},"git add deployment.yml\ngit commit -m \"Adds Kubernetes Deployment definition\"\ngit push origin master\n",[104,1141,1142,1151,1162],{"__ignoreMap":114},[118,1143,1144,1146,1148],{"class":120,"line":121},[118,1145,125],{"class":124},[118,1147,159],{"class":128},[118,1149,1150],{"class":128}," deployment.yml\n",[118,1152,1153,1155,1157,1159],{"class":120,"line":135},[118,1154,125],{"class":124},[118,1156,169],{"class":128},[118,1158,172],{"class":138},[118,1160,1161],{"class":128}," \"Adds Kubernetes Deployment definition\"\n",[118,1163,1164,1166,1168,1170],{"class":120,"line":145},[118,1165,125],{"class":124},[118,1167,183],{"class":128},[118,1169,189],{"class":128},[118,1171,192],{"class":128},[750,1173,1175],{"id":1174},"creating-the-gitlab-ci-pipeline","Creating the GitLab CI pipeline",[12,1177,1178,1179,1181,1182,1189,1190,1195,1196,1201],{},"In order to make use of ",[15,1180,41],{"href":40}," we need to add the ",[15,1183,1186],{"href":1184,"rel":1185},"https://docs.gitlab.com/ci/yaml/",[],[104,1187,1188],{},".gitlab-ci.yml"," configuration file to the root directory of our repository. This file is used by ",[15,1191,1194],{"href":1192,"rel":1193},"https://docs.gitlab.com/ci/runners/",[],"GitLab Runners"," to manage our project's builds and deployments. Therein we can define an unlimited number of ",[15,1197,1200],{"href":1198,"rel":1199},"https://docs.gitlab.com/ci/jobs/",[],"Jobs"," and their role in the whole build lifecycle.",[109,1203,1205],{"className":913,"code":1204,"language":915,"meta":114,"style":114},"image: docker:latest\nservices:\n  - docker:dind\n\nvariables:\n  DOCKER_DRIVER: overlay\n  SPRING_PROFILES_ACTIVE: gitlab-ci\n\nstages:\n  - build\n  - package\n  - deploy\n\nmaven-build:\n  image: maven:3-jdk-8\n  stage: build\n  script: \"mvn package -B\"\n  artifacts:\n    paths:\n      - target/*.jar\n\ndocker-build:\n  stage: package\n  script:\n  - docker build -t registry.gitlab.com/marcolenzo/actuator-sample .\n  - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.gitlab.com\n  - docker push registry.gitlab.com/marcolenzo/actuator-sample\n\nk8s-deploy:\n  image: google/cloud-sdk\n  stage: deploy\n  script:\n  - echo \"$GOOGLE_KEY\" > key.json\n  - gcloud auth activate-service-account --key-file key.json\n  - gcloud config set compute/zone europe-west1-c\n  - gcloud config set project actuator-sample\n  - gcloud config set container/use_client_certificate True\n  - gcloud container clusters get-credentials actuator-sample\n  - kubectl delete secret registry.gitlab.com\n  - kubectl create secret docker-registry registry.gitlab.com --docker-server=https://registry.gitlab.com --docker-username=marcolenzo --docker-password=$REGISTRY_PASSWD --docker-email=lenzo.marco@gmail.com\n  - kubectl apply -f deployment.yml\n\n",[104,1206,1207,1217,1224,1232,1236,1243,1253,1263,1267,1274,1281,1288,1295,1299,1306,1316,1325,1335,1342,1349,1356,1360,1368,1377,1384,1392,1400,1408,1413,1421,1431,1440,1447,1455,1463,1471,1479,1487,1495,1503,1511],{"__ignoreMap":114},[118,1208,1209,1212,1214],{"class":120,"line":121},[118,1210,1211],{"class":922},"image",[118,1213,926],{"class":382},[118,1215,1216],{"class":128},"docker:latest\n",[118,1218,1219,1222],{"class":120,"line":135},[118,1220,1221],{"class":922},"services",[118,1223,947],{"class":382},[118,1225,1226,1229],{"class":120,"line":145},[118,1227,1228],{"class":382},"  - ",[118,1230,1231],{"class":128},"docker:dind\n",[118,1233,1234],{"class":120,"line":154},[118,1235,317],{"emptyLinePlaceholder":316},[118,1237,1238,1241],{"class":120,"line":164},[118,1239,1240],{"class":922},"variables",[118,1242,947],{"class":382},[118,1244,1245,1248,1250],{"class":120,"line":178},[118,1246,1247],{"class":922},"  DOCKER_DRIVER",[118,1249,926],{"class":382},[118,1251,1252],{"class":128},"overlay\n",[118,1254,1255,1258,1260],{"class":120,"line":347},[118,1256,1257],{"class":922},"  SPRING_PROFILES_ACTIVE",[118,1259,926],{"class":382},[118,1261,1262],{"class":128},"gitlab-ci\n",[118,1264,1265],{"class":120,"line":359},[118,1266,317],{"emptyLinePlaceholder":316},[118,1268,1269,1272],{"class":120,"line":364},[118,1270,1271],{"class":922},"stages",[118,1273,947],{"class":382},[118,1275,1276,1278],{"class":120,"line":370},[118,1277,1228],{"class":382},[118,1279,1280],{"class":128},"build\n",[118,1282,1283,1285],{"class":120,"line":375},[118,1284,1228],{"class":382},[118,1286,1287],{"class":128},"package\n",[118,1289,1290,1292],{"class":120,"line":395},[118,1291,1228],{"class":382},[118,1293,1294],{"class":128},"deploy\n",[118,1296,1297],{"class":120,"line":400},[118,1298,317],{"emptyLinePlaceholder":316},[118,1300,1301,1304],{"class":120,"line":406},[118,1302,1303],{"class":922},"maven-build",[118,1305,947],{"class":382},[118,1307,1308,1311,1313],{"class":120,"line":411},[118,1309,1310],{"class":922},"  image",[118,1312,926],{"class":382},[118,1314,1315],{"class":128},"maven:3-jdk-8\n",[118,1317,1318,1321,1323],{"class":120,"line":425},[118,1319,1320],{"class":922},"  stage",[118,1322,926],{"class":382},[118,1324,1280],{"class":128},[118,1326,1327,1330,1332],{"class":120,"line":433},[118,1328,1329],{"class":922},"  script",[118,1331,926],{"class":382},[118,1333,1334],{"class":128},"\"mvn package -B\"\n",[118,1336,1337,1340],{"class":120,"line":438},[118,1338,1339],{"class":922},"  artifacts",[118,1341,947],{"class":382},[118,1343,1344,1347],{"class":120,"line":444},[118,1345,1346],{"class":922},"    paths",[118,1348,947],{"class":382},[118,1350,1351,1353],{"class":120,"line":449},[118,1352,1023],{"class":382},[118,1354,1355],{"class":128},"target/*.jar\n",[118,1357,1358],{"class":120,"line":509},[118,1359,317],{"emptyLinePlaceholder":316},[118,1361,1363,1366],{"class":120,"line":1362},22,[118,1364,1365],{"class":922},"docker-build",[118,1367,947],{"class":382},[118,1369,1371,1373,1375],{"class":120,"line":1370},23,[118,1372,1320],{"class":922},[118,1374,926],{"class":382},[118,1376,1287],{"class":128},[118,1378,1380,1382],{"class":120,"line":1379},24,[118,1381,1329],{"class":922},[118,1383,947],{"class":382},[118,1385,1387,1389],{"class":120,"line":1386},25,[118,1388,1228],{"class":382},[118,1390,1391],{"class":128},"docker build -t registry.gitlab.com/marcolenzo/actuator-sample .\n",[118,1393,1395,1397],{"class":120,"line":1394},26,[118,1396,1228],{"class":382},[118,1398,1399],{"class":128},"docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.gitlab.com\n",[118,1401,1403,1405],{"class":120,"line":1402},27,[118,1404,1228],{"class":382},[118,1406,1407],{"class":128},"docker push registry.gitlab.com/marcolenzo/actuator-sample\n",[118,1409,1411],{"class":120,"line":1410},28,[118,1412,317],{"emptyLinePlaceholder":316},[118,1414,1416,1419],{"class":120,"line":1415},29,[118,1417,1418],{"class":922},"k8s-deploy",[118,1420,947],{"class":382},[118,1422,1424,1426,1428],{"class":120,"line":1423},30,[118,1425,1310],{"class":922},[118,1427,926],{"class":382},[118,1429,1430],{"class":128},"google/cloud-sdk\n",[118,1432,1434,1436,1438],{"class":120,"line":1433},31,[118,1435,1320],{"class":922},[118,1437,926],{"class":382},[118,1439,1294],{"class":128},[118,1441,1443,1445],{"class":120,"line":1442},32,[118,1444,1329],{"class":922},[118,1446,947],{"class":382},[118,1448,1450,1452],{"class":120,"line":1449},33,[118,1451,1228],{"class":382},[118,1453,1454],{"class":128},"echo \"$GOOGLE_KEY\" > key.json\n",[118,1456,1458,1460],{"class":120,"line":1457},34,[118,1459,1228],{"class":382},[118,1461,1462],{"class":128},"gcloud auth activate-service-account --key-file key.json\n",[118,1464,1466,1468],{"class":120,"line":1465},35,[118,1467,1228],{"class":382},[118,1469,1470],{"class":128},"gcloud config set compute/zone europe-west1-c\n",[118,1472,1474,1476],{"class":120,"line":1473},36,[118,1475,1228],{"class":382},[118,1477,1478],{"class":128},"gcloud config set project actuator-sample\n",[118,1480,1482,1484],{"class":120,"line":1481},37,[118,1483,1228],{"class":382},[118,1485,1486],{"class":128},"gcloud config set container/use_client_certificate True\n",[118,1488,1490,1492],{"class":120,"line":1489},38,[118,1491,1228],{"class":382},[118,1493,1494],{"class":128},"gcloud container clusters get-credentials actuator-sample\n",[118,1496,1498,1500],{"class":120,"line":1497},39,[118,1499,1228],{"class":382},[118,1501,1502],{"class":128},"kubectl delete secret registry.gitlab.com\n",[118,1504,1506,1508],{"class":120,"line":1505},40,[118,1507,1228],{"class":382},[118,1509,1510],{"class":128},"kubectl create secret docker-registry registry.gitlab.com --docker-server=https://registry.gitlab.com --docker-username=marcolenzo --docker-password=$REGISTRY_PASSWD --docker-email=lenzo.marco@gmail.com\n",[118,1512,1514,1516],{"class":120,"line":1513},41,[118,1515,1228],{"class":382},[118,1517,1518],{"class":128},"kubectl apply -f deployment.yml\n",[12,1520,1521],{},"Let's break the file in pieces to understand what is going on.",[1523,1524,1526],"h4",{"id":1525},"image-and-services","Image and Services",[109,1528,1530],{"className":913,"code":1529,"language":915,"meta":114,"style":114},"image: docker:latest\nservices:\n  - docker:dind\n\n",[104,1531,1532,1540,1546],{"__ignoreMap":114},[118,1533,1534,1536,1538],{"class":120,"line":121},[118,1535,1211],{"class":922},[118,1537,926],{"class":382},[118,1539,1216],{"class":128},[118,1541,1542,1544],{"class":120,"line":135},[118,1543,1221],{"class":922},[118,1545,947],{"class":382},[118,1547,1548,1550],{"class":120,"line":145},[118,1549,1228],{"class":382},[118,1551,1231],{"class":128},[12,1553,824,1554,1558,1559,1564,1565,1572,1573,1102,1578,1580],{},[15,1555,1557],{"href":1192,"rel":1556},[],"GitLab Runner"," can ",[15,1560,1563],{"href":1561,"rel":1562},"https://docs.gitlab.com/ci/docker/using_docker_images/",[],"use Docker images"," to support our pipelines. The ",[15,1566,1569,1571],{"href":1567,"rel":1568},"https://docs.gitlab.com/ci/yaml/#image",[],[104,1570,1211],{}," element"," defines the name of the Docker image we want to use. Valid images are those hosted in the local Docker Engine or on ",[15,1574,1577],{"href":1575,"rel":1576},"https://hub.docker.com/",[],"Docker Hub",[104,1579,1221],{}," element defines additional Docker images which are linked to the main container. In our case the main container is a plain Docker image while the linked container is enabled for running Docker in Docker.",[1523,1582,1583],{"id":1240},"Variables",[109,1585,1587],{"className":913,"code":1586,"language":915,"meta":114,"style":114},"variables:\n  DOCKER_DRIVER: overlay\n  SPRING_PROFILES_ACTIVE: gitlab-ci\n\n",[104,1588,1589,1595,1603],{"__ignoreMap":114},[118,1590,1591,1593],{"class":120,"line":121},[118,1592,1240],{"class":922},[118,1594,947],{"class":382},[118,1596,1597,1599,1601],{"class":120,"line":135},[118,1598,1247],{"class":922},[118,1600,926],{"class":382},[118,1602,1252],{"class":128},[118,1604,1605,1607,1609],{"class":120,"line":145},[118,1606,1257],{"class":922},[118,1608,926],{"class":382},[118,1610,1262],{"class":128},[12,1612,1613,1614,1620,1621,1624,1625,1628,1629,1102,1634,1637,1638,1643,1644,1647,1648,1651],{},"This is the definition of ",[15,1615,1618],{"href":1616,"rel":1617},"https://docs.gitlab.com/ci/yaml/#variables",[],[104,1619,1240],{}," to be set on our build environment. The ",[104,1622,1623],{},"DOCKER_DRIVER"," signals the Docker Engine which storage driver to use. We use ",[104,1626,1627],{},"overlay"," for ",[15,1630,1633],{"href":1631,"rel":1632},"https://docs.gitlab.com/ci/docker/using_docker_build/#using-the-overlayfs-driver",[],"performance reasons",[104,1635,1636],{},"SPRING_PROFILES_ACTIVE"," is very useful when dealing with Spring Boot applications. It activates ",[15,1639,1642],{"href":1640,"rel":1641},"http://docs.spring.io/autorepo/docs/spring-boot/current/reference/html/boot-features-profiles.html",[],"Spring Profiles",", which provide a way to segregate parts of our application configuration and make it available only in certain environments. For instance, we can define different database URIs per environment, e.g. ",[104,1645,1646],{},"localhost"," when running on the developer machine and ",[104,1649,1650],{},"mongo"," when running within GitLab CI.",[1523,1653,1654],{"id":1271},"Stages",[109,1656,1658],{"className":913,"code":1657,"language":915,"meta":114,"style":114},"stages:\n  - build\n  - package\n  - deploy\n\n",[104,1659,1660,1666,1672,1678],{"__ignoreMap":114},[118,1661,1662,1664],{"class":120,"line":121},[118,1663,1271],{"class":922},[118,1665,947],{"class":382},[118,1667,1668,1670],{"class":120,"line":135},[118,1669,1228],{"class":382},[118,1671,1280],{"class":128},[118,1673,1674,1676],{"class":120,"line":145},[118,1675,1228],{"class":382},[118,1677,1287],{"class":128},[118,1679,1680,1682],{"class":120,"line":154},[118,1681,1228],{"class":382},[118,1683,1294],{"class":128},[12,1685,824,1686,1692,1693,1697],{},[15,1687,1690,1571],{"href":1688,"rel":1689},"https://docs.gitlab.com/ci/yaml/#stages",[],[104,1691,1271],{}," defines the lifecycle of our build. We associate each ",[15,1694,1696],{"href":1198,"rel":1695},[],"job"," with one stage. All jobs within a stage are run in parallel and stages are triggered sequentially in the order we define them, i.e. the next stage is initiated only when the previous one is complete.",[1523,1699,824,1701,1703],{"id":1700},"the-maven-build-job",[104,1702,1303],{}," job",[109,1705,1707],{"className":913,"code":1706,"language":915,"meta":114,"style":114},"maven-build:\n  image: maven:3-jdk-8\n  stage: build\n  script: \"mvn package -B\"\n  artifacts:\n    paths:\n      - target/*.jar\n\n",[104,1708,1709,1715,1723,1731,1739,1745,1751],{"__ignoreMap":114},[118,1710,1711,1713],{"class":120,"line":121},[118,1712,1303],{"class":922},[118,1714,947],{"class":382},[118,1716,1717,1719,1721],{"class":120,"line":135},[118,1718,1310],{"class":922},[118,1720,926],{"class":382},[118,1722,1315],{"class":128},[118,1724,1725,1727,1729],{"class":120,"line":145},[118,1726,1320],{"class":922},[118,1728,926],{"class":382},[118,1730,1280],{"class":128},[118,1732,1733,1735,1737],{"class":120,"line":154},[118,1734,1329],{"class":922},[118,1736,926],{"class":382},[118,1738,1334],{"class":128},[118,1740,1741,1743],{"class":120,"line":164},[118,1742,1339],{"class":922},[118,1744,947],{"class":382},[118,1746,1747,1749],{"class":120,"line":178},[118,1748,1346],{"class":922},[118,1750,947],{"class":382},[118,1752,1753,1755],{"class":120,"line":347},[118,1754,1023],{"class":382},[118,1756,1355],{"class":128},[12,1758,1759,1760,1762,1763,1767],{},"This is a job definition. Jobs can have any name except keywords. Have a look at the ",[104,1761,1188],{}," ",[15,1764,1766],{"href":1184,"rel":1765},[],"documentation"," for the complete list of keywords.",[12,1769,1770,1771,1774,1775,1778],{},"The scope of this job is to perform a ",[15,1772,226],{"href":224,"rel":1773},[]," build. For this reason, we define the ",[104,1776,1777],{},"maven:3-jdk-8"," as the Docker image on which this job should execute. This image comes with Maven 3 and the Java JDK 8 pre-installed for us.",[12,1780,1781,1782,238,1785,1788,1789,569],{},"We then specify ",[104,1783,1784],{},"build",[104,1786,1787],{},"stage"," of this job. Jobs associated with the same stage run concurrently. This is extremely useful if you need to cross-compile your application. For instance, if we wanted to compile and test our application also on Java JDK 7, we could simply create another job with a different name and use the image ",[104,1790,1791],{},"maven:3-jdk-7",[109,1793,1795],{"className":913,"code":1794,"language":915,"meta":114,"style":114},"maven-test-jdk-7:\n  image: maven:3-jdk-7\n  stage: build\n  script: \"mvn package -B\"\n  artifacts:\n    paths:\n      - target/*.jar\n\n",[104,1796,1797,1804,1813,1821,1829,1835,1841],{"__ignoreMap":114},[118,1798,1799,1802],{"class":120,"line":121},[118,1800,1801],{"class":922},"maven-test-jdk-7",[118,1803,947],{"class":382},[118,1805,1806,1808,1810],{"class":120,"line":135},[118,1807,1310],{"class":922},[118,1809,926],{"class":382},[118,1811,1812],{"class":128},"maven:3-jdk-7\n",[118,1814,1815,1817,1819],{"class":120,"line":145},[118,1816,1320],{"class":922},[118,1818,926],{"class":382},[118,1820,1280],{"class":128},[118,1822,1823,1825,1827],{"class":120,"line":154},[118,1824,1329],{"class":922},[118,1826,926],{"class":382},[118,1828,1334],{"class":128},[118,1830,1831,1833],{"class":120,"line":164},[118,1832,1339],{"class":922},[118,1834,947],{"class":382},[118,1836,1837,1839],{"class":120,"line":178},[118,1838,1346],{"class":922},[118,1840,947],{"class":382},[118,1842,1843,1845],{"class":120,"line":347},[118,1844,1023],{"class":382},[118,1846,1355],{"class":128},[12,1848,1849,1850,1852,1853,1855],{},"As previously said, the ",[104,1851,1801],{}," job runs in parallel with the ",[104,1854,1303],{},". Hence, it does not have an impact on the pipeline execution time.",[12,1857,824,1858,1865,1866,1869,1870,1873,1874,1879,1880,1883,1884,252,1887,1890,1891,1894,1895,1897],{},[15,1859,1862],{"href":1860,"rel":1861},"https://docs.gitlab.com/ci/yaml/#script",[],[104,1863,1864],{},"script"," is a shell command to be executed by the GitLab Runner. The ",[104,1867,1868],{},"mvn package -B"," triggers a non-interactive Maven build up to the ",[104,1871,1872],{},"package"," phase. This phase is specific to the ",[15,1875,1878],{"href":1876,"rel":1877},"https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html",[],"Maven build lifecycle"," and it includes also the ",[104,1881,1882],{},"validate",", ",[104,1885,1886],{},"compile",[104,1888,1889],{},"test"," phases. That means that our Maven project will be validated, compiled and (unit) tested as well. Tests are to be included in the ",[104,1892,1893],{},"src/test/java"," folder. In our specific case, Spring Initializr has already created a unit test which verifies that the application context loads without errors. We are free to add as many unit tests as we like. Finally, the ",[104,1896,1872],{}," phase creates the executable JAR.",[12,1899,1900,1901,1908],{},"To persist the executable JAR and share it across jobs, we specify job ",[15,1902,1905],{"href":1903,"rel":1904},"https://docs.gitlab.com/ci/yaml/#artifacts",[],[104,1906,1907],{},"artifacts",". These are files or directories that are attached to the build after success and made downloadable from the UI in the Pipelines screen.",[12,1910,1911],{},[273,1912],{"alt":1913,"src":1914},"Downloading artifacts from pipelines","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398114/blog/Content%20Images/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/artifacts.png",[1523,1916,824,1918,1703],{"id":1917},"the-docker-build-job",[104,1919,1365],{},[109,1921,1923],{"className":913,"code":1922,"language":915,"meta":114,"style":114},"docker-build:\n  stage: package\n  script:\n  - docker build -t registry.gitlab.com/marcolenzo/actuator-sample .\n  - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.gitlab.com\n  - docker push registry.gitlab.com/marcolenzo/actuator-sample\n\n",[104,1924,1925,1931,1939,1945,1951,1957],{"__ignoreMap":114},[118,1926,1927,1929],{"class":120,"line":121},[118,1928,1365],{"class":922},[118,1930,947],{"class":382},[118,1932,1933,1935,1937],{"class":120,"line":135},[118,1934,1320],{"class":922},[118,1936,926],{"class":382},[118,1938,1287],{"class":128},[118,1940,1941,1943],{"class":120,"line":145},[118,1942,1329],{"class":922},[118,1944,947],{"class":382},[118,1946,1947,1949],{"class":120,"line":154},[118,1948,1228],{"class":382},[118,1950,1391],{"class":128},[118,1952,1953,1955],{"class":120,"line":164},[118,1954,1228],{"class":382},[118,1956,1399],{"class":128},[118,1958,1959,1961],{"class":120,"line":178},[118,1960,1228],{"class":382},[118,1962,1407],{"class":128},[12,1964,824,1965,1967,1968,1970,1971,1973,1974,1976],{},[104,1966,1365],{}," job packages the application into a Docker container. We define ",[104,1969,1872],{}," as the build ",[104,1972,1787],{}," since we need the ",[104,1975,1303],{}," job to produce the executable JAR beforehand.",[12,1978,1979,1980,1983,1984,569],{},"The scripts are a typical sequence of ",[104,1981,1982],{},"docker"," commands used to build an image, log in to a private registry and push the image to it. We will be pushing images to the ",[15,1985,1121],{"href":1120},[12,1987,824,1988,1991],{},[104,1989,1990],{},"$CI_BUILD_TOKEN"," is a pre-defined variable which is injected by GitLab CI into our build environment automatically. It is used to log in to the GitLab Container Registry.",[12,1993,1994,1995,569],{},"For a complete list of pre-defined variables, have a look at the ",[15,1996,1999],{"href":1997,"rel":1998},"https://docs.gitlab.com/ci/variables/",[],"variables documentation",[1523,2001,824,2003,1703],{"id":2002},"the-k8s-deploy-job",[104,2004,1418],{},[12,2006,2007,2008,2012,2013,2018,2019,2022],{},"This job is responsible for deploying our application to the ",[15,2009,2011],{"href":732,"rel":2010},[],"Google Kubernetes Engine",". I purposely decided to make use of the ",[15,2014,2017],{"href":2015,"rel":2016},"https://cloud.google.com/sdk/gcloud/",[],"Google Cloud SDK"," (",[104,2020,2021],{},"gcloud",") because it gives us the possibility to programmatically create and manage Google Container Engine clusters and other products of the Google Cloud ecosystem. In this tutorial, we will simplify things a bit by creating the Google Container Engine cluster beforehand through the GUI.",[12,2024,2025,2026,2028,2029,2032,2033,2035,2036,569],{},"First, we create a Google Cloud Project named ",[104,2027,106],{},". Take note of the ",[104,2030,2031],{},"Project ID"," since it sometimes differs from the project name we specify. Then we create a Google Kubernetes Engine cluster named ",[104,2034,106],{}," as well. We can choose any machine type and any number of nodes. For the purpose of this tutorial one node and a small machine are sufficient. Let's take note of the ",[104,2037,2038],{},"zone",[12,2040,2041],{},[273,2042],{"alt":2043,"src":2044},"Create a container cluster","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398114/blog/Content%20Images/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/create-gce-cluster.png",[12,2046,2047,2048,2050,2051,2054,2055,2054,2058,2061,2062,569],{},"Finally we need to create a service account which is necessary to perform a non-interactive login with ",[104,2049,2021],{},". Navigate to Google Cloud ",[214,2052,2053],{},"API Manager"," > ",[214,2056,2057],{},"Credentials",[214,2059,2060],{},"Create Credentials"," and create a JSON key for the ",[104,2063,2064],{},"Compute Engine default service account",[12,2066,2067],{},"We can now analyze the configuration.",[109,2069,2071],{"className":913,"code":2070,"language":915,"meta":114,"style":114},"k8s-deploy:\n  image: google/cloud-sdk\n  stage: deploy\n  script:\n  - echo \"$GOOGLE_KEY\" > key.json # Google Cloud service account key\n  - gcloud auth activate-service-account --key-file key.json\n  - gcloud config set compute/zone europe-west1-c\n  - gcloud config set project actuator-sample\n  - gcloud config set container/use_client_certificate True\n  - gcloud container clusters get-credentials actuator-example\n  - kubectl delete secret registry.gitlab.com\n  - kubectl create secret docker-registry registry.gitlab.com --docker-server=https://registry.gitlab.com --docker-username=marcolenzo --docker-password=$REGISTRY_PASSWD --docker-email=lenzo.marco@gmail.com\n  - kubectl apply -f deployment.yml\n\n",[104,2072,2073,2079,2087,2095,2101,2111,2117,2123,2129,2135,2142,2148,2154],{"__ignoreMap":114},[118,2074,2075,2077],{"class":120,"line":121},[118,2076,1418],{"class":922},[118,2078,947],{"class":382},[118,2080,2081,2083,2085],{"class":120,"line":135},[118,2082,1310],{"class":922},[118,2084,926],{"class":382},[118,2086,1430],{"class":128},[118,2088,2089,2091,2093],{"class":120,"line":145},[118,2090,1320],{"class":922},[118,2092,926],{"class":382},[118,2094,1294],{"class":128},[118,2096,2097,2099],{"class":120,"line":154},[118,2098,1329],{"class":922},[118,2100,947],{"class":382},[118,2102,2103,2105,2108],{"class":120,"line":164},[118,2104,1228],{"class":382},[118,2106,2107],{"class":128},"echo \"$GOOGLE_KEY\" > key.json",[118,2109,2110],{"class":310}," # Google Cloud service account key\n",[118,2112,2113,2115],{"class":120,"line":178},[118,2114,1228],{"class":382},[118,2116,1462],{"class":128},[118,2118,2119,2121],{"class":120,"line":347},[118,2120,1228],{"class":382},[118,2122,1470],{"class":128},[118,2124,2125,2127],{"class":120,"line":359},[118,2126,1228],{"class":382},[118,2128,1478],{"class":128},[118,2130,2131,2133],{"class":120,"line":364},[118,2132,1228],{"class":382},[118,2134,1486],{"class":128},[118,2136,2137,2139],{"class":120,"line":370},[118,2138,1228],{"class":382},[118,2140,2141],{"class":128},"gcloud container clusters get-credentials actuator-example\n",[118,2143,2144,2146],{"class":120,"line":375},[118,2145,1228],{"class":382},[118,2147,1502],{"class":128},[118,2149,2150,2152],{"class":120,"line":395},[118,2151,1228],{"class":382},[118,2153,1510],{"class":128},[118,2155,2156,2158],{"class":120,"line":400},[118,2157,1228],{"class":382},[118,2159,1518],{"class":128},[12,2161,2162,2163,2166,2167,2169,2170,238,2173,2175],{},"We use the ",[104,2164,2165],{},"google/cloud-sdk"," image for this process since it comes preloaded with ",[104,2168,2021],{}," and all components and dependencies of the Google Cloud SDK including alpha and beta components. We obviously chose ",[104,2171,2172],{},"deploy",[104,2174,1787],{}," since we want our application to be packaged beforehand and its container pushed to the GitLab Container Registry. Then we execute a set of scripts.",[12,2177,824,2178,2180,2181,2184,2185,2190,2191,2193,2194,2054,2197,2054,2199,2202],{},[104,2179,2107],{}," script injects the Google Cloud service account key in the container. ",[104,2182,2183],{},"$GOOGLE_KEY"," is a Secure Variable having the content of the Google Cloud service account key as its value. ",[15,2186,2189],{"href":2187,"rel":2188},"https://docs.gitlab.com/ci/variables/#user-defined-variables-secure-variables",[],"Secure Variables"," are user-defined variables that should not be shown in the ",[104,2192,1188],{}," file. They are set per project by navigating to ",[214,2195,2196],{},"Project",[214,2198,1583],{},[214,2200,2201],{},"Add Variable"," in GitLab.",[12,2204,2205],{},[273,2206],{"alt":2189,"src":2207},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398117/blog/Content%20Images/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/secure-variables.png",[12,2209,824,2210,2213,2214,2217,2218,2221,2222,2225,2226,2228,2229,2232,2233,569],{},[104,2211,2212],{},"gcloud auth activate-service-account --key-file key.json"," script performs the non-interactive authentication process. The ",[104,2215,2216],{},"gcloud config set ..."," scripts are selecting the target project, zone and cluster. Make sure these values correspond to those you jotted down before. The ",[104,2219,2220],{},"gcloud container clusters get-credentials actuator-example"," script downloads the ",[104,2223,2224],{},"kubectl"," configuration file. If we wanted to use Kubernetes on another cloud provider or custom installation, we would source the ",[104,2227,2224],{}," configuration ",[104,2230,2231],{},"~/.kube/config"," without the need to interact with ",[104,2234,2021],{},[12,2236,824,2237,2240,2241,2244,2245,2247,2248,2251,2252,2255,2256,2259,2260,2265,2266,1883,2271,1883,2276,2281,2282,2287,2288,2291,2292,2295],{},[104,2238,2239],{},"kubectl create secret docker-registry ..."," script creates the ",[104,2242,2243],{},"imagePullSecret"," we had defined in the ",[104,2246,910],{},". This is used by Kubernetes to authenticate with our private GitLab Container Registry and download the container images. The ",[104,2249,2250],{},"kubectl delete secret"," is necessary because the Kubernetes API is lacking the ",[104,2253,2254],{},"replace"," operation for ",[104,2257,2258],{},"docker-registry"," secrets. In a real-world scenario, I would suggest handling ",[15,2261,2264],{"href":2262,"rel":2263},"http://kubernetes.io/docs/user-guide/secrets/",[],"Kubernetes secrets"," that can affect multiple pipelines (such as a password for a private Docker registry) in a separate pipeline or through configuration management tools like ",[15,2267,2270],{"href":2268,"rel":2269},"https://www.ansible.com/",[],"Ansible",[15,2272,2275],{"href":2273,"rel":2274},"https://saltstack.com/",[],"Salt",[15,2277,2280],{"href":2278,"rel":2279},"https://puppet.com/",[],"Puppet"," or ",[15,2283,2286],{"href":2284,"rel":2285},"https://www.chef.io/",[],"Chef",". The reason is that such secrets should be rotated periodically for security reasons and updated in each GitLab project using them. There is also the risk of interference between pipelines because of the ",[104,2289,2290],{},"kubectl delete"," command. Note that ",[104,2293,2294],{},"$REGISTRY_PASSWD"," is another Secure Variable.",[12,2297,2298],{},"Time to check if everything is in order on our cluster.",[109,2300,2302],{"className":111,"code":2301,"language":113,"meta":114,"style":114},"$ kubectl get deployments\nNAME              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE\nactuator-sample   2         2         2            2           2m\n$ kubectl get pods\nNAME                               READY     STATUS    RESTARTS   AGE\nactuator-sample-3641958612-3e5xy   1/1       Running   0          2m\nactuator-sample-5542343546-fr4gh   1/1       Running   0          2m\n",[104,2303,2304,2318,2338,2356,2367,2382,2399],{"__ignoreMap":114},[118,2305,2306,2309,2312,2315],{"class":120,"line":121},[118,2307,2308],{"class":124},"$",[118,2310,2311],{"class":128}," kubectl",[118,2313,2314],{"class":128}," get",[118,2316,2317],{"class":128}," deployments\n",[118,2319,2320,2323,2326,2329,2332,2335],{"class":120,"line":135},[118,2321,2322],{"class":124},"NAME",[118,2324,2325],{"class":128},"              DESIRED",[118,2327,2328],{"class":128},"   CURRENT",[118,2330,2331],{"class":128},"   UP-TO-DATE",[118,2333,2334],{"class":128},"   AVAILABLE",[118,2336,2337],{"class":128},"   AGE\n",[118,2339,2340,2342,2345,2348,2350,2353],{"class":120,"line":145},[118,2341,106],{"class":124},[118,2343,2344],{"class":138},"   2",[118,2346,2347],{"class":138},"         2",[118,2349,2347],{"class":138},[118,2351,2352],{"class":138},"            2",[118,2354,2355],{"class":128},"           2m\n",[118,2357,2358,2360,2362,2364],{"class":120,"line":154},[118,2359,2308],{"class":124},[118,2361,2311],{"class":128},[118,2363,2314],{"class":128},[118,2365,2366],{"class":128}," pods\n",[118,2368,2369,2371,2374,2377,2380],{"class":120,"line":164},[118,2370,2322],{"class":124},[118,2372,2373],{"class":128},"                               READY",[118,2375,2376],{"class":128},"     STATUS",[118,2378,2379],{"class":128},"    RESTARTS",[118,2381,2337],{"class":128},[118,2383,2384,2387,2390,2393,2396],{"class":120,"line":178},[118,2385,2386],{"class":124},"actuator-sample-3641958612-3e5xy",[118,2388,2389],{"class":128},"   1/1",[118,2391,2392],{"class":128},"       Running",[118,2394,2395],{"class":138},"   0",[118,2397,2398],{"class":128},"          2m\n",[118,2400,2401,2404,2406,2408,2410],{"class":120,"line":347},[118,2402,2403],{"class":124},"actuator-sample-5542343546-fr4gh",[118,2405,2389],{"class":128},[118,2407,2392],{"class":128},[118,2409,2395],{"class":138},[118,2411,2398],{"class":128},[12,2413,2414],{},[273,2415],{"alt":47,"src":2416},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398117/blog/Content%20Images/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/kubernetes.png",[12,2418,2419],{},"Deployed!",[1523,2421,2423],{"id":2422},"gitlab-environments","GitLab Environments",[12,2425,2426,2427,2431],{},"Before concluding the tutorial, we will learn about ",[15,2428,2423],{"href":2429,"rel":2430},"https://docs.gitlab.com/ci/environments/",[]," which enable us to track environments and deployments.",[12,2433,2434,2435,2437],{},"Let's refactor the ",[104,2436,1418],{}," job and split it in two. One job will target the staging environment and the other the production environment.",[109,2439,2441],{"className":913,"code":2440,"language":915,"meta":114,"style":114},"k8s-deploy-staging:\n  image: google/cloud-sdk\n  stage: deploy\n  script:\n  - echo \"$GOOGLE_KEY\" > key.json\n  - gcloud auth activate-service-account --key-file key.json\n  - gcloud config set compute/zone europe-west1-c\n  - gcloud config set project actuator-sample\n  - gcloud config set container/use_client_certificate True\n  - gcloud container clusters get-credentials actuator-example\n  - kubectl delete secret registry.gitlab.com\n  - kubectl create secret docker-registry registry.gitlab.com --docker-server=https://registry.gitlab.com --docker-username=marcolenzo --docker-password=$REGISTRY_PASSWD --docker-email=lenzo.marco@gmail.com\n  - kubectl apply -f deployment.yml --namespace=staging\n  environment:\n    name: staging\n    url: https://example.staging.com\n  only:\n  - master\n\nk8s-deploy-production:\n  image: google/cloud-sdk\n  stage: deploy\n  script:\n  - echo \"$GOOGLE_KEY\" > key.json\n  - gcloud auth activate-service-account --key-file key.json\n  - gcloud config set compute/zone europe-west1-c\n  - gcloud config set project actuator-sample\n  - gcloud config set container/use_client_certificate True\n  - gcloud container clusters get-credentials actuator-example\n  - kubectl delete secret registry.gitlab.com\n  - kubectl create secret docker-registry registry.gitlab.com --docker-server=https://registry.gitlab.com --docker-username=marcolenzo --docker-password=$REGISTRY_PASSWD --docker-email=lenzo.marco@gmail.com\n  - kubectl apply -f deployment.yml --namespace=production\n  environment:\n    name: production\n    url: https://example.production.com\n  when: manual\n  only:\n  - production\n\n",[104,2442,2443,2450,2458,2466,2472,2478,2484,2490,2496,2502,2508,2514,2520,2527,2534,2544,2554,2561,2568,2572,2579,2587,2595,2601,2607,2613,2619,2625,2631,2637,2643,2649,2656,2662,2671,2680,2690,2696],{"__ignoreMap":114},[118,2444,2445,2448],{"class":120,"line":121},[118,2446,2447],{"class":922},"k8s-deploy-staging",[118,2449,947],{"class":382},[118,2451,2452,2454,2456],{"class":120,"line":135},[118,2453,1310],{"class":922},[118,2455,926],{"class":382},[118,2457,1430],{"class":128},[118,2459,2460,2462,2464],{"class":120,"line":145},[118,2461,1320],{"class":922},[118,2463,926],{"class":382},[118,2465,1294],{"class":128},[118,2467,2468,2470],{"class":120,"line":154},[118,2469,1329],{"class":922},[118,2471,947],{"class":382},[118,2473,2474,2476],{"class":120,"line":164},[118,2475,1228],{"class":382},[118,2477,1454],{"class":128},[118,2479,2480,2482],{"class":120,"line":178},[118,2481,1228],{"class":382},[118,2483,1462],{"class":128},[118,2485,2486,2488],{"class":120,"line":347},[118,2487,1228],{"class":382},[118,2489,1470],{"class":128},[118,2491,2492,2494],{"class":120,"line":359},[118,2493,1228],{"class":382},[118,2495,1478],{"class":128},[118,2497,2498,2500],{"class":120,"line":364},[118,2499,1228],{"class":382},[118,2501,1486],{"class":128},[118,2503,2504,2506],{"class":120,"line":370},[118,2505,1228],{"class":382},[118,2507,2141],{"class":128},[118,2509,2510,2512],{"class":120,"line":375},[118,2511,1228],{"class":382},[118,2513,1502],{"class":128},[118,2515,2516,2518],{"class":120,"line":395},[118,2517,1228],{"class":382},[118,2519,1510],{"class":128},[118,2521,2522,2524],{"class":120,"line":400},[118,2523,1228],{"class":382},[118,2525,2526],{"class":128},"kubectl apply -f deployment.yml --namespace=staging\n",[118,2528,2529,2532],{"class":120,"line":406},[118,2530,2531],{"class":922},"  environment",[118,2533,947],{"class":382},[118,2535,2536,2539,2541],{"class":120,"line":411},[118,2537,2538],{"class":922},"    name",[118,2540,926],{"class":382},[118,2542,2543],{"class":128},"staging\n",[118,2545,2546,2549,2551],{"class":120,"line":425},[118,2547,2548],{"class":922},"    url",[118,2550,926],{"class":382},[118,2552,2553],{"class":128},"https://example.staging.com\n",[118,2555,2556,2559],{"class":120,"line":433},[118,2557,2558],{"class":922},"  only",[118,2560,947],{"class":382},[118,2562,2563,2565],{"class":120,"line":438},[118,2564,1228],{"class":382},[118,2566,2567],{"class":128},"master\n",[118,2569,2570],{"class":120,"line":444},[118,2571,317],{"emptyLinePlaceholder":316},[118,2573,2574,2577],{"class":120,"line":449},[118,2575,2576],{"class":922},"k8s-deploy-production",[118,2578,947],{"class":382},[118,2580,2581,2583,2585],{"class":120,"line":509},[118,2582,1310],{"class":922},[118,2584,926],{"class":382},[118,2586,1430],{"class":128},[118,2588,2589,2591,2593],{"class":120,"line":1362},[118,2590,1320],{"class":922},[118,2592,926],{"class":382},[118,2594,1294],{"class":128},[118,2596,2597,2599],{"class":120,"line":1370},[118,2598,1329],{"class":922},[118,2600,947],{"class":382},[118,2602,2603,2605],{"class":120,"line":1379},[118,2604,1228],{"class":382},[118,2606,1454],{"class":128},[118,2608,2609,2611],{"class":120,"line":1386},[118,2610,1228],{"class":382},[118,2612,1462],{"class":128},[118,2614,2615,2617],{"class":120,"line":1394},[118,2616,1228],{"class":382},[118,2618,1470],{"class":128},[118,2620,2621,2623],{"class":120,"line":1402},[118,2622,1228],{"class":382},[118,2624,1478],{"class":128},[118,2626,2627,2629],{"class":120,"line":1410},[118,2628,1228],{"class":382},[118,2630,1486],{"class":128},[118,2632,2633,2635],{"class":120,"line":1415},[118,2634,1228],{"class":382},[118,2636,2141],{"class":128},[118,2638,2639,2641],{"class":120,"line":1423},[118,2640,1228],{"class":382},[118,2642,1502],{"class":128},[118,2644,2645,2647],{"class":120,"line":1433},[118,2646,1228],{"class":382},[118,2648,1510],{"class":128},[118,2650,2651,2653],{"class":120,"line":1442},[118,2652,1228],{"class":382},[118,2654,2655],{"class":128},"kubectl apply -f deployment.yml --namespace=production\n",[118,2657,2658,2660],{"class":120,"line":1449},[118,2659,2531],{"class":922},[118,2661,947],{"class":382},[118,2663,2664,2666,2668],{"class":120,"line":1457},[118,2665,2538],{"class":922},[118,2667,926],{"class":382},[118,2669,2670],{"class":128},"production\n",[118,2672,2673,2675,2677],{"class":120,"line":1465},[118,2674,2548],{"class":922},[118,2676,926],{"class":382},[118,2678,2679],{"class":128},"https://example.production.com\n",[118,2681,2682,2685,2687],{"class":120,"line":1473},[118,2683,2684],{"class":922},"  when",[118,2686,926],{"class":382},[118,2688,2689],{"class":128},"manual\n",[118,2691,2692,2694],{"class":120,"line":1481},[118,2693,2558],{"class":922},[118,2695,947],{"class":382},[118,2697,2698,2700],{"class":120,"line":1489},[118,2699,1228],{"class":382},[118,2701,2670],{"class":128},[12,2703,824,2704,2707,2708,2711,2712,2715,2716,2719,2720,2723,2724,2727,2728,2732,2733,2737,2738,2741],{},[104,2705,2706],{},"environment"," keyword associates the job with a specific environment while the ",[104,2709,2710],{},"url"," element is used to generate a handy hyperlink to our application on the GitLab Environments page (found under your project's ",[104,2713,2714],{},"Pipelines > Environments","). The ",[104,2717,2718],{},"only"," keyword signals to GitLab CI that the job should be executed only when the pipeline is building the listed branches. Finally, ",[104,2721,2722],{},"when: manual"," is used to turn the job execution from automatic to manual. Turning the execution of this job to ",[104,2725,2726],{},"automatic"," would project us in the world of ",[15,2729,2731],{"href":2730},"/blog/continuous-integration-delivery-and-deployment-with-gitlab/#continuous-deployment","Continuous Deployment"," rather than ",[15,2734,2736],{"href":2735},"/blog/continuous-integration-delivery-and-deployment-with-gitlab/#continuous-delivery","Continuous Delivery",". From a Kubernetes perspective, we are making use of ",[104,2739,2740],{},"namespaces"," to segregate the different environments.",[12,2743,2744,2745,252,2747,2750,2751,2755,2756,2760,2761,2766,2767,2770],{},"By committing on ",[104,2746,660],{},[104,2748,2749],{},"production"," we ",[15,2752,2754],{"href":2753},"/blog/ci-deployment-and-environments/","trigger a pipeline per environment",". As mentioned before, we are not making use of any collaboration tool because it is out of the scope of this tutorial. In real-world scenarios, we would use ",[15,2757,2759],{"href":2758},"/topics/version-control/what-is-gitlab-flow/#merge-request","merge requests"," with ",[15,2762,2765],{"href":2763,"rel":2764},"https://docs.gitlab.com/ci/review_apps/",[],"Review Apps"," to move code across branches. Merge requests allow the team to review and discuss the changes before they get merged into the target branch. ",[15,2768,2765],{"href":2763,"rel":2769},[]," take that one step further by spinning up dynamic environments for our merge requests, offering the team access to a deployed instance of our application without the need of checking out the branch. This is extremely useful not only for non-technical members of the team, but also to collaborators and project managers to preview the changes without having to clone and install the app and its dependencies when evaluating a proposal.",[109,2772,2774],{"className":111,"code":2773,"language":113,"meta":114,"style":114},"git commit -am \"Showcasing Pipelines\"\ngit push origin master\ngit checkout -b production\ngit push origin production\n",[104,2775,2776,2788,2798,2811],{"__ignoreMap":114},[118,2777,2778,2780,2782,2785],{"class":120,"line":121},[118,2779,125],{"class":124},[118,2781,169],{"class":128},[118,2783,2784],{"class":138}," -am",[118,2786,2787],{"class":128}," \"Showcasing Pipelines\"\n",[118,2789,2790,2792,2794,2796],{"class":120,"line":135},[118,2791,125],{"class":124},[118,2793,183],{"class":128},[118,2795,189],{"class":128},[118,2797,192],{"class":128},[118,2799,2800,2802,2805,2808],{"class":120,"line":145},[118,2801,125],{"class":124},[118,2803,2804],{"class":128}," checkout",[118,2806,2807],{"class":138}," -b",[118,2809,2810],{"class":128}," production\n",[118,2812,2813,2815,2817,2819],{"class":120,"line":154},[118,2814,125],{"class":124},[118,2816,183],{"class":128},[118,2818,189],{"class":128},[118,2820,2810],{"class":128},[12,2822,2823],{},[273,2824],{"alt":2825,"src":2826},"Pipelines","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398117/blog/Content%20Images/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/pipelines.png",[12,2828,2829,2830,2832,2833,2835],{},"The Pipelines screen details all pipeline executions. We can gather information about the branch and the individual result of each stage. In the case of the ",[104,2831,2749],{}," pipeline the ",[104,2834,2576],{}," is not executed automatically as expected but can be triggered from the GUI from where we can also download the build artifacts.",[12,2837,2838],{},[273,2839],{"alt":2840,"src":2841},"Environments","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398114/blog/Content%20Images/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/environments.png",[12,2843,2844],{},"Environments are listed on a separate page, from which it is possible to redeploy the latest version of an environment or to roll back to a particular version of the environment by accessing the relative details page.",[12,2846,2847],{},[273,2848],{"alt":2849,"src":2850},"Rollbacks","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398117/blog/Content%20Images/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/rollbacks.png",[50,2852,2854],{"id":2853},"conclusion","Conclusion",[12,2856,2857,2858,2862,2863,2866,2867,2870,2871,2874,2875,2877,2878,2883],{},"In this tutorial, we were able to create a ",[15,2859,2736],{"href":2860,"rel":2861},"https://en.wikipedia.org/wiki/Continuous_delivery",[]," pipeline with ease thanks to the suite of ",[15,2864,27],{"href":2865},"/"," products that supported us at every stage. ",[15,2868,36],{"href":34,"rel":2869},[]," gave us agility by auto-configuring the application context and offering production-grade services out of the box. ",[15,2872,47],{"href":45,"rel":2873},[]," abstracted us from the compute resources and orchestration duties allowing us to define only the desired deployment state. ",[15,2876,41],{"href":40}," was the core engine of our pipeline. Its declarative ",[15,2879,2881],{"href":1184,"rel":2880},[],[104,2882,1188],{}," file allowed us to define, version and manage our pipelines while the GUI gave us full visibility and control.",[12,2885,2886],{},"While this is a basic example, it clearly shows the immense benefits any team or company can gain by using the unified GUI of GitLab for issues, code review, CI and CD.",[50,2888,2890],{"id":2889},"about-guest-author","About Guest Author",[12,2892,2893,2897],{},[15,2894,7],{"href":2895,"rel":2896},"https://twitter.com/marco_lenzo",[]," is a Software Architect always up for a challenge. He has expertise in transaction processing and platform as a service (PaaS). Java, Spring, Go and Kubernetes are currently his bread and butter.",[2899,2900,2901],"style",{},"\n  .h4 {\n    font-weight: bold;\n  }\n",[2899,2903,2904],{},"html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .shJU0, html code.shiki .shJU0{--shiki-default:#22863A}",{"title":114,"searchDepth":135,"depth":135,"links":2906},[2907,2908,2909,2910,2915,2916],{"id":52,"depth":135,"text":53},{"id":92,"depth":135,"text":93},{"id":202,"depth":135,"text":203},{"id":704,"depth":135,"text":705,"children":2911},[2912,2913,2914],{"id":752,"depth":145,"text":753},{"id":903,"depth":145,"text":904},{"id":1174,"depth":145,"text":1175},{"id":2853,"depth":135,"text":2854},{"id":2889,"depth":135,"text":2890},"engineering","2016-12-14","Create a Continuous Delivery pipeline to deploy a Spring Boot app with GitLab CI and Kubernetes to Google Cloud Container Engine","md",null,false,"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749672314/Blog/Hero%20Images/dew-leaf.jpg",{"excerpt":2925},{"type":9,"value":2926},[2927,2931],[12,2928,2929,19],{},[15,2930,18],{"href":17},[12,2932,22,2933,28],{},[15,2934,27],{"href":25,"rel":2935},[],"/en-us/blog/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes",{"title":2938,"description":2919,"ogTitle":2938,"ogDescription":2919,"noIndex":2922,"ogImage":2923,"ogUrl":2939,"ogSiteName":2940,"ogType":2941,"canonicalUrls":2939},"Spring Boot delivery with GitLab CI and Kubernetes","https://about.gitlab.com/blog/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes","https://about.gitlab.com","article","continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes","en-us/blog/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes","BlogPost","gjdkZYTbcue2Q9MbcQZzQs7ieS0JCGisdJSLUUdeovo",{"logo":2947,"freeTrial":2951,"sales":2956,"login":2961,"items":2966,"search":3294,"minimal":3325,"duo":3344,"switchNav":3353,"pricingDeployment":3364},{"config":2948},{"href":2865,"dataGaName":2949,"dataGaLocation":2950},"gitlab logo","header",{"text":2952,"config":2953},"Get free trial",{"href":2954,"dataGaName":2955,"dataGaLocation":2950},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":2957,"config":2958},"Request a demo",{"href":2959,"dataGaName":2960,"dataGaLocation":2950},"/request-a-demo/","sales",{"text":2962,"config":2963},"Sign in",{"href":2964,"dataGaName":2965,"dataGaLocation":2950},"https://gitlab.com/users/sign_in/","sign in",[2967,3052,3140,3145,3219,3272],{"text":2968,"left":316,"config":2969,"menu":2971},"Platform",{"dataNavLevelOne":2970},"platform",{"type":2972,"link":2973,"columns":2977,"feature":3041},"lists",{"text":2974,"config":2975},"Explore the Platform",{"href":2976,"dataGaName":2970,"dataGaLocation":2950},"/platform/",[2978,3000,3023],{"title":2979,"items":2980},"Execution & Workflows",[2981,2985,2990,2995],{"text":2982,"config":2983},"CI/CD",{"href":40,"dataGaLocation":2950,"dataGaName":2984},"ci/cd",{"text":2986,"config":2987},"Source Code Management",{"href":2988,"dataGaLocation":2950,"dataGaName":2989},"/solutions/source-code-management/","source code management",{"text":2991,"config":2992},"Agile Delivery",{"href":2993,"dataGaLocation":2950,"dataGaName":2994},"/solutions/agile-delivery/","agile delivery",{"text":2996,"config":2997},"Artifact Management",{"href":2998,"dataGaLocation":2950,"dataGaName":2999},"/artifact-central/","artifact management",{"title":3001,"items":3002},"Security & Governance",[3003,3008,3013,3018],{"text":3004,"config":3005},"Application Security Testing",{"href":3006,"dataGaLocation":2950,"dataGaName":3007},"/solutions/application-security-testing/","application security testing",{"text":3009,"config":3010},"Governance & Compliance",{"href":3011,"dataGaLocation":2950,"dataGaName":3012},"/solutions/software-compliance/","governance and compliance",{"text":3014,"config":3015},"Secrets Management",{"href":3016,"dataGaLocation":2950,"dataGaName":3017},"/gitlab-secrets-manager/","secrets management",{"text":3019,"config":3020},"Supply Chain Security",{"href":3021,"dataGaLocation":2950,"dataGaName":3022},"/solutions/supply-chain/","supply chain security",{"title":3024,"items":3025},"Context & AI",[3026,3031,3036],{"text":3027,"config":3028},"Agentic Orchestration",{"href":3029,"dataGaLocation":2950,"dataGaName":3030},"/gitlab-duo-agent-platform/","agentic orchestration",{"text":3032,"config":3033},"Context Graph",{"href":3034,"dataGaLocation":2950,"dataGaName":3035},"/gitlab-orbit/","context graph",{"text":3037,"config":3038},"Visibility & Measurement",{"href":3039,"dataGaLocation":2950,"dataGaName":3040},"/solutions/visibility-measurement/","visibility and measurement",{"config":3042,"title":3045,"text":3046,"link":3047},{"background":3043,"textColor":3044},"url('https://res.cloudinary.com/about-gitlab-com/image/upload/v1790185102/o7dn32anqcb6z8dgd3as.jpg')","#000","Why GitLab","One platform for speed with control across your software lifecycle.",{"text":3048,"config":3049},"Learn more",{"href":3050,"dataGaName":3051,"dataGaLocation":2950},"/why-gitlab/","why gitlab",{"text":3053,"left":316,"config":3054,"menu":3056},"Solutions",{"dataNavLevelOne":3055},"solutions",{"type":2972,"link":3057,"columns":3061,"feature":3131},{"text":3058,"config":3059},"View all Solutions",{"href":3060,"dataGaName":3055,"dataGaLocation":2950},"/solutions/",[3062,3080,3098],{"title":3063,"items":3064},"Outcomes",[3065,3070,3075],{"text":3066,"config":3067},"DevOps Modernization",{"href":3068,"dataGaLocation":2950,"dataGaName":3069},"/assessments/devops-modernization-assessment/","devops modernization",{"text":3071,"config":3072},"Security Modernization",{"href":3073,"dataGaLocation":2950,"dataGaName":3074},"/assessments/security-modernization-assessment/","security modernization",{"text":3076,"config":3077},"AI Modernization",{"href":3078,"dataGaLocation":2950,"dataGaName":3079},"/assessments/ai-modernization-assessment/","ai modernization",{"title":3081,"items":3082},"By size",[3083,3088,3093],{"text":3084,"config":3085},"Enterprise",{"href":3086,"dataGaLocation":2950,"dataGaName":3087},"/enterprise/","enterprise",{"text":3089,"config":3090},"Small Business",{"href":3091,"dataGaLocation":2950,"dataGaName":3092},"/small-business/","small business",{"text":3094,"config":3095},"Startups",{"href":3096,"dataGaLocation":2950,"dataGaName":3097},"/solutions/startups/","startups",{"title":3099,"items":3100},"Industries",[3101,3106,3111,3116,3121,3126],{"text":3102,"config":3103},"Financial Services",{"href":3104,"dataGaLocation":2950,"dataGaName":3105},"/solutions/finance/","financial services",{"text":3107,"config":3108},"Public Sector",{"href":3109,"dataGaLocation":2950,"dataGaName":3110},"/solutions/public-sector/","public sector",{"text":3112,"config":3113},"Telecommunications",{"href":3114,"dataGaLocation":2950,"dataGaName":3115},"/solutions/telecommunications/","telecommunications",{"text":3117,"config":3118},"Automotive",{"href":3119,"dataGaLocation":2950,"dataGaName":3120},"/solutions/automotive/","automotive",{"text":3122,"config":3123},"Education",{"href":3124,"dataGaLocation":2950,"dataGaName":3125},"/solutions/education/","education",{"text":3127,"config":3128},"Aerospace",{"href":3129,"dataGaLocation":2950,"dataGaName":3130},"/solutions/aerospace/","aerospace",{"config":3132,"title":3133,"text":3134,"link":3135},{"background":3043,"textColor":3044},"GitLab Security Standard","Learn how to harden your software factory and let AI agents earn autonomy, without trading away security.",{"text":3136,"config":3137},"Explore the standard",{"href":3138,"dataGaName":3139,"dataGaLocation":2950},"/gitlab-security-standard/","gitlab security standard",{"text":3141,"config":3142},"Pricing",{"href":3143,"dataGaName":3144,"dataGaLocation":2950,"dataNavLevelOne":3144},"/pricing/","pricing",{"text":3146,"config":3147,"menu":3149},"Resources",{"dataNavLevelOne":3148},"resources",{"type":2972,"link":3150,"columns":3154,"feature":3210},{"text":3151,"config":3152},"View all resources",{"href":3153,"dataGaName":3148,"dataGaLocation":2950},"/resources/",[3155,3182],{"title":3156,"items":3157},"Discover",[3158,3163,3168,3173,3178],{"text":3159,"config":3160},"Docs",{"href":3161,"dataGaName":3162,"dataGaLocation":2950},"https://docs.gitlab.com/","docs",{"text":3164,"config":3165},"University",{"href":3166,"dataGaName":3167,"dataGaLocation":2950},"https://university.gitlab.com/","university",{"text":3169,"config":3170},"Demo Series",{"href":3171,"dataGaName":3172,"dataGaLocation":2950},"/gitlab-technical-demo-series/","demo series",{"text":3174,"config":3175},"Demo Hub",{"href":3176,"dataGaName":3177,"dataGaLocation":2950},"/demo-hub/","demo hub",{"text":3179,"config":3180},"Services",{"href":3181,"dataGaName":1221,"dataGaLocation":2950},"/services/",{"title":3183,"items":3184},"Connect",[3185,3190,3195,3200,3205],{"text":3186,"config":3187},"Blog",{"href":3188,"dataGaName":3189,"dataGaLocation":2950},"/blog/","blog",{"text":3191,"config":3192},"Community",{"href":3193,"dataGaName":3194,"dataGaLocation":2950},"/community/","community",{"text":3196,"config":3197},"Customers",{"href":3198,"dataGaName":3199,"dataGaLocation":2950},"/customers/","customers",{"text":3201,"config":3202},"Partners",{"href":3203,"dataGaName":3204,"dataGaLocation":2950},"/partners/","partners",{"text":3206,"config":3207},"Events",{"href":3208,"dataGaName":3209,"dataGaLocation":2950},"/events/","events",{"config":3211,"title":3212,"text":3213,"link":3214},{"background":3043,"textColor":3044},"What’s new in GitLab","Stay updated with our latest features and improvements.",{"text":3215,"config":3216},"Read the latest",{"href":3217,"dataGaName":3218,"dataGaLocation":2950},"/whats-new/","whats new",{"text":3220,"config":3221,"menu":3223},"Company",{"dataNavLevelOne":3222},"company",{"type":2972,"columns":3224},[3225],{"items":3226},[3227,3232,3237,3242,3247,3252,3257,3262,3267],{"text":3228,"config":3229},"About",{"href":3230,"dataGaName":3231,"dataGaLocation":2950},"/company/","about",{"text":1200,"config":3233,"footerGa":3236},{"href":3234,"dataGaName":3235,"dataGaLocation":2950},"/jobs/","jobs",{"dataGaName":3235},{"text":3238,"config":3239},"Press",{"href":3240,"dataGaName":3241,"dataGaLocation":2950},"/press/","press",{"text":3243,"config":3244},"Handbook",{"href":3245,"dataGaName":3246,"dataGaLocation":2950},"https://handbook.gitlab.com/","handbook",{"text":3248,"config":3249},"Leadership",{"href":3250,"dataGaName":3251,"dataGaLocation":2950},"/company/team/e-group/","leadership",{"text":3253,"config":3254},"Investor relations",{"href":3255,"dataGaName":3256,"dataGaLocation":2950},"https://ir.gitlab.com/overview/default.aspx","investor relations",{"text":3258,"config":3259},"Trust Center",{"href":3260,"dataGaName":3261,"dataGaLocation":2950},"/security/","trust center",{"text":3263,"config":3264},"AI Transparency Center",{"href":3265,"dataGaName":3266,"dataGaLocation":2950},"/ai-transparency-center/","ai transparency center",{"text":3268,"config":3269},"Newsletter",{"href":3270,"dataGaName":3271,"dataGaLocation":2950},"/company/contact/#contact-forms","newsletter",{"text":3273,"config":3274,"menu":3275},"Contact us",{"dataNavLevelOne":3222},{"type":2972,"columns":3276},[3277],{"items":3278},[3279,3284,3289],{"text":3280,"config":3281},"Talk to sales",{"href":3282,"dataGaName":3283,"dataGaLocation":2950},"/sales/","talk to sales",{"text":3285,"config":3286},"Support portal",{"href":3287,"dataGaName":3288,"dataGaLocation":2950},"https://support.gitlab.com/hc/en-us","support portal",{"text":3290,"config":3291},"Customer portal",{"href":3292,"dataGaName":3293,"dataGaLocation":2950},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":3295,"login":3296,"suggestions":3303},"Close",{"text":3297,"link":3298},"To search repositories and projects, login to",{"text":3299,"config":3300},"gitlab.com",{"href":2964,"dataGaName":3301,"dataGaLocation":3302},"search login","search",{"text":3304,"default":3305},"Suggestions",[3306,3309,3312,3314,3318,3322],{"text":3307,"config":3308},"GitLab Duo Agent Platform",{"href":3029,"dataGaName":3307,"dataGaLocation":3302},{"text":3310,"config":3311},"Code Suggestions (AI)",{"href":3029,"dataGaName":3310,"dataGaLocation":3302},{"text":2982,"config":3313},{"href":40,"dataGaName":2982,"dataGaLocation":3302},{"text":3315,"config":3316},"GitLab on AWS",{"href":3317,"dataGaName":3315,"dataGaLocation":3302},"/partners/technology-partners/aws/",{"text":3319,"config":3320},"GitLab on Google Cloud",{"href":3321,"dataGaName":3319,"dataGaLocation":3302},"/partners/technology-partners/google-cloud-platform/",{"text":3323,"config":3324},"Why GitLab?",{"href":3050,"dataGaName":3323,"dataGaLocation":3302},{"freeTrial":3326,"mobileIcon":3331,"desktopIcon":3336,"secondaryButton":3339},{"text":3327,"config":3328},"Start free trial",{"href":3329,"dataGaName":2955,"dataGaLocation":3330},"https://gitlab.com/-/trials/new/","nav",{"altText":3332,"config":3333},"Gitlab Icon",{"src":3334,"dataGaName":3335,"dataGaLocation":3330},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":3332,"config":3337},{"src":3338,"dataGaName":3335,"dataGaLocation":3330},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":3340,"config":3341},"Get Started",{"href":3342,"dataGaName":3343,"dataGaLocation":3330},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":3345,"mobileIcon":3349,"desktopIcon":3351},{"text":3346,"config":3347},"Learn more about GitLab Duo",{"href":3029,"dataGaName":3348,"dataGaLocation":3330},"gitlab duo",{"altText":3332,"config":3350},{"src":3334,"dataGaName":3335,"dataGaLocation":3330},{"altText":3332,"config":3352},{"src":3338,"dataGaName":3335,"dataGaLocation":3330},{"button":3354,"mobileIcon":3359,"desktopIcon":3361},{"text":3355,"config":3356},"/switch",{"href":3357,"dataGaName":3358,"dataGaLocation":3330},"#contact","switch",{"altText":3332,"config":3360},{"src":3334,"dataGaName":3335,"dataGaLocation":3330},{"altText":3332,"config":3362},{"src":3363,"dataGaName":3335,"dataGaLocation":3330},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":3365,"mobileIcon":3370,"desktopIcon":3372},{"text":3366,"config":3367},"Back to pricing",{"href":3143,"dataGaName":3368,"dataGaLocation":3330,"icon":3369},"back to pricing","GoBack",{"altText":3332,"config":3371},{"src":3334,"dataGaName":3335,"dataGaLocation":3330},{"altText":3332,"config":3373},{"src":3338,"dataGaName":3335,"dataGaLocation":3330},{"title":3375,"button":3376,"config":3382},"Ship at agent speed. Prove every step. Transcend is here.",{"text":3377,"config":3378},"Watch now",{"href":3379,"dataGaName":3380,"dataGaLocation":3381},"/events/transcend/virtual/#watch-gitlab-transcend","transcend-october-2026-banner","global-banner",{"layout":3383,"disabled":2922},"release",{"data":3385},{"text":3386,"source":3387,"edit":3393,"contribute":3398,"config":3403,"items":3408,"minimal":3632},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":3388,"config":3389},"View page source",{"href":3390,"dataGaName":3391,"dataGaLocation":3392},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":3394,"config":3395},"Edit this page",{"href":3396,"dataGaName":3397,"dataGaLocation":3392},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":3399,"config":3400},"Please contribute",{"href":3401,"dataGaName":3402,"dataGaLocation":3392},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":3404,"facebook":3405,"youtube":3406,"linkedin":3407},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[3409,3455,3512,3559,3600],{"title":3141,"links":3410,"subMenu":3425},[3411,3415,3420],{"text":3412,"config":3413},"View plans",{"href":3143,"dataGaName":3414,"dataGaLocation":3392},"view plans",{"text":3416,"config":3417},"Why Premium?",{"href":3418,"dataGaName":3419,"dataGaLocation":3392},"/pricing/premium/","why premium",{"text":3421,"config":3422},"Why Ultimate?",{"href":3423,"dataGaName":3424,"dataGaLocation":3392},"/pricing/ultimate/","why ultimate",[3426],{"title":3427,"links":3428},"Contact Us",[3429,3432,3434,3436,3440,3445,3450],{"text":3430,"config":3431},"Contact sales",{"href":3282,"dataGaName":2960,"dataGaLocation":3392},{"text":3285,"config":3433},{"href":3287,"dataGaName":3288,"dataGaLocation":3392},{"text":3290,"config":3435},{"href":3292,"dataGaName":3293,"dataGaLocation":3392},{"text":3437,"config":3438},"Status",{"href":3439,"dataGaName":610,"dataGaLocation":3392},"https://status.gitlab.com/",{"text":3441,"config":3442},"Terms of use",{"href":3443,"dataGaName":3444,"dataGaLocation":3392},"/terms/","terms of use",{"text":3446,"config":3447},"Privacy statement",{"href":3448,"dataGaName":3449,"dataGaLocation":3392},"/privacy/","privacy statement",{"text":3451,"config":3452},"Cookie preferences",{"dataGaName":3453,"dataGaLocation":3392,"id":3454,"isOneTrustButton":316},"cookie preferences","ot-sdk-btn",{"title":2968,"links":3456,"subMenu":3470},[3457,3460,3462,3464,3466,3468],{"text":3458,"config":3459},"Explore the platform",{"href":2976,"dataGaName":2970,"dataGaLocation":3392},{"text":3027,"config":3461},{"href":3029,"dataGaName":3030,"dataGaLocation":3392},{"text":2982,"config":3463},{"href":40,"dataGaName":2984,"dataGaLocation":3392},{"text":3032,"config":3465},{"href":3034,"dataGaName":3035,"dataGaLocation":3392},{"text":3014,"config":3467},{"href":3016,"dataGaName":3017,"dataGaLocation":3392},{"text":3045,"config":3469},{"href":3050,"dataGaName":3051,"dataGaLocation":3392},[3471],{"title":3472,"links":3473},"Topics",[3474,3477,3482,3487,3492,3497,3502,3507],{"text":2982,"config":3475},{"href":17,"dataGaName":3476,"dataGaLocation":3392},"cicd",{"text":3478,"config":3479},"GitOps",{"href":3480,"dataGaName":3481,"dataGaLocation":3392},"/topics/gitops/","gitops",{"text":3483,"config":3484},"DevOps",{"href":3485,"dataGaName":3486,"dataGaLocation":3392},"/topics/devops/","devops",{"text":3488,"config":3489},"Version Control",{"href":3490,"dataGaName":3491,"dataGaLocation":3392},"/topics/version-control/","version control",{"text":3493,"config":3494},"DevSecOps",{"href":3495,"dataGaName":3496,"dataGaLocation":3392},"/topics/devsecops/","devsecops",{"text":3498,"config":3499},"Cloud Native",{"href":3500,"dataGaName":3501,"dataGaLocation":3392},"/topics/cloud-native/","cloud native",{"text":3503,"config":3504},"AI for Coding",{"href":3505,"dataGaName":3506,"dataGaLocation":3392},"/topics/devops/ai-for-coding/","ai for coding",{"text":3508,"config":3509},"Agentic AI",{"href":3510,"dataGaName":3511,"dataGaLocation":3392},"/topics/agentic-ai/","agentic ai",{"title":3053,"links":3513},[3514,3517,3520,3523,3526,3529,3531,3533,3535,3537,3539,3542,3544,3547,3551,3555],{"text":3515,"config":3516},"DevOps modernization",{"href":3068,"dataGaName":3069,"dataGaLocation":3392},{"text":3518,"config":3519},"Security modernization",{"href":3073,"dataGaName":3074,"dataGaLocation":3392},{"text":3521,"config":3522},"AI modernization",{"href":3078,"dataGaName":3079,"dataGaLocation":3392},{"text":3524,"config":3525},"Financial services",{"href":3104,"dataGaName":3105,"dataGaLocation":3392},{"text":3527,"config":3528},"Public sector",{"href":3109,"dataGaName":3110,"dataGaLocation":3392},{"text":3112,"config":3530},{"href":3114,"dataGaName":3115,"dataGaLocation":3392},{"text":3117,"config":3532},{"href":3119,"dataGaName":3120,"dataGaLocation":3392},{"text":3122,"config":3534},{"href":3124,"dataGaName":3125,"dataGaLocation":3392},{"text":3127,"config":3536},{"href":3129,"dataGaName":3130,"dataGaLocation":3392},{"text":3084,"config":3538},{"href":3086,"dataGaName":3087,"dataGaLocation":3392},{"text":3540,"config":3541},"Small business",{"href":3091,"dataGaName":3092,"dataGaLocation":3392},{"text":3094,"config":3543},{"href":3096,"dataGaName":3097,"dataGaLocation":3392},{"text":3478,"config":3545},{"href":3546,"dataGaName":3481,"dataGaLocation":3392},"/solutions/gitops/",{"text":3548,"config":3549},"Software composition analysis",{"href":3021,"dataGaName":3550,"dataGaLocation":3392},"software composition analysis",{"text":3552,"config":3553},"Value stream management",{"href":3039,"dataGaName":3554,"dataGaLocation":3392},"value stream management",{"text":3556,"config":3557},"All solutions",{"href":3060,"dataGaName":3558,"dataGaLocation":3392},"all solutions",{"title":3146,"links":3560},[3561,3566,3571,3573,3575,3577,3579,3581,3583,3585,3587,3589,3591,3593,3596],{"text":3562,"config":3563},"Install",{"href":3564,"dataGaName":3565,"dataGaLocation":3392},"/install/","install",{"text":3567,"config":3568},"Quick start guides",{"href":3569,"dataGaName":3570,"dataGaLocation":3392},"/get-started/","quick setup checklists",{"text":3159,"config":3572},{"href":3161,"dataGaName":3162,"dataGaLocation":3392},{"text":3164,"config":3574},{"href":3166,"dataGaName":3167,"dataGaLocation":3392},{"text":3169,"config":3576},{"href":3171,"dataGaName":3172,"dataGaLocation":3392},{"text":3174,"config":3578},{"href":3176,"dataGaName":3177,"dataGaLocation":3392},{"text":3179,"config":3580},{"href":3181,"dataGaName":1221,"dataGaLocation":3392},{"text":3186,"config":3582},{"href":3188,"dataGaName":3189,"dataGaLocation":3392},{"text":3191,"config":3584},{"href":3193,"dataGaName":3194,"dataGaLocation":3392},{"text":3196,"config":3586},{"href":3198,"dataGaName":3199,"dataGaLocation":3392},{"text":3201,"config":3588},{"href":3203,"dataGaName":3204,"dataGaLocation":3392},{"text":3206,"config":3590},{"href":3208,"dataGaName":3209,"dataGaLocation":3392},{"text":3268,"config":3592},{"href":3270,"dataGaName":3271,"dataGaLocation":3392},{"text":3594,"config":3595},"What's new",{"href":3217,"dataGaName":3218,"dataGaLocation":3392},{"text":3597,"config":3598},"All resources",{"href":3153,"dataGaName":3599,"dataGaLocation":3392},"all resources",{"title":3220,"links":3601},[3602,3604,3606,3608,3610,3612,3614,3616,3618,3622,3627],{"text":3228,"config":3603},{"href":3230,"dataGaName":3222,"dataGaLocation":3392},{"text":1200,"config":3605},{"href":3234,"dataGaName":3235,"dataGaLocation":3392},{"text":3238,"config":3607},{"href":3240,"dataGaName":3241,"dataGaLocation":3392},{"text":3243,"config":3609},{"href":3245,"dataGaName":3246,"dataGaLocation":3392},{"text":3248,"config":3611},{"href":3250,"dataGaName":3251,"dataGaLocation":3392},{"text":3253,"config":3613},{"href":3255,"dataGaName":3256,"dataGaLocation":3392},{"text":3258,"config":3615},{"href":3260,"dataGaName":3261,"dataGaLocation":3392},{"text":3263,"config":3617},{"href":3265,"dataGaName":3266,"dataGaLocation":3392},{"text":3619,"config":3620},"Sustainability",{"href":3621,"dataGaName":3619,"dataGaLocation":3392},"/sustainability/",{"text":3623,"config":3624},"Diversity, inclusion and belonging (DIB)",{"href":3625,"dataGaName":3626,"dataGaLocation":3392},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":3628,"config":3629},"Modern Slavery Transparency Statement",{"href":3630,"dataGaName":3631,"dataGaLocation":3392},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":3633},[3634,3637,3640],{"text":3635,"config":3636},"Terms",{"href":3443,"dataGaName":3444,"dataGaLocation":3392},{"text":3638,"config":3639},"Cookies",{"dataGaName":3453,"dataGaLocation":3392,"id":3454,"isOneTrustButton":316},{"text":3641,"config":3642},"Privacy",{"href":3448,"dataGaName":3449,"dataGaLocation":3392},[3644],{"id":3645,"title":7,"body":2921,"config":3646,"content":3648,"description":2921,"extension":915,"meta":3652,"navigation":316,"path":3653,"seo":3654,"stem":3655,"__hash__":3656},"blogAuthors/en-us/blog/authors/marco-lenzo.yml",{"template":3647},"BlogAuthor",{"name":7,"config":3649},{"headshot":3650,"ctfId":3651},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659488/Blog/Author%20Headshots/gitlab-logo-extra-whitespace.png","Marco-Lenzo",{},"/en-us/blog/authors/marco-lenzo",{},"en-us/blog/authors/marco-lenzo","fjO3SQv6yZkg2dO9SrHRmJN-ehG8IOEmmPMCf-0kzGo",[3658,3666,3674],{"title":3659,"description":3660,"heroImage":3661,"category":2917,"date":3662,"authors":3663,"slug":3665,"externalUrl":2921},"Two front doors: Module-level access in a Django GRC app","A technical deep-dive on restructuring a Django internal tool to support two distinct GitLab teams: Security Compliance and Internal Audit.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097663/Blog/Hero%20Images/Blog/Hero%20Images/Workflow%201800x945_2gQoQIbY9NvjLFpXtsxtXy_1750097663612.png","2026-10-05",[3664],"Donovan Felton","module-level-access-in-a-django-grc-app",{"title":3667,"description":3668,"heroImage":3669,"category":2917,"date":3670,"authors":3671,"slug":3673,"externalUrl":2921},"How GitLab reduced code-per-agentic-flow ratio by 45%","Learn how Flow Registry, a component-based framework that compiles YAML configurations into fully functional LangGraph flows, helped improve scalability and more.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1774465167/n5hlvrsrheadeccyr1oz.png","2026-09-22",[3672],"Mikolaj Wawrzyniak","how-gitlab-reduced-code-per-agentic-flow-ratio",{"title":3675,"description":3676,"heroImage":3677,"category":2917,"date":3678,"authors":3679,"slug":3681,"externalUrl":2921},"Co-Create: Building GitLab with our users","How GitLab users collaborated with us to build practical product improvements in the first half of 2026.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099203/Blog/Hero%20Images/Blog/Hero%20Images/blog-image-template-1800x945%20%2820%29_2bJGC5ZP3WheoqzlLT05C5_1750099203484.png","2026-09-10",[3680],"Isa Huerga","co-create-h1-2026",{"header":3683,"text":3684,"button":3688,"disclaimer":3691},"Start your free\n\n30-day GitLab trial\n",[3685,3686,3687],"GitLab Duo Agent Platform access","Try our most advanced features","Automate complex tasks with AI",{"text":2952,"config":3689},{"href":3690,"dataGaName":2955,"dataGaLocation":3189},"https://gitlab.com/-/trials/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/blog/","No credit card required.",{"header":3693,"blurb":3694,"button":3695,"secondaryButton":3700},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":3696,"config":3697},"Get your free trial",{"href":3698,"dataGaName":2955,"dataGaLocation":3699},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":3430,"config":3701},{"href":3282,"dataGaName":2960,"dataGaLocation":3699},1792010312764]