Devreal

Scale By The Bay 2020: James Ward, Packaging & Deployment Options for Scala Applications / Services

Scale By The Bay 2020: James Ward, Packaging & Deployment Options for Scala Applications / Services

Recording: Scale By The Bay 2020: James Ward, Packaging & Deployment Options for Scala Applications / Services

[Music] hi i'm james ward a developer advocate at google the session is on comparing docker methods with scala and so we're going to go through some different ways to dockerize applications and so if you want to get to the source code for this it's all my github github.com james ward comparing docker methods scala okay so let's dive right in to this project so i've got a scholar project here a really basic one and of course we can run this thing so if we come in and just do our spt run we can see it's just gonna be a little web application that's going to just respond with hello world so let's go check this thing out once it compiles here in our browser there we go up and running let's go to localhost 8080 we see our hello world great so we can see that this thing works let's go see what the code is it's not very interesting and is just about as basic as we can get with no dependencies i'm using scholar 3 so there's some fancy syntax here but you'll see i've just got an http server an http handler that says hello world and wire all that stuff up so that's my basic application to containerize this thing we need to first be able to package it up into everything that's needed to run this on the jvm and so to do this initially what i'm doing is i'm using the sbt native packager plugin and then if we look over in my build.spt i've told it to enable the plugin java server app packaging so what this enables us to do is go and run sbt and then stage we run sbt stage what this is going to do is take our application and the dependencies package those all up create some start scripts and we can see those in the target universal stage directory so that we've got a bin and a lib if we look in our lib there's not really many dependencies in this case just the skull library and my application and then we've got the start script to start it so we can actually run that thing to see how it works let's go to here and then let's start it up and we'll see sure enough it starts just the same way that did before okay so we are able to package up our application but now let's let's package it into a docker container and so the easiest way to do this is we're going to use what's called build packs and i've got the pack tool installed and i've got docker installed locally and now what we can do is run pack build we do not tell it which build packs we want to use i'm going to use the heroku build packs because those have support for spt and then i'm going to tell it the the docker container name that i want to package it into so now you'll see that the roku build packs are running on this project it has detected hey this is an sbt project and i know how to build an sbt project so it's going to install the jdk run sbt compile stage and so that's going to do the packaging just like we saw before so now this is actually running inside of a docker container itself and so that provides a nice reproducible way for us to package our application up okay there was some cash stuff that that it pulled from so it didn't have to do a full rebuilding on that so it ran faster the second time around which you didn't see the first time around but it would download the dependencies and cache those and that sort of thing okay so now that we've got our docker container packaged up let's make sure that this thing can run with docker so i'm going to do my docker run i'm going to set the port environment variable map the port 8080 and run that thing and you'll see it's listening on port 8080. so if we connect to it it's running just like it did before okay so let's go look and see what's inside of this container to do so i'm going to use a tool called dive and i'm going to go inspect that that build pack created container image and then we'll be able to look and see what all is inside of this container image so here we go here we see an overview of my container image you'll see that my total size of my container image is pretty large 691 megabytes and we can see what's actually in each layer layering in docker containers is nice because that provides us with a great way to do caching so if i've already downloaded a particular layer then i don't need to do it again and that means i can invalidate higher up layers of my application and it won't have to reload lower layers that sort of thing so if we look through and see this base this base layer 63 megs this looks like it's the base operating system and then it adds in some more pieces into this we get to this large 466 mig layer which is the operating system some other stuff in there and then if we continue walking up we can see that it adds the heroku build packs add some other things in then we get to this 159 meg layer and in here we can see it adds in our workspace and our workspace has in it our application has the target directory has the jdk that was installed so that's a pretty large layer here so um we can see all the stuff that we saw in there okay and then there's a few other layers for some build pack needed stuff so this particular build build packs created container image is fairly large you can use other build packs that do a bit better job at creating more optimized layering in the the container image but let's move on to the next method which we'll see better layering for our container image and more optimize so what i'm going to do is check out the jib branch jib is an open source tool that is sponsored by google and it is able to create container images and has plugins for maven and gradle are the main supported ones but then there's a community one for sbt okay so um let's go take a look at our plugins uh in sbt you'll see that we're using the sbt jib plugin and one of the nice things about jib is that if you run it locally and want to install the container image into your local docker daemon you can do that but if you're doing a remote docker daemon you don't actually have to have docker running locally so that's a nice feature of jib let's go see how it's configured so i've got some configuration settings in here to tell it about the the registry the organization name version and then the base image that we want to use and then i have a way to override that with a environment variable so that i can easily switch out the the default settings for where it stores that container image okay and then same source code as before let's go take a look at our instructions we're just going to run this sbt jib docker build and so let's go do that so now this will use that jib tool to take our application and turn it into a container so it's running jib on this project and we'll assemble those layers assemble our container and then again we'll use dive to go check and see what's actually in it so that worked pretty fast there's good incremental support in jib so now let's use our dive tool and let's go inspect our container image which is our scala web app and then the tag here is jib okay so now you'll see this image is a lot nicer in terms of size 138 megs we see some base layers here which are the base operating system and the jvm and we can walk through those layers and see what's going on but let's go look now at the 7.2 megabyte layer and so this is a layer just for our dependencies in our applications you'll see that uh that we've got the dependencies here on our scala library and uh and so that's nice to have a layer with just our dependencies because then if we don't change our dependencies then we don't have to invalidate that particular the cache of that particular layer okay then if we go to the next layer then we'll see here's my compiled classes for my application so so it's nice to have those separated out creates a not a lot more concise and better cache friendly container image so that's jib and again if you if you want to use it not against a docker container image then you can or not against a local docker daemon then you can run a different task which is jib and then image build and then we would need to set that image url in my case to tell it the coordinates for where to store that but you wouldn't need a local docker daemon so that's a nice thing about using jib okay so that's um that is a great option for containerizing your scala applications now let's go on to uh the next one so let's check out another branch here which is going to be the um docker branch and now in in this example what i'm using is back to the sbt native packager but in this case i'm actually going to use docker as the way to containerize this application you may be familiar with the kind of traditional way of containerizing applications which is you'll do like docker build and tell it to build the stuff in dot and then you have to have a docker file uh that then docker reads as the instructions for how to assemble the container and so sbt native packager has kind of a nice way to to do that type of containerization so let's go take a look at our build sbt so we need to have a way to package up our application like before so in this case i'm using a launcher jar plug-in and then i'm also also have the docker plug-in from sbt native packager and then i have to set some settings for how to package this thing up and how to start it and then like before we're telling it how to override the default coordinates for where to store this container image if we want to store it on a remote container registry so let's go run this one and the way that we do that is we run sbt docker publish local for publishing to our local docker daemon so like before this is going to take our application and turn it into a docker container and it's going to do that through packaging up the application using the uh if we look back in our build.sbt using the launcher jar plug-in we could have also used the one that we used in the in the beginning but this one i like this one a little bit better for how it packages up the application so we can go look and see how this thing actually built the image and let's go look in the target directory and then docker and then stage and you'll see that there's a docker file here and then there's two layers that it has added here so this is the different layers you'll see the op docker bin lib stuff so we get our lib that has our launcher jar and our dependencies and then if we look into stage two then we see our actual application so we can see where the sbt native packager stored those but we can also now go look at the docker file that was created so i'm going to actually pull that up here in vs code and if we look at our target directory then docker stage so we can see that what the spt native packager plugin actually did was really just to package the application and then uh and then create a docker file that then it runs docker build against to containerize the application so that's how how it worked in that case and now let's go use dive again to go check out this uh this container so this is the scala web app and then the docker tag on there and so uh almost i think just about the same exact size as the jib one uh because we're using the same base image in this case and you can see that in my settings build.sbt you'll see that i'm using the distro list java distribution there so the same base images and then if we look here at our 7.3 megabyte layer you'll see that it's organized a bit differently than jib organized it but essentially the same we've got our our dependencies in a given layer and then we've got our compiled application one difference is that it compiled our application into a jar file whereas jib left it as class files i don't know if any either of those is particularly better but at least we have the nice layering in this case okay so that is our our sbt native packager option for using docker underneath the covers to build this and there's a lot of flexibility with this particular method and can result in in nice concise images like you just saw okay so i do want to show you uh one of the nice things with container images before we get on to gravim the last example is that uh once we have things in container images we can run those things really anywhere and so i'm going to go to one of these methods let's go to the jib one here and i'm on the jib branch in that repo and i just want to show you how we can take this container image and put it onto a container registry to do this i'm going to use this run on google cloud button here and this will walk through a couple steps so yes i trust my repo and then it's going to launch me into cloud shell and in cloud shell it's going to do some steps for me to get this application built and then up and running yes and i want to authorize it to do all that stuff okay so it's cleaning that repo i need to tell it the project that i want to use and then yes us central so it's doing the sbt docker build come doing the compile and doing the build of the container image and then then it will take that container build take what it built turn it into a container image okay so that's going to take a minute to run because it needs to do a bunch of stuff while that's running let me go show you where it's going to store these container images and we could also of course do this all locally but if we go over to the container registry this is where it's going to actually store that container image so if we search for comparing docker method scala we'll see that i've built some images here's the jib one that i built recently but once we get the container image built in onto container registry then of course we can run this thing wherever you can run containers so you can run it on kubernetes you can run it on just any host with docker gce if you're on google cloud and you can run this thing on on cloud run which is serverless for containers okay so this has finished building the container image it's done the docker push of that container image to the container registry and then of course we can deploy it wherever it can run container so i'm using gcloud run deploy deploying that container image and now that it's up and running we can go check that container image out on the cloud and it's going to start up that application and great we're up and running on the cloud cool so uh you can run those container images really anywhere and so if you go to uh to that repo and go to those and click that run on google cloud button then that's an easy way to kick the tires and see how that works okay but now let's move on to our last example where what we're going to do is is use graviem to create a optimized version of our application ahead of time compiled version of our application and we're going to do some some nice tricks to with that as well let's check out the grawl vm branch and if we go take a look at what's in here well we'll see first let's look at our plugins spt i'm still using the sbt native packager to package the application and then i'm using this graviem native image plugin and this allows me to use gravium native image to take my application and compile it down to a native image i did have to go down to scala 213 because i'm waiting on a dependency that hasn't yet been released first call of three and then we specify some options for graw vm so first i want to create a static image static image is statically compiled and so and then there's a way that we can include all of the system dependencies that are needed through a library called mucil in here as well and then there is a thing in native image where you can tell it to fall back to running in jvm if it can't create the native image and i don't want to do that and grab vm also has a way to use a server i'm going to install some exit handlers okay so those are my graviem native image options so let's go open up our docker file so we can see what's going on but what i want to show you first is that we can't just use spt and run the thing to create the native image directly and this will this will actually run native image gravim native image within a docker container to create that so that's great that we can create the uh the native image for our application but what i want to do is i want to create that native image but then i want to package that native image up into a container image as well and so there's a few things that i need to do and so i've added a docker file to this repo for this example so that i can do some other things on top of just creating that native image so the first thing is that i need to i need to be able to build my native image and so i'm starting with the container image which is my girl vm community edition there and i'm telling it that this is my builder image so what this means is that what happens in in these set of steps in my docker file are actually going to get thrown out of my my final container image so i copy my program in i install native image and then i need to do some stuff to do my static compilation with mucil so there's some stuff here uh which i just got from uh github uh as an example uh an example there for how to compile what's needed for uh for native image to do the the static statically linked image okay so then i create my my native image and then what i'm going to do is i'm going to say from scratch so start with a container image that has nothing in it and copy what was compiled that that natively compiled executable copy that in as slash web app and so and then set the entry point so that's what's going to sets what's going to be in my final container image so let's go uh and and run that now and let's get the command here for my docker build okay run that docker build we're going to set the tag on my container image to scholar web app crawl vm and so now it's running all this stuff within docker and so this is going to take a minute to go through and uh and do all the compilation that's needed for native image to do the static linking all that kind of stuff okay and then it's going to create the container image which is the the longer part of all that so the nice thing about doing the ahead of time compilation on this project is that that it will be able to con create a executable that doesn't have any of the unused stuff and it doesn't have the jvm so it doesn't have the overhead of starting up the jvm and of all the other parts memory constraint memory usage stuff that the jvm uses and that kind of thing this is especially useful for for auto scaling environments where you want to be able to scale down and scale up quickly and when we're using graw vm it allows us to to get much much faster startup times and also much smaller container images which we'll see in a minute so now is uh the the long part of this build where it's got to actually compile the application and then and then take the compiled application and turn it into uh turn it into that container image native native image and then container image okay so that's going to take a minute to run so we'll talk a little bit more about grovium and some of the nice things here as we get those benefits for startup performance but there are some challenges and some trade-offs to do in that one is that we grall vm needs to know about reflection or you need to not use reflection luckily in scala we don't do a lot of reflection and so typically and so so it can be sometimes easier to to deal with with that constraint and grow the m but there's also some other challenging pieces to grovium because it's doing all this ahead of time compilation things like method handles sometimes can be challenging and so that's actually why in this particular project why i had to go down to scala 2 13 is that to work around one of the issues with with method handles i had to include this scala meta svm subs to to enable graviem to work around that and that wasn't released yet for scholar three so so there are some challenges to being able to do uh gravim native image stuff but there's cases where it certainly can be worth it okay my application is now compiled and it's compiled into a jar file and so one of the nice things about crawl vm is that when i'm doing local development i'm using the jvm i'm running in jvm i've got some hot reload cycle that is making my developer productivity nice and and nice tight loop for testing my changes but then in my production ci cd system is where i actually do my graw vm build into that taking the the jar application the jar and turn it into that native executable and so because that takes a while uh to do this part of turning into the native image it's not something that i want to have as part of my typical developer cycle it's one it's something that i leave just for ci cd but i'm showing it to you here so we can actually see how this all works and get it all up and running okay so now it's actually doing the graw vm ahead of time compilation and that takes a while to see that took a minute to do on that very small simple application but great it was able to turn that application into a native image and then it turned uh and then it added that into a container image so now let's do our dive into this container image so you can see what's running up in there and let's check it out okay so now you'll see that my whole container image is only 12 megabytes i didn't have to have a base operating system everything that was needed is included in this 12 megabyte container image so no jvm no operating system and then we statically compiled that mucil library with the parts the the native bindings that were needed there and so nice uh small container image okay just to validate that this is this is all working let's run this thing just so you can see sure enough it it works so we run that thing should start up super fast because we don't have any layers we don't have any jvm to start and so yeah so that's our graviem and of course you can run this container image that nice small container image anywhere that container images run so on kubernetes on cloudron or wherever you want to run your containers okay so those are our four methods that i wanted to walk through for comparing docker methods in scala there's build packs jib the kind of traditional docker method with some made a little bit nicer by the sbt native packager plug-in and then graw vm which uses both the dockerfile and spt native packager so in terms of what you should use i like build packs when i want something just to be easy don't want to have to think about it build packs work on lots of different types of projects so you can run build packs on maven projects and gradle projects on ruby projects node go whatever and build packs will do it there's also a really great build pack ecosystem out there that has lots of different build packs there's the pacquiao build packs there's a google cloud build packs so it's really great when you don't want to think about how your application gets turned into a container image if you want to do a little bit of work then you can use jib which is nice provides creates nice concise container images and then you could use the sbt native packager plugin which had pretty similar results and packaging for as jib did but underneath the covers what that was using was docker and so if you are in an environment where you don't want to use docker then you can use jib and store your container images on a remote registry where so if you're okay running on docker then uh that spt native packager option using docker is great and then graw vm uh of course great if you're doing serverless stuff or need i have other needs for starting up really quickly so so yeah those are the four options that i wanted to walk through so uh hopefully that was useful and check out the source code and the different examples there and let me know what you think thanks [Music] you