 Okay, it works, all right, it's okay. All right, so hi, my name is Soda, and today I'll be talking a bit about Docker and Ruby and Rails development. So before I start, I just want to get a few of, how many of you are doing Ruby and Rails? All right. So I will be going through some of the introduction to Ruby as well, so that you have the context on what I'm talking about. So I actually work in this lab called Experimental Systems Technology Lab. We're actually based in the Ministry of Education. So what I'm trying to do is to help develop some of the tools for the teachers and the students, so to help them improve their educational landscape. So if you're interested and if you're a Singaporean, then do come look for me. We do some cool stuff. So I'm just going to skip past this very quickly. I think more than 50 percent of us know what Docker is. So the thing that I want to call out is that actually, Docker is not really the container runtime. Like OpenVZ and LXD, they actually make use of the kernel features to run containers, and for Docker, they actually use libcontainers. So that's something that people might not be aware of, and which brings us to this point that Docker is actually an opinionated way of running and using application in containers. So what that means is that it's not like a VM. I think some of you guys may be just starting out with Docker. So I might think that, oh, let's just put all, let's just treat a container like a VM, and we'll just stuff my Nginx, stuff my Ruby application, or some of my database together in one big container. But that's actually not the right way to do things, and then I'll explain a bit on why you shouldn't do that. But basically, they're very opinionated, and if you don't go with what Docker wants you to go with, you find yourself running to a lot of roadblocks. That's my experience. So you have this idea on one process per container. So it has set up a bit of a controversy in the community. So people, some of them are against the idea, but I think if you really want to scale and you want to use different orchestration tools, you kind of have to bind to this idea. So every container should only have one application running on top of it. So you put your Nginx in one container, and then you put your app, and you put your database. So what it allows you to do is let you scale each of the components of the application independently of the other application. So for instance, if a Rails app, which if you do Rails, you know that they are horrendously slow and they're also good. So what I can do, you can spin up multiple Rails containers from which you can run and no balance them across Nginx. So you don't scale an entire infrastructure across many times. And this is the great thing. So I'm going to just go a bit on why I like Docker. So let's say you're going to install Postgres, and then if you're a Mac user, you go like brew install Postgres. If you're a Ubuntu user, you use like Appget install Postgres. And the Windows guy just use some GUI nonsense to get Postgres in. So what you can do is you can replace the installation of all your application with just one command, which is basically Docker pull Postgres QL. So you might be thinking, well, I'm a Mac user. I don't give a s**t about Ubuntu users, right? Like they can handle, they can install their Postgres however way they want to. But what happens if you're dealing with some legacy application, which requires you to use an older version of Postgres. Now, if you're on Mac, you have to do this nasty looking thing of homebrew to try and get the correct version of Postgres. If you're on Ubuntu, you have to go and get the app report, and you need to get a Postgres report and install the correct version there. And then for Windows, you do more GUI nonsense to get Postgres 9.4, right? But the cool thing about Docker is that, well, you just need to change the command and pull the right image. And then you have Postgres 9.4 and that's your dependency within your application. And the other way, the other good thing is that it converts why I was down left, which is different ways of running application into a single unified way of running application. So you don't have to care about how I'm going to run Postgres, how I'm going to run my Rails app, and how I run my factorial server. I don't know how to run it, right? But I know that it's probably going to be like this if there's a Docker image for it. So I think that's a really cool thing about Docker. And so, yeah, so basically Docker file is like an Ansible shell script which you use to create a Docker image which represents like a hard disk or your VMDK or your virtual hard disk. And then your Docker container is your running VM. So this is the basic idea of what Docker is about in terms of the tools that you use at the very basic level. So the high level you do stuff like compose and so on, but I'm not going to go into that. I think you mentioned this a bit earlier. So it's important to differentiate in my opinion between the Docker engine, which is the actual runtime for containers as well as from the Docker ecosystem which are all the tools like swarm and compose. Because I mean, if when I first started out, I get a bit confused over like all the resources online. And it's very called Docker is a way to run containers. And recently they have been introducing a lot more tools about swarm and compose on orchestration. And that I think that has caused some tension between the different communities. Like I think the red hat people are not very happy about Docker moving more and more towards orchestration and being containers. So you don't have to be locked in to the entire ecosystem if you just want to use containers. Because I think containers is the way to go. Personally, I'm not very convinced that Docker ecosystem is the way to go. But the reason why I point it out is that I had an argument with my infrared on what Docker is and what it isn't. So just to clarify that, there's actually two different things that we are talking about when we talk about Docker. And right now what I'm going to describe today is the Docker engine part, which is just the runtime. So, okay, a bit on Ruby on Rails. So it's a web framework. It is also an operator web framework. So with it, it comes with a lot of baggages, a lot of existing tools, a lot of very cool stuff that people has built on top of Ruby on Rails. And some of these actually hindered our experience when trying to Dockerize our Ruby on Rails app because there's so much tools involved. So the way that Ruby manages dependencies is through Ruby gems. So basically you have a text file that writes the gem of your dependencies. I want Rails, I want bootstrap. And then you run this bundle install command and then Ruby will grab all these dependencies onto your application. So this is how they manage the dependencies. And there's also this idea in Rails which is the assets pipeline. So what they do is you write your JS file or your CSS file and then you run through this rake assets pre-compile. And then what they'll do is that they will squash all the JS files together into one combined file which is MIMI file, ugly file that you use for production. So in the left side of development, you will use the plain text JS file in your dev. And then in production, you will use this MIMI file and ugly JavaScript file. And the typical Ruby on Rails app look like this. You have Nginx, the access web, the loop balancer. The Nginx will also pick up the static assets which is pre-compiled earlier as I described. And then you'll serve those static assets. Then you follow the subsequent request to the Rails server which will do your actual Rails processing. Then in the back end, you have Redis, most likely to manage your sessions. And then you have your database from which you will store your files. So now I will talk a bit about how we use Docker and Rails in development, test and production. So in dev. So let's talk about this hypothetical example, right? So one use case is that, well, if you have John the intern coming in, your smiley face, and then ready to get to work. And then you say, hey, why don't you just to get your dev environment set up, just run these few commands, and then you get to work. Sounds really cool, right? And now you have your senior dev who's a bit more old, a bit more jaded about things, about new technology in general. And you tell him, hey, you know what, this cool Docker thing, what it allows you to do is to achieve this development production parity. So you can code, code, code, you just generate an image, once you're done in your dev containers. So once you're done, you bring this thing to your production machine, and then you run your production containers. So you achieve the so-called development and production parity, right? So what we have realized is that this is actually a unicorn setup. Both cases does not truly reflect on what you can actually do with Docker. It sounds like a very tempting idea, and I'm going to explain why it doesn't really work out. In my opinion, I mean, if you guys have, I mean, if you guys disagree with what I say, feel free to come and talk to me about this. But the basic problem with this is development tools. So what I mean by that, as developers code, developers come up with different toolings to help them code more effectively. And some of these tooling you cannot, and it doesn't really fit very well within the entire ecosystem, and it's very apparent in the Rails, and Ruby and Rails development setup. So the first way, the way, so there's probably two approach that I can achieve the first thing which I talked about, which is that you want to achieve this thing where you can download and you're going to get John the intern setup with the system. So one thing you can do is you can move all your Ruby or Node.js, all your dependencies for the application into one image. And then you say, okay, you can now run my container with this image, and then on your operating system, you will then mount a volume containing all your application files onto this container, and then you run the gems in there. And then you'll be able to not install Ruby and not install all your dependencies on your machine, yet still able to develop Ruby code. Now the trouble is with this part here, like all your workflow in your Git, there are some toolings which doesn't allow you to so easily translate outside of your environment. So what I mean to say by that is that some tooling requires you to have Ruby dependencies, and now you're stuck because you have to run Docker exact, run some commands within that Ruby container to get the work. So developers have a lot of resistance because they're no longer, they're not familiar with the work, they're not familiar with the commands that they're aware anymore, and now they have to relearn all the stuff that they have done. The other thing is that, what's the other thing with this? Yeah, so for the Mac users, this is a very, very painful setup for them because before there is native Docker for Mac, what you have to do is you have to spin a virtual machine up here, and then you'll run into the mess of, I need to tell my dev, hey, access this IP to preview our site, and that's very, very painful, and it's very, very counter-intuitive to what they have been promised. So the next thing you can do is, okay, fine, so some of the dev tools, since some of the dev tools are outside, why not I just move all my dev tools inside the image and then you just code off the image, like you just spawn, the first thing you do is you spawn a container, and then you just do all your coding inside there. So the problem with that is that now you have forced everybody to use basically the same set of dev tools, which is a problem, right? Like, who uses VIM? Who uses sublime text? And it would be mine. I have colleagues who do that. So now we have to standardize across every single employee within the company and say that, no, all of you learn VIM now. I mean, I would say that, but my developers will not buy it, so that is the problem there. So these two approaches, none of it is very good, and this is why the first case doesn't really work out so well. And for the second case, you will run the same problem because your dev tools, some dev tools that is present in your application, you don't want them in your production image. And in your production image where you want to have minified assets, you don't want them in your development because they make your developers life bad. So what makes more sense to us is having a staging QA production parity means that you generate a master image from which you run in your product, in your staging setup, for which you can test against that. So your dev, you just do your normal dev. So what we do, right, so this is just to show how much time we have spent trying to automate a very simple thing. And we have to find a balance and for us it's been hitting, we've been trying it so hard and then it doesn't really work out. So what we do now is basically on your dev machine, you'll get the developers to install all the Ruby dependencies, all the dependencies with regards to development will be on the host machine. But things like your database, things like your Redis, things like your external database in external systems, you will use containers to launch them. So with this hybrid setup where your local machine and your local tools are within your machine, but you still get to code against something that is more reputable rather than installing all this database stuff because I hate installing database on my machine and it's terrible. So in a test setup, so because we are the ministry, we cannot use Travis and we cannot use CodeShip, we cannot deploy our code on AWS, we cannot deploy our code on DigitalOcean. So we have our own different set of challenges that we face that is probably a bit unique. So we have to build this whole continuous ECI pipeline. So I mean, this is quite basic actually, like on coming to master on a Git server, we will generate the test image from which we'll run our test on. We'll elaborate this a bit later. We will have a security container that runs with the amount of volume of the app inside this security container, which they will test for vulnerabilities and within the gems as well as some of the coding practices. And we will generate a master image from which we will deploy to our staging. So once this master image is built, we will push it into a Docker registry that we host in-house. And our CS server will then trigger a deployment on job success onto the staging server and say, hey, go ahead and pull this new image now. So once the developer pushed any code to master, we would, I think maybe after five minutes, the code change will be refactored live on the staging server from which we will go in the server, we will test it out, we will try. And once you're happy with the state of the application in staging, we will move it into the production. So what it allows us to do is that we know that this master image will behave somewhat consistently in both the staging environment as well as the production environment. So this is the part where we use Docker as a bit of a test runner. So anybody familiar with Selenium? Right, so basically is you will launch a browser and you will do those things. So the nice thing about Dockerizing this whole test setup is that now your test runner is, how should I put it? Your test runner can be anywhere right now. So on our CS server, we will run our test on the CS server. But let's say if anything fails, our developer can run the exact same setup on the dev machine. And if you have tried installing Selenium onto a Linux machine before, on a headless machine, you realize that you can install accept beefy buffer so that it can generate the view from which you can run a test on and all bunch of nasty stuff. But after you containerize all our setup, it doesn't matter what the base machine is anymore because all we need to do is install Docker and then we'll run a test setup there. So I'll give a demo on this thing a bit later after I finish the presentation. So some of the problems that we face in tests is that downloading RubyGems is very long. So the server is in US and then you do this like bundle install thing which takes like three minutes. So your test basically takes three minutes that is just downloading gems, right? And if you use Docker, you realize that yes, there's cache. There's a Docker cache which cache that particular instruction when you do a bundle install. But what it doesn't do is that if you modify the gem file even to remove gems, it will trigger the full download again. So that is really very painful. So how we solve this problem is that we actually created a gems volume inside our setup. So in our CS server, we have a gem volume which will mount the gems inside the app. And then when it install, it will basically install into this common gem volume which we mounted on all the different containers as you run a force of sequence test. So if you have another application, you can also link it up with the same gems container and they actually reuse the gems. So your gems don't download, you don't download gems every single time you do a build even though the project. So you can reuse the gems that you have downloaded. Right, so in production. So whatever I just described earlier, actually it only makes sense for tests and development. In production, you actually want your bundle image to have all the gems downloaded. So if you have to pay the cost when you build this image, you have to pay the cost. Because the whole idea is that your image should be entirely self-contained. And that allows you to bring this image to whatever machine that has Docker and it will run. So if you have this additional dependency here where there's this, where you have this restriction of I have to have a gem volume in my thing, then you kind of miss the whole point of using Docker in the first place. So no shame on gems in production. And the other thing that I find really useful is to create your own base image. So it's very tempting to say that we will just use the base image from one of the base image that is given in Docker Hub and then I'll add more instructions on them. If you have two or three different Ruby application which share the same common base, you should actually have another image from which to install all your dependencies on. So for us, we have like, I think the base Ruby image doesn't have like postgres drivers and some XML stuff that we need for Ruby. So we actually build another image with all these dependencies on top of them and then we'll base our application on top of that image instead. And so this is the last problem that we encountered in production. Basically, your static assets are compiled together in your production image. So if you launch your Rails app, it's very hard for your Nginx to actually pick up the static image in another container because they are in different containers, right? So you need a way to fit these static assets from another container. So in container setup, this is how it actually looks like. So how does Nginx know where to pick these things from? So the solution in this case is also to use volumes. So what you do is that you have an assets volume from which you put all your assets in and then Rails, you run this rig assets command at the time where you deploy to make an update onto the assets volume. So there are some issues with doing it this way. So if you're interested and if you do Ruby, then we can talk about how we can solve these issues. But generally the solution is the same, just use volumes, right? So lastly, I'll talk a bit about the pain points that we have encountered. So the biggest challenge is to actually decouple and, right, so here's how I put it. Your application that you run in the Docker and run on your host OS, they are very similar yet they are actually very, very different. So how you wanna think of application is actually like mini VMs which is only running one single application. Part of the problem is that if you have a real server, it's very tempting to go like, my DB is in local host 5.432 and then when you build an image, you will realize that it doesn't work because the local host actually means the local host within that particular container. So you have to kind of tell yourself that every container that I run is a separate little VM and I have to network them instead of doing what I can do normally. So normally what you could do is you can access the files in the same file system, you can access the ports in the same file system but it doesn't work out once you start to containerize application and you run into a lot of trouble if you don't get this right. You run into very silly things like cannot access database in local host and you have no idea why but it's because the machine is actually on a different place. And the last thing is that I'm not sure how many of you have encountered permissions problem but our security guy don't like us to run root, don't like us to run the containers as root. So we have to do a lot of, we have to create a new user in the image in a container and then you'll run as a different user and then you realize that the files that is mounted onto the container actually have all different permissions so you can't access the files that you could have otherwise. This is a problem which I think Docker hasn't really solved right yet but basically they've been telling everybody to run the containers as root so nobody has this issue but if you face a security guy in your team then this is the kind of problems that you're run into. There is a whole topic about username space and how they work and 10 methods and all this kind of thing. I mean in subsequent meetup I will talk about those but in general this is just a bit nasty. And yeah so I've come to the end so this is actually our experience working on Docker and Ruby on Rails. So again if you're interested if you do Ruby on Rails development and you wanna come and talk to me about the stuff that I've talked about then feel free to come to me after the talk. Thank you. Yes? So regarding the file permissions how did you solve it? Did you create like an entry script or? How did we solve the permission problem? The, it's actually not so bad because once you, what we do is we set all the files to all right, all readable. I know it kind of defeats the whole purpose there but we kind of haven't really found a good way to solve this issue yet. The trouble is that there is way you can mount volumes as 10FS so we can say that this particular volume I wanna mount as a temporary file system but and that all just messes up the volume permissions like the second time you run it you have a different set of permissions. So yes, right now we just do A plus R and then we just call it a day but there's probably a better way to solve this. Yeah I thought about like doing an entry script but the entry script wouldn't, basically if you start the container the entry script would then own the files but if you have, if the same files need to be accessible to different user IDs that will be initiated then. So this is where they haven't really nailed. So wait, I need to show you the demo which I wanna show you the demo so, right. Okay, okay so in a typical real setup you will run like this aspect command and then you'll run, you'll create, you will start to run the test for the application. So you'll launch a nice browser on your end and then you will, something's happened so, wait hold on, right so you will run, you'll start running a test and then you will do these fancy food things. So if you wanna replicate this on your own Hitler server it'd be very, very, it'd be very painful thing to do. So what we did is we launched a bunch of, so there's this nice image called the Selenium image. What it allows you to do is it will create a container where you can VPN into and then in that container has all the accept FB dependencies and Chrome dependencies. So this test script basically would create all the different containers, initialize the database for the different stuff and then what you can do is you can do this VNC viewer and then you have this. So this is actually running inside a container. So your Rails app will talk to this container and you will launch a browser within this container. So, right, so it's running a test now. So the thing that we realized is that running the test this way is a bit slower than not but if we run it on our CS server on build it doesn't really matter. This is running on machine, but it's running as containers. So you see the container is spinning up and then you would do the Selenium test there. Okay, cool.