A Clojure demonstration project designed to enable you to rapidly spin up and experiment with a Clojure application using Docker, Containerization, and the like.
To run locally, do one of:
- Launch a REPL and evaluate
(clj-cloud-playground.core/start)
- lein run
- lein uberjar, java -jar target/clj-cloud-playground-0.1.0-SNAPSHOT-standalone.jar
To run using Docker:
- Build the standalone app with
lein uberjar
- Build the image using
docker build --tag=clj-cloud-playground .
- Run the app using
docker run -p 3000:3000 clj-cloud-playground
. This will run you app in a local container that exposes port 3000 to port 3000 locally. You might also try these invocations:docker run -e NREPL_PORT=3001 -p 80:3000 -p 3001:3001 clj-cloud-playground
: Sets thenrepl-port
variable to to 3001 and map the container's port 3000 to local port 80. This allows you to connect to your running image and do interactive development.docker run -e IS_PRODUCTION=true -p 3000:3000 clj-cloud-playground
: Sets theis-production
environment variable to true so you can modify your internal app as appropriate.
- Create a repo at Docker's Cloud Site. In this example my repo name is
markbastian/clj-cloud-playground
. - Build the image using
docker build --tag=$REPO:$TAG .
where$REPO
and$TAG
are your repository and tag names. In this case the exact command I am using isdocker build --tag=markbastian/clj-cloud-playground:latest .
I selected a tag oflatest
arbitrarily. It can be whatever you want. - Push the image using
docker push markbastian/clj-cloud-playground:latest
. - TODO: Deploy commands...
Let's say you want to transfer your image to an EC2 instance and not use DockerHub or any other sort of repo. Assuming you have an EC2 instance running and all the correct keys and permissions set up, you can run your app in the cloud by doing the following:
- On you local machine, run
docker save -o clj-cloud-playground.tar clj-cloud-playground
. This will package up your app as a single archive. - On your local machine, run
scp -i ~/.ssh/mykey.pem clj-cloud-playground.tar ec2-user@XXXXXXXXXX.amazonaws.com:/home/ec2-user/
to copy the file to your remote machine. You must fill in the right values for your ssh key and host. - On the remote machine, run
docker load -i clj-cloud-playground.tar
to unpack and load your app. - On the remote machine, run the app using any of the same commands as you did above.
To use Amazon Elastic Container Registry (ECR) to push an image:
- Using the AWS Console, create a repository (e.g. clj-cloud-playground). It will create a repository with a URI like
XXXXXXXXXX.dkr.ecr.us-east-1.amazonaws.com/clj-cloud-playground
. - Click the button 'View push commands' at the top right to get the command sequence needed to upload your image. For this project, the commands will be along these lines:
$(aws ecr get-login --no-include-email --region us-east-1)
docker build -t clj-cloud-playground .
docker tag clj-cloud-playground:latest XXXXXXXXXX.dkr.ecr.us-east-1.amazonaws.com/clj-cloud-playground:latest
docker push XXXXXXXXXX.dkr.ecr.us-east-1.amazonaws.com/clj-cloud-playground:latest
Note that this can take several minutes.
- Once you've done the above your image should be hosted in the repo.
- To launch an EC2 instance:
- Go to the launch instance wizard
- Optional: Select free tier only instances
- Choose an Amazon Machine Image (AMI). Choose the Amazon Linux 2 AMI 64-bit (x86) option.
- Select a t2.micro instance as it is free tier eligible.
- Specify a keypair and launch the instance. This could take several minutes.
- Once the instance launches you can see it in the EC2 Instances Panel.
- Now, ssh into the EC2 instance you will be hosting your docker image. The commands to do this can be found in the "Connect" item on the Actions menu in the EC2 Instances Panel. Alternatively, there is a browser based ssh client in the connect options.
- Ensure docker is set up and running by following these instructions.
- You must also configure your EC2 instance with permissions to access ECR.
- Create a policy that allows access to your ECR repository.
- Create a role with the above policy attached.
- From the EC2 console, select your instance and choose Actions -> Instance Settings -> Attach/Replace IAM Role. Choose the role you created.
- Test your role out by executing
$(aws ecr get-login --no-include-email --region us-east-1)
from your EC2 instance. - You can now run your image with any of the
docker run
commands from above, except you need to specify the ECR Image URI in the command. For example,docker run -e NREPL_PORT=3001 -p 80:3000 -p 3001:3001 XXXXXXXXXX.dkr.ecr.us-east-1.amazonaws.com/clj-cloud-playgrod:latest
. - You now need to open port 80 for access from the outside. From the EC2 console, choose your image. At the bottom of the screen will be a link next to the Security Groups item for the image's security group. Click this.
- From the newly-opened security group tab, choose the inbound rules tab at the bottom of the screen and select edit.
- Choose "Add Rule" and select HTTP with a Source of Anywhere. If your image is running you can now navigate to its Public DNS entry (found on the EC2 page) and see your app.
- Finally, to allow REPL access add another custom security rule with the settings "Custom TCP", Protocol: TCP, Port Range: your nREPL port (e.g. 3001), Source: My IP (This is critical - don't make it anywhere), and Description: nREPL (or whatever).
- Connect to your repl using a remote connection with the IP of the EC2 instance and your configured REPL port.
- Ensure you are logged in to ECR by following these instructions.
- Launch the container with
docker run -e NREPL_PORT=3001 -p 80:3000 -p 3001:3001 XXXXXXXXXX.dkr.ecr.us-east-1.amazonaws.com/clj-cloud-playground &
. Note that the & will run the process in the background.
To stop your app, use docker ps
to identify the container id then docker stop id
(id is the container you just identified) to stop the container.
If you want an end-to-end solution, use one of the following solutions.
Heroku is probably the easiest way to create a complete app
This is a fairly straightforward way to create a complete app
Follow this guide
Sometimes when you go to revist a stack it will fail since the :stack-name entry may be deprecated. One good way to list the most recent options is to use the Cognitect AWS API. To do so, add the following dependencies to your project:
[com.cognitect.aws/api "0.8.352"]
[com.cognitect.aws/endpoints "1.1.11.651"]
[com.cognitect.aws/elasticbeanstalk "746.2.533.0"]
Then, in a repl invoke the following and look for the stack you want:
(require '[cognitect.aws.client.api :as aws])
(def ebs (aws/client {:api :elasticbeanstalk}))
(:SolutionStacks (aws/invoke ebs {:op :ListAvailableSolutionStacks}))
TODO: Create a new beanstalk plugin using the Cognitect API
By default, EBS provides only insecure (http) connections. To configure your app for https follow the directions here and here. For the second link, use a "Classic Load Balancer" on step 5.
By design, this project is meant to be live-coded with a REPL connection. The following methods for connection are pre-wired:
- If launched with a repl and the system is initialized with (start) you are already in a REPL session.
- If launched with the NREPL_PORT environment variable set and that port is open, you can connect to a REPL server on that port.
- ring-drawbridge is configured such that you can connect via
lein repl :connect http://localhost:3000/repl
where the correct server name and port are used (in this case locahost:3000).
If the aws cli or other commands start to fail, there's a good chance you have a Python problem (who doesn't?). For example, this error started randomly happening on my development box:
iMac:clj-cloud-playground mbastian$ $(aws ecr get-login --no-include-email --region us-east-1)
Traceback (most recent call last):
File "/usr/local/bin/aws", line 6, in <module>
from aws.main import main
File "/usr/local/lib/python3.7/site-packages/aws/main.py", line 23
print '%(name)s: %(endpoint)s' % {
^
SyntaxError: invalid syntax
The best way to remedy this is with the pipenv. I did the following to fix the above:
pipenv shell
pipenv install awscli
Once the above was done and everything worked in the Python virtual env.
- Better understand ring-middleware such that defaults work for all cases. Currently,
(wrap-defaults (assoc-in api-defaults [:responses :content-types] false))
handles content and the drawbridge connection correctly. - Maybe add security to ring-drawbridge. The goal of this project is not to show how to do everything, so this isn't necessarily a requirement for this project, but should absolutely be done in any real application.
- Figure out port forwarding or some other solution for drawbridge+cursive connections (https://groups.google.com/forum/#!topic/cursive/7zethDTEIXo)
- Convert this into Github pages
- Develop new beanstalk plugin with the cognitect APIs
- Docker Basics
- Running Docker on AWS from the ground up
- Running Docker on AWS EC2
- AWS Beanstalk, Docker and Clojure – The JUXT experience of deploying Docker containers through Beanstalk
- Configuring HTTPS for Your Elastic Beanstalk Environment
- Configuring Your Elastic Beanstalk Environment's Load Balancer to Terminate HTTPS
- Drawbridge - HTTP Transport for nREPL
- Live-Debugging Remote Clojure Apps with Drawbridge
- Configuring the Reverse Proxy
- How do I use my own security group for my load balancer when I deploy an AWS Elastic Beanstalk application?
- Configuring AWS Elastic Beanstalk Environments
- Elastic Beanstalk without Elastic Load Balancer: Might this be the answer along with a correct security group?
- Deploying Spring Boot Jar Application on Beanstalk Java SE Platform. This could give some clues for java jar deployment.
Copyright © 2019 Mark Bastian
This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0.
This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version, with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html.