├── .gitignore ├── Makefile ├── README.md ├── main.tf ├── output.tf ├── templates ├── cron.sh └── user_data.tpl └── variables.tf /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea/ 3 | init.txt 4 | .terraform 5 | *.pem -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile to kickoff terraform. 2 | # 3 | # Before you run this Makefile, you should set the following environment variables to authenticate with AWS, 4 | # which allows you to store and retrieve the remote state. 5 | # 6 | # export AWS_ACCESS_KEY_ID= 7 | # export AWS_SECRET_ACCESS_KEY= 8 | # export AWS_DEFAULT_REGION= 9 | # export TF_VAR_access_key=$AWS_ACCESS_KEY # exposed as access_key in the terraform scripts 10 | # export TF_VAR_secret_key=$AWS_SECRET_ACCESS_KEY 11 | 12 | # #################################################### 13 | # 14 | STATEBUCKET = mycompany-terraform 15 | PREFIX = jenkins 16 | 17 | # # Before we start test that we have the mandatory executables available 18 | EXECUTABLES = git terraform 19 | K := $(foreach exec,$(EXECUTABLES),\ 20 | $(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH, consider apt-get install $(exec)"))) 21 | # 22 | # .PHONY: all s3bucket plan 23 | 24 | 25 | .PHONY: all plan apply 26 | 27 | all: init.txt plan 28 | echo "All" 29 | 30 | plan: 31 | @echo "running terraform plan" 32 | terraform plan 33 | 34 | apply: 35 | @echo running terraform apply 36 | terraform apply 37 | 38 | destroy: 39 | @echo running terraform destroy 40 | terraform destroy 41 | 42 | # little hack target to prevent it running again without need 43 | # for second nested Makefile 44 | init.txt: 45 | @echo "initialize remote state file" 46 | terraform remote config -backend=s3 -backend-config="bucket=$(STATEBUCKET)" -backend-config="key=$(PREFIX)/terraform.tfstate" 47 | echo "ran terraform remote config -backend=s3 -backend-config=\"bucket=$(STATEBUCKET)\" -backend-config=\"key=$(PREFIX)/terraform.tfstate\"" > ./init.txt 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraformed Jenkins 2 | 3 | Deploy Jenkins 2.0 in a [docker container](https://hub.docker.com/r/library/jenkins/tags/) on an AWS EC2 instance using [Terraform](https://www.terraform.io/). 4 | 5 | The terraform script stores the terraform state remotely in an S3 bucket. The Makefile by default sets up a copy of the remote state if it doesn’t exist and then runs either `terraform plan` or `terraform apply` depending on the target. 6 | 7 | ## Usage 8 | 9 | Before you run the Makefile, you should set the following environment variables to authenticate with AWS: 10 | ``` 11 | $ export AWS_ACCESS_KEY_ID= # to store and retrieve the remote state in s3. 12 | $ export AWS_SECRET_ACCESS_KEY= 13 | $ export AWS_DEFAULT_REGION= 14 | $ export TF_VAR_access_key=$AWS_ACCESS_KEY # exposed as access_key in terraform scripts 15 | $ export TF_VAR_secret_key=$AWS_SECRET_ACCESS_KEY # exposed as secret_key in terraform scripts 16 | ``` 17 | 18 | You need to change the default values of `s3_bucket` and `key_name` terraform variables defined in `variables.tf` or set them via environment variables: 19 | ``` 20 | $ export TF_VAR_s3_bucket= 21 | $ export TF_VAR_key_name= 22 | ``` 23 | You also need to change the value of `STATEBUCKET` in the Makefile to match that of the `s3_bucket` terraform variable. 24 | 25 | ### Run 'terraform plan' 26 | 27 | make 28 | 29 | ### Run 'terraform apply' 30 | 31 | make apply 32 | 33 | 34 | ### Run 'terraform destroy' 35 | 36 | make destroy 37 | 38 | Upon completion, terraform will output the DNS name of Jenkins, e.g.: 39 | ``` 40 | jenkins_public_dns = [ ec2-54-235-229-108.compute-1.amazonaws.com ] 41 | ``` 42 | You can then reach Jenkins via your browser at `http://ec2-54-235-229-108.compute-1.amazonaws.com`. 43 | 44 | ## Configuring Backup 45 | 46 | After the Jenkins EC2 instance is started, a cronjob is configured to back up Jenkins data to an S3 bucket set via the `s3_bucket` variable (see variables.tf). To accomplish this, a script needs to be copied onto the EC2 instance, therefore, Terraform requires SSH access to the instance. 47 | 48 | To grant SSH access to Terraform, place the instance's PEM file in this project's directory. Note that the key file should have the same name as the EC2 keypair, or you can update the `key_file` variable in the connection section of the Terraform EC2 resource (see main.tf). 49 | 50 | ## Monitoring 51 | 52 | The AMI on which Jenkins is run has [Weave Scope](https://www.weave.works/products/weave-scope/) pre-installed. Scope is a Docker monitoring, visualisation and management tool from Weaveworks. 53 | 54 | The Scope UI can be reached at the URL of the Jenkins instance on port 4040, e.g. `http://ec2-54-235-229-108.compute-1.amazonaws.com:4040`. 55 | 56 | ## Credits 57 | 58 | * The Makefile idea (and the Makefile itself) is taken from this [blog post](http://karlcode.owtelse.com/blog/2015/09/01/working-with-terraform-remote-statefile/). 59 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | access_key = "${var.access_key}" 3 | secret_key = "${var.secret_key}" 4 | region = "${var.region}" 5 | } 6 | 7 | data "terraform_remote_state" "tfstate" { 8 | backend = "s3" 9 | 10 | config { 11 | bucket = "mycompany-terraform" 12 | key = "terraform/terraform.tfstate" 13 | region = "us-east-1" 14 | } 15 | } 16 | 17 | resource "aws_security_group" "sg_jenkins" { 18 | name = "sg_${var.jenkins_name}" 19 | description = "Allows all traffic" 20 | 21 | # SSH 22 | ingress { 23 | from_port = 22 24 | to_port = 22 25 | protocol = "tcp" 26 | cidr_blocks = [ 27 | "0.0.0.0/0"] 28 | } 29 | 30 | # HTTP 31 | ingress { 32 | from_port = 80 33 | to_port = 80 34 | protocol = "tcp" 35 | cidr_blocks = [ 36 | "0.0.0.0/0"] 37 | } 38 | 39 | # HTTPS 40 | ingress { 41 | from_port = 443 42 | to_port = 443 43 | protocol = "tcp" 44 | cidr_blocks = [ 45 | "0.0.0.0/0"] 46 | } 47 | 48 | # Jenkins JNLP port 49 | ingress { 50 | from_port = 50000 51 | to_port = 50000 52 | protocol = "tcp" 53 | cidr_blocks = [ 54 | "0.0.0.0/0"] 55 | } 56 | 57 | # Weave Scope port 58 | ingress { 59 | from_port = 4040 60 | to_port = 4040 61 | protocol = "tcp" 62 | cidr_blocks = [ 63 | "0.0.0.0/0"] 64 | } 65 | 66 | egress { 67 | from_port = 0 68 | to_port = 0 69 | protocol = "-1" 70 | cidr_blocks = [ 71 | "0.0.0.0/0"] 72 | } 73 | } 74 | 75 | data "template_file" "user_data" { 76 | template = "${file("templates/user_data.tpl")}" 77 | } 78 | 79 | resource "aws_instance" "jenkins" { 80 | instance_type = "${var.instance_type}" 81 | security_groups = ["${aws_security_group.sg_jenkins.name}"] 82 | ami = "${lookup(var.amis, var.region)}" 83 | key_name = "${var.key_name}" 84 | user_data = "${data.template_file.user_data.rendered}" 85 | 86 | tags { 87 | "Name" = "${var.jenkins_name}" 88 | } 89 | 90 | # Add backup task to crontab 91 | provisioner "file" { 92 | connection { 93 | user = "ec2-user" 94 | host = "${aws_instance.jenkins.public_ip}" 95 | timeout = "1m" 96 | private_key = "${file("${var.key_name}.pem")}" 97 | } 98 | source = "templates/cron.sh" 99 | destination = "/home/ec2-user/cron.sh" 100 | } 101 | 102 | provisioner "remote-exec" { 103 | connection { 104 | user = "ec2-user" 105 | host = "${aws_instance.jenkins.public_ip}" 106 | timeout = "1m" 107 | private_key = "${file("${var.key_name}.pem")}" 108 | } 109 | inline = [ 110 | "chmod +x /home/ec2-user/cron.sh", 111 | "/home/ec2-user/cron.sh ${var.access_key} ${var.secret_key} ${var.s3_bucket} ${var.jenkins_name}" 112 | ] 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /output.tf: -------------------------------------------------------------------------------- 1 | output "jenkins_public_dns" { 2 | value = "[ ${aws_instance.jenkins.public_dns} ]" 3 | } 4 | -------------------------------------------------------------------------------- /templates/cron.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | access_key=$1 4 | secret_key=$2 5 | s3_bucket=$3 6 | jenkins_name=$4 7 | 8 | JENKINS_HOME=/opt/jenkins_home 9 | 10 | (crontab -l 2>/dev/null; echo "* * * * * /usr/bin/docker run --env aws_key=${access_key} --env aws_secret=${secret_key} --env cmd=sync-local-to-s3 --env DEST_S3=s3://${s3_bucket}/${jenkins_name}/jenkins_home/ -v $JENKINS_HOME:/opt/src/$(/bin/date +\%Y\%m\%d) garland/docker-s3cmd") | crontab - 11 | -------------------------------------------------------------------------------- /templates/user_data.tpl: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | JENKINS_HOME=/opt/jenkins_home 4 | 5 | # Create and set correct permissions for Jenkins mount directory 6 | sudo mkdir -p $JENKINS_HOME 7 | sudo chmod -R 777 $JENKINS_HOME 8 | 9 | # Start Jenkins 10 | docker run -id --name jenkins -p 80:8080 -p 50000:50000 -v $JENKINS_HOME:/var/jenkins_home jenkins:2.3-alpine 11 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | variable "access_key" { 2 | description = "The AWS access key." 3 | } 4 | 5 | variable "secret_key" { 6 | description = "The AWS secret key." 7 | } 8 | 9 | variable "region" { 10 | description = "The AWS region to create resources in." 11 | default = "us-east-1" 12 | } 13 | 14 | variable "availability_zone" { 15 | description = "The availability zone" 16 | default = "us-east-1b" 17 | } 18 | 19 | variable "jenkins_name" { 20 | description = "The name of the Jenkins server." 21 | default = "jenkins" 22 | } 23 | 24 | variable "amis" { 25 | description = "Which AMI to spawn. Defaults to the Weave ECS AMIs: https://github.com/weaveworks/integrations/tree/master/aws/ecs." 26 | default = { 27 | us-east-1 = "ami-49617b23" 28 | us-west-1 = "ami-24057b44" 29 | us-west-2 = "ami-3cac5c5c" 30 | eu-west-1 = "ami-1025aa63" 31 | eu-central-1 = "ami-e010f38f" 32 | ap-northeast-1 = "ami-54d5cc3a" 33 | ap-southeast-1 = "ami-664d9905" 34 | ap-southeast-2 = "ami-c2e9c4a1" 35 | } 36 | } 37 | 38 | variable "instance_type" { 39 | default = "t2.micro" 40 | } 41 | 42 | variable "key_name" { 43 | default = "jenkins" 44 | description = "SSH key name in your AWS account for AWS instances." 45 | } 46 | 47 | variable "s3_bucket" { 48 | default = "mycompany-jenkins" 49 | description = "S3 bucket where remote state and Jenkins data will be stored." 50 | } 51 | --------------------------------------------------------------------------------