├── README.md ├── lesson-ansible ├── .gitignore ├── README.md ├── ansible.cfg └── deploy-hellonode.yml ├── lesson-chef ├── .gitignore ├── README.md ├── app.go ├── chef │ ├── cookbooks │ │ └── example_app │ │ │ ├── CHANGELOG.md │ │ │ ├── README.md │ │ │ ├── files │ │ │ └── app-linux-amd64 │ │ │ ├── metadata.rb │ │ │ └── recipes │ │ │ └── default.rb │ ├── node.json │ └── solo.rb ├── id_rsa_example ├── id_rsa_example.pub └── resources.tf ├── lesson-docker ├── Dockerfile ├── README.md └── hello-node-app │ ├── main.js │ ├── package.json │ └── test.js ├── lesson-gitlab-ci ├── .gitlab-ci.yml └── README.md ├── lesson-jenkins ├── Dockerfile ├── Jenkinsfile ├── README.md └── hello-node-app │ ├── main.js │ ├── package.json │ └── test.js ├── lesson-openstack └── README.md ├── lesson-puppet ├── .gitignore ├── README.md ├── environments │ └── production │ │ ├── manifests │ │ └── site.pp │ │ └── modules │ │ └── hellophp │ │ ├── files │ │ └── index.php │ │ └── manifests │ │ └── init.pp ├── id_rsa_example ├── id_rsa_example.pub └── resources.tf ├── lesson-rancher └── README.md ├── lesson-rundeck ├── CreateEnvironmentJobDefinition.yml ├── Dockerfile ├── README.md └── inline_script.txt └── lesson-terraform ├── .gitignore ├── README.md ├── id_rsa_example ├── id_rsa_example.pub └── resources.tf /README.md: -------------------------------------------------------------------------------- 1 | # Code examples for Get into DevOps: The Masterclass 2 | The code examples in this repository are for the Get into DevOps: The Masterclass online course. 3 | 4 | The examples are organized by lesson. 5 | 6 | For more information, see https://www.releaseworksacademy.com 7 | -------------------------------------------------------------------------------- /lesson-ansible/.gitignore: -------------------------------------------------------------------------------- 1 | hellonode_key* 2 | *.retry 3 | -------------------------------------------------------------------------------- /lesson-ansible/README.md: -------------------------------------------------------------------------------- 1 | # Lesson: Ansible 2 | In this lesson, we'll use Ansible to launch an EC2 instance, configure Docker on it, and run the Hellonode Docker image. 3 | 4 | To begin, configure your AWS API keys: 5 | ``` 6 | aws configure 7 | ``` 8 | 9 | Then, run the playbook: 10 | ``` 11 | ansible-playbook deploy-hellonode.yml 12 | ``` 13 | 14 | You can see the application is listening on port 8000 of the EC2 instance IP. You can find the EC2 instance IP under "PLAY RECAP" of the Ansible playbook output. 15 | 16 | Finally, terminate the EC2 instance: 17 | ``` 18 | ansible-playbook -e server_count=0 deploy-hellonode.yml 19 | ``` -------------------------------------------------------------------------------- /lesson-ansible/ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | host_key_checking = False 3 | 4 | -------------------------------------------------------------------------------- /lesson-ansible/deploy-hellonode.yml: -------------------------------------------------------------------------------- 1 | - hosts: localhost 2 | name: Create infrastructure on AWS 3 | connection: local 4 | gather_facts: False 5 | vars: 6 | server_count: 1 7 | 8 | tasks: 9 | - name: Generate SSH key if it doesn't exist 10 | shell: ssh-keygen -b 2048 -t rsa -f ./hellonode_key -q -N "" 11 | args: 12 | creates: ./hellonode_key 13 | 14 | - name: Ensure key pair exists in EC2 15 | ec2_key: 16 | name: hellonode_key 17 | key_material: "{{ item }}" 18 | region: "us-east-1" 19 | with_file: ./hellonode_key.pub 20 | 21 | - name: Ensure security group exists 22 | ec2_group: 23 | name: hellonode_sg 24 | description: Security group for hellonode server 25 | rules: 26 | - proto: tcp 27 | from_port: 8000 28 | to_port: 8000 29 | cidr_ip: 0.0.0.0/0 30 | - proto: tcp 31 | from_port: 22 32 | to_port: 22 33 | cidr_ip: 0.0.0.0/0 34 | rules_egress: 35 | - proto: all 36 | from_port: -1 37 | to_port: -1 38 | cidr_ip: 0.0.0.0/0 39 | region: "us-east-1" 40 | 41 | - name: Ensure {{ server_count }} server(s) are running 42 | ec2: 43 | key_name: hellonode_key 44 | group: hellonode_sg 45 | instance_type: t2.micro 46 | image: "ami-da05a4a0" 47 | wait: true 48 | exact_count: "{{ server_count }}" 49 | count_tag: 50 | Name: Hellonode Server 51 | instance_tags: 52 | Name: Hellonode Server 53 | user_data: | 54 | #!/bin/sh 55 | sudo apt-get -y update 56 | sudo apt-get -y --force-yes install python python-pip 57 | region: "us-east-1" 58 | register: ec2 59 | 60 | - name: Add instance public IP to host group 61 | add_host: 62 | hostname: "{{ item.public_ip }}" 63 | groups: ec2hosts 64 | ansible_ssh_private_key_file: ./hellonode_key 65 | when: item.public_ip != None 66 | with_items: "{{ ec2.instances }}" 67 | 68 | - hosts: ec2hosts 69 | name: Configure Hellonode server 70 | user: ubuntu 71 | become: true 72 | gather_facts: False 73 | 74 | tasks: 75 | - name: Wait 300 seconds for target connection to become reachable/usable 76 | wait_for_connection: 77 | timeout: 300 78 | 79 | - name: Wait for provisioning script to finish 80 | become: yes 81 | shell: while sudo fuser /var/lib/dpkg/lock >/dev/null 2>&1; do sleep 1; done; 82 | 83 | - name: Ensure repository key is installed 84 | apt_key: 85 | id: "58118E89F3A912897C070ADBF76221572C52609D" 86 | keyserver: "hkp://p80.pool.sks-keyservers.net:80" 87 | state: present 88 | 89 | - name: Ensure Docker repository is available 90 | apt_repository: repo='deb https://apt.dockerproject.org/repo ubuntu-xenial main' state=present 91 | 92 | - name: Ensure Docker and dependencies are installed 93 | apt: name=docker-engine update_cache=yes 94 | 95 | - name: Ensure Docker-py is installed 96 | pip: name=docker-py 97 | 98 | - name: Ensure Docker is running 99 | service: name=docker state=restarted 100 | 101 | - name: Ensure the hellonode container is running 102 | docker_container: 103 | name: hellonode 104 | state: started 105 | image: "getintodevops/hellonode:latest" 106 | pull: true 107 | ports: 108 | - 8000:8000 109 | -------------------------------------------------------------------------------- /lesson-chef/.gitignore: -------------------------------------------------------------------------------- 1 | terraform.tfstate* 2 | .terraform/ 3 | -------------------------------------------------------------------------------- /lesson-chef/README.md: -------------------------------------------------------------------------------- 1 | # Lesson: Chef 2 | In this lesson, we are extending the Terraform configuration we wrote earlier to use Chef for configuring two application servers behind our load balancer. 3 | 4 | ## To see what changes would be done 5 | ``` 6 | terraform plan -var 'aws_access_key=ACCESS_KEY_HERE' -var 'aws_secret_key=SECRET_KEY_HERE' 7 | ``` 8 | 9 | ## To apply the changes 10 | ``` 11 | terraform apply -var 'aws_access_key=ACCESS_KEY_HERE' -var 'aws_secret_key=SECRET_KEY_HERE' 12 | ``` 13 | 14 | ## To destroy all Terraform-managed infrastructure 15 | ``` 16 | terraform destroy -var 'aws_access_key=ACCESS_KEY_HERE' -var 'aws_secret_key=SECRET_KEY_HERE' 17 | ``` 18 | -------------------------------------------------------------------------------- /lesson-chef/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | ) 8 | 9 | func handler(w http.ResponseWriter, r *http.Request) { 10 | h, _ := os.Hostname() 11 | fmt.Fprintf(w, "Hi there, I'm served from %s!", h) 12 | } 13 | 14 | func main() { 15 | http.HandleFunc("/", handler) 16 | http.ListenAndServe(":8484", nil) 17 | } 18 | -------------------------------------------------------------------------------- /lesson-chef/chef/cookbooks/example_app/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # example_app CHANGELOG 2 | 3 | ## 0.1.0 4 | - Miiro Juuso - Initial release of example_app 5 | 6 | - - - 7 | Check the [Markdown Syntax Guide](http://daringfireball.net/projects/markdown/syntax) for help with Markdown. 8 | 9 | The [Github Flavored Markdown page](http://github.github.com/github-flavored-markdown/) describes the differences between markdown on github and standard markdown. 10 | -------------------------------------------------------------------------------- /lesson-chef/chef/cookbooks/example_app/README.md: -------------------------------------------------------------------------------- 1 | # example_app cookbook 2 | 3 | Cookbook for example app deployment. 4 | -------------------------------------------------------------------------------- /lesson-chef/chef/cookbooks/example_app/files/app-linux-amd64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/releaseworks/masterclass-codeexamples/0c27df659163ff1fdb087aab0416778b32a38d06/lesson-chef/chef/cookbooks/example_app/files/app-linux-amd64 -------------------------------------------------------------------------------- /lesson-chef/chef/cookbooks/example_app/metadata.rb: -------------------------------------------------------------------------------- 1 | name 'example_app' 2 | maintainer 'Miiro Juuso' 3 | maintainer_email 'miiro.juuso@gmail.com' 4 | license 'Public domain' 5 | description 'Installs/Configures example_app' 6 | long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) 7 | version '0.1.0' 8 | -------------------------------------------------------------------------------- /lesson-chef/chef/cookbooks/example_app/recipes/default.rb: -------------------------------------------------------------------------------- 1 | # We'll run our app under a non-privileged user 2 | user 'deploy' do 3 | comment 'Deploy user' 4 | home '/app' 5 | shell '/bin/bash' 6 | password '' 7 | end 8 | 9 | # Our app lives in /app 10 | directory '/app' do 11 | owner 'deploy' 12 | group 'deploy' 13 | mode '0755' 14 | action :create 15 | end 16 | 17 | # Copy the pre-compiled application to the directory 18 | cookbook_file '/app/app' do 19 | source 'app-linux-amd64' 20 | owner 'deploy' 21 | group 'deploy' 22 | mode '0755' 23 | action :create 24 | end 25 | 26 | # Run the application 27 | execute 'start-app' do 28 | command '/app/app &' 29 | user 'deploy' 30 | action :run 31 | end 32 | -------------------------------------------------------------------------------- /lesson-chef/chef/node.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /lesson-chef/chef/solo.rb: -------------------------------------------------------------------------------- 1 | file_cache_path "/home/ubuntu/chef" 2 | cookbook_path "/home/ubuntu/chef/cookbooks" 3 | json_attribs "/home/ubuntu/chef/node.json" 4 | -------------------------------------------------------------------------------- /lesson-chef/id_rsa_example: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEArkdqAhHRmw2C2rvrmOVXQxQH4Vg/Fxq2zV92au5RS2OIUk6o 3 | uecORfX2IBpUIUdu5zJcDZ8ONiGgXNqON+8eIymd3oEnqwXMHscN+LwM6WHUlSaJ 4 | F+ooSN7EhvpOSeQgMSBetunr1MWTwV9McJ79A3+2FqXtYiSQ+m4kS/q+65NHTEoA 5 | OXuCSiMVFTCHvtNZMwO7hy1nALD1uP4qc8digOxo4JfEa0D/sNwRYnjHwjtLZo+e 6 | ERQn9qyTl/EW/0temd6H8a0OVIQBQUrSy5btYWyD1yNsRMxS1RwiPRQSgls8YShC 7 | fB1rGJLOgLCF4Va649tTsbnvkwiuNnl0F1HEzQIDAQABAoIBAQCYlmY2UauXegjr 8 | wkTQpVv1mwAbSMxUSktrHP3tOaNrwkWE6uHHgol4r5VDHkoyqPxs6Ca4tl4W/vIM 9 | 2j5SssVMPA/A1IwKrRS+jcGp2dvkKoeZ3xeBHL8Qrhqbo2QQRe/5k0HaHI0iyapI 10 | UEv4/vq/qX2f7r8cgq981Bpre6rVTbcKeEn4cIJ1iK91DuWhHo7CFVJI3X6fizRg 11 | 6eqraNLxOMPU1BN4uZmU9hsSgv9E3QAve79LQ1zO9CDGPgroqmCpwDFs2zeGYym7 12 | Wu52p0Fy0Axnoq8ITMw4dW6TievFWK23nB6WIw11P4NfDhfvwwR5hCd6knXZRP8x 13 | hdtmf2TxAoGBAOJLvt7Lx0EH6VyRArufj37q9zPKvP1+LIsc7Q9uf8JKJS0ScHD2 14 | coTxNPDV95twcmnfuuqCat+7XZt4t1e+4ZviLOzaanDKXajb7E9XE9n5siKRmQ93 15 | LsgW4jKYEaMKu2itPIBTT8KnCetzYGPQFhBNYuN49Kk9uEpBKv+D84AjAoGBAMUn 16 | vHRtPzVUF9OwGueD/JgozOPUSELVQr5lUrWGXJonsyJnRa48KKUMyM1iTruIpFPq 17 | wCX41S7PCHF7TE09WxFGWbNNmSYNzh1L/UNKuhy6uQuF9+gmGKilsti+cBDsOkmp 18 | xrfjTc+9KVZFkyGYWd0gBi8pQPsdI+Fwi7v1NH5PAoGBAKrDGb8p6qvp+nC9hGBs 19 | oW3WhL/yZvaqtZYsN78DVSkZpkACwBKeBgDHu3lZHMWQ7uVxzKyVyWwXCSX+y+tM 20 | wRZOcDQzBUsjidWYTxP306USxRdM40FGYGjgy8P9+KEdwhnVT+hN9cwfHF8t6zim 21 | l1+p5ctdRNJJHr35uyahPagdAoGAb4C2s74plnaV9zJNNQzPqhrBLkUcDThhxB63 22 | 9VQlQUYcqONxZEY/0oD1fDsSPjvcfF1zCMa/gvayVsQd9j0yKQX5q0/CwuPh423b 23 | sdgshB0SlLLS72fEYHU+PhkOdnOzz3+GlO+oTUo1e8ZjnQd2I3p+JOQXDS6A4Xpu 24 | fQIECz8CgYAUpk/WCXsKcZordwK+qpkaDsNVH3AiyqEeDHOPIDU3jnz+lqFmRURJ 25 | fvYvZlDhN6ihZ7uSQ9cIV6rwznfYCRor5ZTbnHp5iLlmtMY8toQ9u4MHAL9bAhjh 26 | RNsDGqvQdQpswjObsW8p7Lv0NWdIfBMzso5ocQWq96KIpZZ9gDZGHg== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /lesson-chef/id_rsa_example.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCuR2oCEdGbDYLau+uY5VdDFAfhWD8XGrbNX3Zq7lFLY4hSTqi55w5F9fYgGlQhR27nMlwNnw42IaBc2o437x4jKZ3egSerBcwexw34vAzpYdSVJokX6ihI3sSG+k5J5CAxIF626evUxZPBX0xwnv0Df7YWpe1iJJD6biRL+r7rk0dMSgA5e4JKIxUVMIe+01kzA7uHLWcAsPW4/ipzx2KA7Gjgl8RrQP+w3BFieMfCO0tmj54RFCf2rJOX8Rb/S16Z3ofxrQ5UhAFBStLLlu1hbIPXI2xEzFLVHCI9FBKCWzxhKEJ8HWsYks6AsIXhVrrj21Oxue+TCK42eXQXUcTN mjuuso@Miiros-MacBook.local 2 | -------------------------------------------------------------------------------- /lesson-chef/resources.tf: -------------------------------------------------------------------------------- 1 | /* First we'll set some variables for AWS authentication. */ 2 | 3 | variable "aws_access_key" { 4 | default = "" 5 | } 6 | 7 | variable "aws_secret_key" { 8 | default = "" 9 | } 10 | 11 | 12 | /* These are dummy resources pointing to the default VPC and 13 | subnets in our AWS account. We'll be referencing these later. */ 14 | 15 | resource "aws_default_vpc" "default" { 16 | } 17 | 18 | variable "default_zones" { 19 | default = { 20 | zone0 = "eu-west-1a" 21 | zone1 = "eu-west-1b" 22 | zone2 = "eu-west-1c" 23 | } 24 | } 25 | 26 | resource "aws_default_subnet" "default" { 27 | availability_zone = "${lookup(var.default_zones, format("zone%d", count.index))}" 28 | count = 2 29 | } 30 | 31 | 32 | 33 | /* We'll be using AWS for provisioning the instances, 34 | so we'll set "aws" as the provider. */ 35 | 36 | provider "aws" { 37 | access_key = "${var.aws_access_key}" 38 | secret_key = "${var.aws_secret_key}" 39 | region = "eu-west-1" 40 | } 41 | 42 | 43 | /* Security group for the load balancer. We'll open port 80/tcp 44 | to the world. We'll allow everything outbound. */ 45 | 46 | resource "aws_security_group" "gid-lb" { 47 | name = "GID lb" 48 | description = "Security group for the load balancer" 49 | vpc_id = "${aws_default_vpc.default.id}" 50 | ingress { 51 | from_port = 80 52 | to_port = 80 53 | protocol = "tcp" 54 | cidr_blocks = ["0.0.0.0/0"] 55 | } 56 | 57 | egress { 58 | from_port = 0 59 | to_port = 0 60 | protocol = "-1" 61 | cidr_blocks = ["0.0.0.0/0"] 62 | } 63 | 64 | tags { 65 | Author = "Get into DevOps" 66 | } 67 | } 68 | 69 | 70 | /* Security group for the app nodes. We'll allow 8484/tcp from our load 71 | balancer, and 22/tcp from the world (see note below). */ 72 | 73 | resource "aws_security_group" "gid-app" { 74 | name = "gid app" 75 | description = "Security group for the app instances" 76 | vpc_id = "${aws_default_vpc.default.id}" 77 | ingress { 78 | from_port = 8484 79 | to_port = 8484 80 | protocol = "tcp" 81 | security_groups = ["${aws_security_group.gid-lb.id}"] 82 | } 83 | 84 | ingress { 85 | from_port = 22 86 | to_port = 22 87 | protocol = "tcp" 88 | 89 | /* Good practice would be to set the following to "your.ip.address/32" 90 | instead of "0.0.0.0/0", which will open SSH to the world. */ 91 | cidr_blocks = ["0.0.0.0/0"] 92 | } 93 | 94 | egress { 95 | from_port = 0 96 | to_port = 0 97 | protocol = "-1" 98 | cidr_blocks = ["0.0.0.0/0"] 99 | } 100 | 101 | tags { 102 | Author = "Get into DevOps" 103 | } 104 | } 105 | 106 | 107 | /* Our root SSH key pair is created by the wrapper script. */ 108 | 109 | resource "aws_key_pair" "root" { 110 | key_name = "root-key" 111 | public_key = "${file("id_rsa_example.pub")}" 112 | } 113 | 114 | 115 | /* The app nodes are t2.micros running Ubuntu 16.04 LTS 116 | for simplicity. We place the app nodes in different AZs. */ 117 | 118 | resource "aws_instance" "gid-app" { 119 | ami = "ami-eed00d97" 120 | instance_type = "t2.micro" 121 | count = 2 122 | 123 | tags { 124 | Name = "gid-app${count.index}" 125 | Author = "Get into DevOps" 126 | } 127 | 128 | subnet_id = "${element(aws_default_subnet.default.*.id, count.index)}" 129 | associate_public_ip_address = true 130 | 131 | key_name = "${aws_key_pair.root.key_name}" 132 | vpc_security_group_ids = ["${aws_security_group.gid-app.id}"] 133 | 134 | provisioner "file" { 135 | source = "chef" 136 | destination = "/home/ubuntu" 137 | connection { 138 | user = "ubuntu" 139 | private_key = "${file("id_rsa_example")}" 140 | timeout = "60s" 141 | } 142 | } 143 | 144 | provisioner "remote-exec" { 145 | inline = [ 146 | "curl -L https://www.opscode.com/chef/install.sh | sudo bash", 147 | "sudo chef-solo -c /home/ubuntu/chef/solo.rb -o example_app" 148 | ] 149 | connection { 150 | user = "ubuntu" 151 | private_key = "${file("id_rsa_example")}" 152 | timeout = "60s" 153 | } 154 | } 155 | } 156 | 157 | 158 | /* This is the load balancer. */ 159 | 160 | resource "aws_lb" "gid" { 161 | name = "gid-lb" 162 | internal = false 163 | security_groups = ["${aws_security_group.gid-lb.id}"] 164 | subnets = ["${aws_default_subnet.default.*.id}"] 165 | 166 | tags { 167 | Author = "Get into DevOps" 168 | } 169 | } 170 | 171 | resource "aws_lb_listener" "gid" { 172 | load_balancer_arn = "${aws_lb.gid.arn}" 173 | port = "80" 174 | protocol = "HTTP" 175 | 176 | default_action { 177 | target_group_arn = "${aws_lb_target_group.gid.arn}" 178 | type = "forward" 179 | } 180 | } 181 | 182 | resource "aws_lb_target_group" "gid" { 183 | name = "gid-lb-tg" 184 | port = 80 185 | protocol = "HTTP" 186 | vpc_id = "${aws_default_vpc.default.id}" 187 | } 188 | 189 | resource "aws_lb_target_group_attachment" "test" { 190 | target_group_arn = "${aws_lb_target_group.gid.arn}" 191 | target_id = "${element(aws_instance.gid-app.*.id, count.index)}" 192 | port = 8484 193 | count = 2 194 | } 195 | 196 | 197 | /* We'll need the load balancer hostname */ 198 | 199 | output "lb-hostname" { 200 | value = "${aws_lb.gid.dns_name}" 201 | } 202 | -------------------------------------------------------------------------------- /lesson-docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # use a node base image 2 | FROM node:8 3 | 4 | # set maintainer 5 | LABEL maintainer "miiro@getintodevops.com" 6 | 7 | # run a command inside the container 8 | # this will create a directory for our application 9 | RUN mkdir -p /app 10 | 11 | # set the working directory to our app directory 12 | WORKDIR /app 13 | 14 | # copy our application inside the container 15 | COPY hello-node-app/* /app/ 16 | 17 | # tell docker what port to expose 18 | EXPOSE 8000 19 | 20 | # tell docker what command to run when container is run 21 | CMD npm start 22 | 23 | -------------------------------------------------------------------------------- /lesson-docker/README.md: -------------------------------------------------------------------------------- 1 | # Lesson: Docker 2 | In this lesson, we will be creating a Docker image with a very simple "Hello World" Node.js application. 3 | 4 | This directory includes a `Dockerfile` for building a Docker image, and the application itself. 5 | 6 | ## To build the image 7 | ``` 8 | docker build -t hellonode . 9 | ``` 10 | 11 | ## To run the image, mapping port 8000 to the host machine 12 | ``` 13 | docker run -i -t -p8000:8000 hellonode 14 | ``` 15 | 16 | https://getintodevops.com 17 | -------------------------------------------------------------------------------- /lesson-docker/hello-node-app/main.js: -------------------------------------------------------------------------------- 1 | // load the http module 2 | var http = require('http'); 3 | 4 | // configure our HTTP server 5 | var server = http.createServer(function (request, response) { 6 | response.writeHead(200, {"Content-Type": "text/plain"}); 7 | response.end("Hello getintodevops.com\n"); 8 | }); 9 | 10 | // listen on localhost:8000 11 | server.listen(8000); 12 | console.log("Server listening at http://127.0.0.1:8000/"); 13 | 14 | -------------------------------------------------------------------------------- /lesson-docker/hello-node-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "getintodevops-hellonode", 3 | "version": "1.0.0", 4 | "description": "A Hello World HTTP server", 5 | "main": "main.js", 6 | "scripts": { 7 | "test": "node test.js", 8 | "start": "node main.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/getintodevops/masterclass-codeexamples/" 13 | }, 14 | "keywords": [ 15 | "node", 16 | "docker", 17 | "dockerfile" 18 | ], 19 | "author": "miiro@getintodevops.com", 20 | "license": "ISC", 21 | "devDependencies": { "test": ">=0.6.0" } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /lesson-docker/hello-node-app/test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | 3 | function test() { 4 | assert.equal(2 + 2, 4); 5 | } 6 | 7 | if (module == require.main) require('test').run(test); -------------------------------------------------------------------------------- /lesson-gitlab-ci/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: docker:latest 2 | 3 | build-master: 4 | stage: build 5 | services: 6 | - docker:dind 7 | script: 8 | - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY 9 | - docker build --pull -t "$CI_REGISTRY_IMAGE" . 10 | - docker tag "$CI_REGISTRY_IMAGE:latest" "$CI_REGISTRY_IMAGE:$CI_PIPELINE_ID" 11 | - docker push "$CI_REGISTRY_IMAGE:$CI_PIPELINE_ID" 12 | - docker push "$CI_REGISTRY_IMAGE:latest" 13 | only: 14 | - master 15 | 16 | build: 17 | stage: build 18 | services: 19 | - docker:dind 20 | script: 21 | - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY 22 | - docker build --pull -t "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" . 23 | - docker push "$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG" 24 | except: 25 | - master 26 | 27 | deploy: 28 | image: google/cloud-sdk 29 | stage: deploy 30 | environment: dev 31 | script: 32 | - echo $GCLOUD_AUTH > /tmp/gcloudkey.json 33 | - gcloud auth activate-service-account --key-file /tmp/gcloudkey.json 34 | - > 35 | gcloud config set project $(cat /tmp/gcloudkey.json | tr -d '\n' | sed -n 's/.*"project_id": "\([^\s]*\)",[^\s]*"private_key_id.*/\1/p') 36 | - gcloud container clusters get-credentials -z us-central1-a hellonode 37 | - kubectl run hellonode --image=$CI_REGISTRY_IMAGE:$CI_PIPELINE_ID --port 8000 || kubectl set image deployment/hellonode hellonode=$CI_REGISTRY_IMAGE:$CI_PIPELINE_ID 38 | - kubectl expose deployment hellonode --type=LoadBalancer --port 80 --target-port 8000 || echo "Load Balancer already exists" 39 | - while [[ "$EXTIP" == "" ]]; do EXTIP=$(kubectl get services -l run=hellonode -o jsonpath='{.items[0].status.loadBalancer.ingress[0].ip}'); sleep 5; done; echo "Application deployed to http://$EXTIP)" 40 | only: 41 | - master 42 | after_script: 43 | - rm /tmp/gcloudkey.json 44 | 45 | -------------------------------------------------------------------------------- /lesson-gitlab-ci/README.md: -------------------------------------------------------------------------------- 1 | # Lesson: Gitlab CI 2 | To begin, import the Hellonode project from Github into your Gitlab account. Make the project visibility public on Gitlab, and copy the included .gitlab-ci.yml file into the repository. 3 | 4 | You will also need a Google Container Engine cluster with the name 'hellonode', and a Google Cloud Platform service account with access to the cluster. To pass the service account to Gitlab CI, you will need to create a Secret Variable with the name GCLOUD_AUTH, and the JSON contents of the service account private key file. 5 | 6 | The included .gitlab-ci.yml will build a Hellonode Docker image, push it to the project registry on Gitlab, and then deploy the image onto the Google Container Engine cluster. 7 | -------------------------------------------------------------------------------- /lesson-jenkins/Dockerfile: -------------------------------------------------------------------------------- 1 | # use a node base image 2 | FROM node:8 3 | 4 | # set maintainer 5 | LABEL maintainer "miiro@getintodevops.com" 6 | 7 | # run a command inside the container 8 | # this will create a directory for our application 9 | RUN mkdir -p /app 10 | 11 | # set the working directory to our app directory 12 | WORKDIR /app 13 | 14 | # copy our application inside the container 15 | COPY hello-node-app/* /app/ 16 | 17 | # tell docker what port to expose 18 | EXPOSE 8000 19 | 20 | # tell docker what command to run when container is run 21 | CMD npm start 22 | 23 | -------------------------------------------------------------------------------- /lesson-jenkins/Jenkinsfile: -------------------------------------------------------------------------------- 1 | node { 2 | def app 3 | 4 | stage('Clone repository') { 5 | /* Let's make sure we have the repository cloned to our workspace */ 6 | 7 | checkout scm 8 | } 9 | 10 | stage('Build image') { 11 | /* This builds the actual image; synonymous to 12 | * docker build on the command line */ 13 | 14 | app = docker.build("getintodevops/hellonode") 15 | } 16 | 17 | stage('Test image') { 18 | /* Ideally, we would run a test framework against our image. 19 | * This runs only a single dummy test inside the image. */ 20 | 21 | app.inside { 22 | sh 'npm test' 23 | } 24 | } 25 | 26 | stage('Push image') { 27 | /* Finally, we'll push the image with two tags: 28 | * First, the incremental build number from Jenkins 29 | * Second, the 'latest' tag. 30 | * Pushing multiple tags is cheap, as all the layers are reused. */ 31 | docker.withRegistry('https://registry.hub.docker.com', 'docker-hub-credentials') { 32 | app.push("${env.BUILD_NUMBER}") 33 | app.push("latest") 34 | } 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /lesson-jenkins/README.md: -------------------------------------------------------------------------------- 1 | # Lesson: Jenkins 2 | In this lesson, we will be configuring a Continuous Integration pipeline in Jenkins to build a Hello Node Docker image whenever there's a commit to the code repository. 3 | 4 | This directory includes a `Jenkinsfile` with specifications of the build pipeline, a `Dockerfile` for building the Docker image, and the application itself. 5 | 6 | ## To run Jenkins as a container 7 | ``` 8 | docker run -it -v /var/run/docker.sock:/var/run/docker.sock -p8080:8080 getintodevops/jenkins-withdocker:lts 9 | ``` 10 | 11 | Access the Jenkins user interface in http://localhost:8080 12 | 13 | https://getintodevops.com 14 | -------------------------------------------------------------------------------- /lesson-jenkins/hello-node-app/main.js: -------------------------------------------------------------------------------- 1 | // load the http module 2 | var http = require('http'); 3 | 4 | // configure our HTTP server 5 | var server = http.createServer(function (request, response) { 6 | response.writeHead(200, {"Content-Type": "text/plain"}); 7 | response.end("Hello getintodevops.com\n"); 8 | }); 9 | 10 | // listen on localhost:8000 11 | server.listen(8000); 12 | console.log("Server listening at http://127.0.0.1:8000/"); 13 | 14 | -------------------------------------------------------------------------------- /lesson-jenkins/hello-node-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "getintodevops-hellonode", 3 | "version": "1.0.0", 4 | "description": "A Hello World HTTP server", 5 | "main": "main.js", 6 | "scripts": { 7 | "test": "node test.js", 8 | "start": "node main.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/getintodevops/masterclass-codeexamples/" 13 | }, 14 | "keywords": [ 15 | "node", 16 | "docker", 17 | "dockerfile" 18 | ], 19 | "author": "miiro@getintodevops.com", 20 | "license": "ISC", 21 | "devDependencies": { "test": ">=0.6.0" } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /lesson-jenkins/hello-node-app/test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | 3 | function test() { 4 | assert.equal(2 + 2, 4); 5 | } 6 | 7 | if (module == require.main) require('test').run(test); -------------------------------------------------------------------------------- /lesson-openstack/README.md: -------------------------------------------------------------------------------- 1 | # Lesson: Openstack 2 | 3 | In this lesson, we'll be using Openstack with DevStack. DevStack is a tool that runs a complete Openstack environment on a single server. Because installing DevStack requires various changes to network configuration of the server it's installed on, we'll be creating a new virtual machine for it in Google Cloud Platform. 4 | 5 | Using the skills you have learned in the previous lessons, provision a new virtual machine on Google Compute Engine. Create a server with 2 VCPUs, 8 gigabytes of memory and 100GB root disk with the Ubuntu 16.04 operating system. Also, create a firewall rule to allow TCP port 80 inbound from any source. 6 | 7 | Finally, SSH into the new virtual machine (either via Google Cloud Shell or externally) and follow the instructions below to install DevStack. Note that the installation will take about 15 minutes. 8 | 9 | ## Install DevStack 10 | 11 | First, create a user for running DevStack: 12 | ``` 13 | sudo useradd -s /bin/bash -d /opt/stack -m stack 14 | ``` 15 | 16 | Enable this user to sudo without a password: 17 | ``` 18 | echo "stack ALL=(ALL) NOPASSWD: ALL" | sudo tee /etc/sudoers.d/stack 19 | ``` 20 | 21 | Open a shell as the stack user: 22 | ``` 23 | sudo su - stack 24 | ``` 25 | 26 | Download DevStack via git: 27 | ``` 28 | git clone https://git.openstack.org/openstack-dev/devstack 29 | cd devstack 30 | ``` 31 | 32 | Create the minimum configuration for installing DevStack: 33 | ``` 34 | echo -e "[[local|localrc]]\nADMIN_PASSWORD=masterclass\nDATABASE_PASSWORD=\$ADMIN_PASSWORD\nRABBIT_PASSWORD=\$ADMIN_PASSWORD\nSERVICE_PASSWORD=\$ADMIN_PASSWORD" > local.conf 35 | ``` 36 | 37 | Start the DevStack installation: 38 | ``` 39 | ./stack.sh 40 | ``` 41 | 42 | When the installation is complete, login to the Openstack dashboard in `http:///dashboard/` using the username `demo` and password `masterclass`. 43 | 44 | -------------------------------------------------------------------------------- /lesson-puppet/.gitignore: -------------------------------------------------------------------------------- 1 | terraform.tfstate* 2 | .terraform/ 3 | -------------------------------------------------------------------------------- /lesson-puppet/README.md: -------------------------------------------------------------------------------- 1 | # Lesson: Puppet 2 | In this lesson, we are using Puppet to configure a server we're provisioning with Terraform. 3 | 4 | See our Puppet code in environments/ 5 | 6 | ## First, configure AWS credentials 7 | ``` 8 | aws configure 9 | ``` 10 | 11 | ## To see what changes would be done 12 | ``` 13 | terraform plan 14 | ``` 15 | 16 | ## To apply the changes 17 | ``` 18 | terraform apply 19 | ``` 20 | 21 | ## To destroy all Terraform-managed infrastructure 22 | ``` 23 | terraform destroy 24 | ``` 25 | -------------------------------------------------------------------------------- /lesson-puppet/environments/production/manifests/site.pp: -------------------------------------------------------------------------------- 1 | node default { 2 | include hellophp 3 | } 4 | -------------------------------------------------------------------------------- /lesson-puppet/environments/production/modules/hellophp/files/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Hello getintodevops.com!

4 |

Running PHP

5 | 6 | 7 | -------------------------------------------------------------------------------- /lesson-puppet/environments/production/modules/hellophp/manifests/init.pp: -------------------------------------------------------------------------------- 1 | class hellophp { 2 | class { 'apache': 3 | mpm_module => 'prefork', 4 | } 5 | 6 | include apache::mod::php 7 | 8 | file { '/var/www/html/index.html': 9 | ensure => absent 10 | } 11 | 12 | file { '/var/www/html/index.php': 13 | ensure => file, 14 | source => 'puppet:///modules/hellophp/index.php', 15 | mode => '0644', 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /lesson-puppet/id_rsa_example: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEArkdqAhHRmw2C2rvrmOVXQxQH4Vg/Fxq2zV92au5RS2OIUk6o 3 | uecORfX2IBpUIUdu5zJcDZ8ONiGgXNqON+8eIymd3oEnqwXMHscN+LwM6WHUlSaJ 4 | F+ooSN7EhvpOSeQgMSBetunr1MWTwV9McJ79A3+2FqXtYiSQ+m4kS/q+65NHTEoA 5 | OXuCSiMVFTCHvtNZMwO7hy1nALD1uP4qc8digOxo4JfEa0D/sNwRYnjHwjtLZo+e 6 | ERQn9qyTl/EW/0temd6H8a0OVIQBQUrSy5btYWyD1yNsRMxS1RwiPRQSgls8YShC 7 | fB1rGJLOgLCF4Va649tTsbnvkwiuNnl0F1HEzQIDAQABAoIBAQCYlmY2UauXegjr 8 | wkTQpVv1mwAbSMxUSktrHP3tOaNrwkWE6uHHgol4r5VDHkoyqPxs6Ca4tl4W/vIM 9 | 2j5SssVMPA/A1IwKrRS+jcGp2dvkKoeZ3xeBHL8Qrhqbo2QQRe/5k0HaHI0iyapI 10 | UEv4/vq/qX2f7r8cgq981Bpre6rVTbcKeEn4cIJ1iK91DuWhHo7CFVJI3X6fizRg 11 | 6eqraNLxOMPU1BN4uZmU9hsSgv9E3QAve79LQ1zO9CDGPgroqmCpwDFs2zeGYym7 12 | Wu52p0Fy0Axnoq8ITMw4dW6TievFWK23nB6WIw11P4NfDhfvwwR5hCd6knXZRP8x 13 | hdtmf2TxAoGBAOJLvt7Lx0EH6VyRArufj37q9zPKvP1+LIsc7Q9uf8JKJS0ScHD2 14 | coTxNPDV95twcmnfuuqCat+7XZt4t1e+4ZviLOzaanDKXajb7E9XE9n5siKRmQ93 15 | LsgW4jKYEaMKu2itPIBTT8KnCetzYGPQFhBNYuN49Kk9uEpBKv+D84AjAoGBAMUn 16 | vHRtPzVUF9OwGueD/JgozOPUSELVQr5lUrWGXJonsyJnRa48KKUMyM1iTruIpFPq 17 | wCX41S7PCHF7TE09WxFGWbNNmSYNzh1L/UNKuhy6uQuF9+gmGKilsti+cBDsOkmp 18 | xrfjTc+9KVZFkyGYWd0gBi8pQPsdI+Fwi7v1NH5PAoGBAKrDGb8p6qvp+nC9hGBs 19 | oW3WhL/yZvaqtZYsN78DVSkZpkACwBKeBgDHu3lZHMWQ7uVxzKyVyWwXCSX+y+tM 20 | wRZOcDQzBUsjidWYTxP306USxRdM40FGYGjgy8P9+KEdwhnVT+hN9cwfHF8t6zim 21 | l1+p5ctdRNJJHr35uyahPagdAoGAb4C2s74plnaV9zJNNQzPqhrBLkUcDThhxB63 22 | 9VQlQUYcqONxZEY/0oD1fDsSPjvcfF1zCMa/gvayVsQd9j0yKQX5q0/CwuPh423b 23 | sdgshB0SlLLS72fEYHU+PhkOdnOzz3+GlO+oTUo1e8ZjnQd2I3p+JOQXDS6A4Xpu 24 | fQIECz8CgYAUpk/WCXsKcZordwK+qpkaDsNVH3AiyqEeDHOPIDU3jnz+lqFmRURJ 25 | fvYvZlDhN6ihZ7uSQ9cIV6rwznfYCRor5ZTbnHp5iLlmtMY8toQ9u4MHAL9bAhjh 26 | RNsDGqvQdQpswjObsW8p7Lv0NWdIfBMzso5ocQWq96KIpZZ9gDZGHg== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /lesson-puppet/id_rsa_example.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCuR2oCEdGbDYLau+uY5VdDFAfhWD8XGrbNX3Zq7lFLY4hSTqi55w5F9fYgGlQhR27nMlwNnw42IaBc2o437x4jKZ3egSerBcwexw34vAzpYdSVJokX6ihI3sSG+k5J5CAxIF626evUxZPBX0xwnv0Df7YWpe1iJJD6biRL+r7rk0dMSgA5e4JKIxUVMIe+01kzA7uHLWcAsPW4/ipzx2KA7Gjgl8RrQP+w3BFieMfCO0tmj54RFCf2rJOX8Rb/S16Z3ofxrQ5UhAFBStLLlu1hbIPXI2xEzFLVHCI9FBKCWzxhKEJ8HWsYks6AsIXhVrrj21Oxue+TCK42eXQXUcTN mjuuso@Miiros-MacBook.local 2 | -------------------------------------------------------------------------------- /lesson-puppet/resources.tf: -------------------------------------------------------------------------------- 1 | /* First we'll set some variables for AWS authentication. */ 2 | 3 | variable "aws_access_key" { 4 | default = "" 5 | } 6 | 7 | variable "aws_secret_key" { 8 | default = "" 9 | } 10 | 11 | 12 | /* These are dummy resources pointing to the default VPC and 13 | subnets in our AWS account. We'll be referencing these later. */ 14 | 15 | resource "aws_default_vpc" "default" { 16 | } 17 | 18 | variable "default_zones" { 19 | default = { 20 | zone0 = "eu-west-1a" 21 | zone1 = "eu-west-1b" 22 | zone2 = "eu-west-1c" 23 | } 24 | } 25 | 26 | resource "aws_default_subnet" "default" { 27 | availability_zone = "${lookup(var.default_zones, format("zone%d", count.index))}" 28 | count = 2 29 | } 30 | 31 | 32 | 33 | /* We'll be using AWS for provisioning the instances, 34 | so we'll set "aws" as the provider. */ 35 | 36 | provider "aws" { 37 | access_key = "${var.aws_access_key}" 38 | secret_key = "${var.aws_secret_key}" 39 | region = "eu-west-1" 40 | } 41 | 42 | 43 | /* Security group for the load balancer. We'll open port 80/tcp 44 | to the world. We'll allow everything outbound. */ 45 | 46 | resource "aws_security_group" "gid-lb" { 47 | name = "GID lb" 48 | description = "Security group for the load balancer" 49 | vpc_id = "${aws_default_vpc.default.id}" 50 | ingress { 51 | from_port = 80 52 | to_port = 80 53 | protocol = "tcp" 54 | cidr_blocks = ["0.0.0.0/0"] 55 | } 56 | 57 | egress { 58 | from_port = 0 59 | to_port = 0 60 | protocol = "-1" 61 | cidr_blocks = ["0.0.0.0/0"] 62 | } 63 | 64 | tags { 65 | Author = "Get into DevOps" 66 | } 67 | } 68 | 69 | 70 | /* Security group for the app nodes. We'll allow 8484/tcp from our load 71 | balancer, and 22/tcp from the world (see note below). */ 72 | 73 | resource "aws_security_group" "gid-app" { 74 | name = "gid app" 75 | description = "Security group for the app instances" 76 | vpc_id = "${aws_default_vpc.default.id}" 77 | ingress { 78 | from_port = 80 79 | to_port = 80 80 | protocol = "tcp" 81 | security_groups = ["${aws_security_group.gid-lb.id}"] 82 | } 83 | 84 | ingress { 85 | from_port = 22 86 | to_port = 22 87 | protocol = "tcp" 88 | 89 | /* Good practice would be to set the following to "your.ip.address/32" 90 | instead of "0.0.0.0/0", which will open SSH to the world. */ 91 | cidr_blocks = ["0.0.0.0/0"] 92 | } 93 | 94 | egress { 95 | from_port = 0 96 | to_port = 0 97 | protocol = "-1" 98 | cidr_blocks = ["0.0.0.0/0"] 99 | } 100 | 101 | tags { 102 | Author = "Get into DevOps" 103 | } 104 | } 105 | 106 | 107 | /* Our root SSH key pair is created by the wrapper script. */ 108 | 109 | resource "aws_key_pair" "root" { 110 | key_name = "root-key" 111 | public_key = "${file("id_rsa_example.pub")}" 112 | } 113 | 114 | 115 | /* The app nodes are t2.micros running Ubuntu 16.04 LTS 116 | for simplicity. We place the app nodes in different AZs. */ 117 | 118 | resource "aws_instance" "gid-app" { 119 | ami = "ami-eed00d97" 120 | instance_type = "t2.micro" 121 | count = 1 122 | 123 | tags { 124 | Name = "gid-app${count.index}" 125 | Author = "Get into DevOps" 126 | } 127 | 128 | subnet_id = "${element(aws_default_subnet.default.*.id, count.index)}" 129 | associate_public_ip_address = true 130 | 131 | key_name = "${aws_key_pair.root.key_name}" 132 | vpc_security_group_ids = ["${aws_security_group.gid-app.id}"] 133 | 134 | provisioner "file" { 135 | source = "environments" 136 | destination = "/home/ubuntu" 137 | connection { 138 | user = "ubuntu" 139 | private_key = "${file("id_rsa_example")}" 140 | timeout = "60s" 141 | } 142 | } 143 | 144 | provisioner "remote-exec" { 145 | inline = [ 146 | "curl -O https://apt.puppetlabs.com/puppetlabs-release-pc1-xenial.deb", 147 | "sudo dpkg -i puppetlabs-release-pc1-xenial.deb", 148 | "sudo apt -y update", 149 | "sudo apt -y install puppet-agent", 150 | "sudo cp -a /home/ubuntu/environments/* /etc/puppetlabs/code/environments/", 151 | "sudo /opt/puppetlabs/bin/puppet module install puppetlabs-apache", 152 | "sudo /opt/puppetlabs/bin/puppet apply /etc/puppetlabs/code/environments/production/manifests/site.pp" 153 | ] 154 | connection { 155 | user = "ubuntu" 156 | private_key = "${file("id_rsa_example")}" 157 | timeout = "60s" 158 | } 159 | } 160 | } 161 | 162 | 163 | /* This is the load balancer. */ 164 | 165 | resource "aws_lb" "gid" { 166 | name = "gid-lb" 167 | internal = false 168 | security_groups = ["${aws_security_group.gid-lb.id}"] 169 | subnets = ["${aws_default_subnet.default.*.id}"] 170 | 171 | tags { 172 | Author = "Get into DevOps" 173 | } 174 | } 175 | 176 | resource "aws_lb_listener" "gid" { 177 | load_balancer_arn = "${aws_lb.gid.arn}" 178 | port = "80" 179 | protocol = "HTTP" 180 | 181 | default_action { 182 | target_group_arn = "${aws_lb_target_group.gid.arn}" 183 | type = "forward" 184 | } 185 | } 186 | 187 | resource "aws_lb_target_group" "gid" { 188 | name = "gid-lb-tg" 189 | port = 80 190 | protocol = "HTTP" 191 | vpc_id = "${aws_default_vpc.default.id}" 192 | } 193 | 194 | resource "aws_lb_target_group_attachment" "test" { 195 | target_group_arn = "${aws_lb_target_group.gid.arn}" 196 | target_id = "${element(aws_instance.gid-app.*.id, count.index)}" 197 | port = 80 198 | count = 1 199 | } 200 | 201 | 202 | /* We'll need the load balancer hostname */ 203 | 204 | output "lb-hostname" { 205 | value = "${aws_lb.gid.dns_name}" 206 | } 207 | -------------------------------------------------------------------------------- /lesson-rancher/README.md: -------------------------------------------------------------------------------- 1 | # Lesson: Rancher 2 | In this lesson, we'll configure a virtual machine on Google Compute Engine to run the Rancher master, and a Rancher agent. We'll deploy a simple application on Rancher, and demonstrate Rancher Catalog by installing an Elasticsearch cluster on our Rancher platform. 3 | 4 | Run the following commands on Google Cloud Shell to create the Virtual Machine and firewall rule: 5 | ``` 6 | gcloud beta compute instances create "rancher" --zone "us-east1-b" --machine-type "custom-1-8192-ext" --subnet "default" --maintenance-policy "MIGRATE" --no-service-account --no-scopes --min-cpu-platform "Automatic" --tags "rancher" --image "$(gcloud beta compute images list --filter="family:coreos-alpha" --format="value(NAME)")" --image-project "coreos-cloud" --boot-disk-size "20" --boot-disk-type "pd-standard" --boot-disk-device-name "rancher" 7 | 8 | gcloud compute firewall-rules create rancher-test --direction=INGRESS --priority=1000 --network=default --action=ALLOW --rules="tcp:8000,tcp:8080,tcp:9000" --source-ranges=0.0.0.0/0 --target-tags=rancher 9 | ``` 10 | 11 | Then, SSH into the server with the following command: 12 | ``` 13 | gcloud compute ssh rancher 14 | ``` 15 | 16 | And run the Rancher server: 17 | ``` 18 | docker run -d -p 8080:8080 rancher/server 19 | ``` 20 | 21 | Finally, you will need to run the Rancher node startup command on the Rancher server. You will get this command when you add a host in the Rancher web interface. 22 | 23 | After you have finished testing, run the following command on Google Cloud Shell to terminate the virtual machine: 24 | ``` 25 | gcloud compute instances delete rancher 26 | ``` 27 | 28 | -------------------------------------------------------------------------------- /lesson-rundeck/CreateEnvironmentJobDefinition.yml: -------------------------------------------------------------------------------- 1 | - description: '' 2 | executionEnabled: true 3 | id: 4d9ab787-d97c-4c8b-bda8-44a3e579d3f5 4 | loglevel: INFO 5 | name: CreateEnvironment 6 | nodeFilterEditable: false 7 | options: 8 | - enforced: true 9 | name: ApplicationVersion 10 | required: true 11 | values: 12 | - v1.0 13 | - v1.2 14 | - v2.0 15 | - name: EnvironmentName 16 | required: true 17 | - enforced: true 18 | name: Timeout 19 | required: true 20 | values: 21 | - '300' 22 | - '3600' 23 | - '900' 24 | scheduleEnabled: true 25 | sequence: 26 | commands: 27 | - script: |- 28 | #!/bin/bash 29 | export AWS_ACCESS_KEY_ID="" 30 | export AWS_SECRET_ACCESS_KEY="" 31 | export AWS_DEFAULT_REGION="us-east-1" 32 | 33 | PROVISIONING_SCRIPT="#!/bin/bash 34 | apt-get -y update 35 | apt-get -y install nginx 36 | echo App: $RD_OPTION_APPLICATIONVERSION > /var/www/html/index.html 37 | sleep $RD_OPTION_TIMEOUT 38 | shutdown -h now 39 | " 40 | 41 | EXISTING_SG=$(aws ec2 describe-security-groups --group-names RundeckExample --query 'SecurityGroups[0].{Id:GroupId}' --output text) 42 | 43 | DEFAULT_VPC=$(aws ec2 describe-vpcs --query 'Vpcs[0].{Id:VpcId}' --output text) 44 | 45 | # Create a security group 46 | if [[ "$EXISTING_SG" == "" ]]; then 47 | echo -n "Creating a new security group..." 48 | aws ec2 create-security-group --group-name RundeckExample --description "SG for Rundeck Example" --vpc-id $DEFAULT_VPC 49 | aws ec2 authorize-security-group-ingress --group-name RundeckExample --protocol tcp --port 80 --cidr 0.0.0.0/0 50 | EXISTING_SG=$(aws ec2 describe-security-groups --group-names RundeckExample --query 'SecurityGroups[0].{Id:GroupId}' --output text) 51 | 52 | echo "$EXISTING_SG" 53 | else 54 | echo "Found Security Group $EXISTING_SG" 55 | fi 56 | 57 | # Create the instance 58 | CREATED_ID=$(aws ec2 run-instances --image-id ami-da05a4a0 --count 1 --instance-type t2.micro --security-groups RundeckExample --user-data "$PROVISIONING_SCRIPT" --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=$RD_OPTION_ENVIRONMENTNAME}]" --query 'Instances[0].{Id:InstanceId}' --output text) 59 | 60 | # Get the public hostname 61 | PUBLIC_HOSTNAME=$(aws ec2 describe-instances --instance-id $CREATED_ID --query 'Reservations[0].Instances[0].{Hostname:PublicDnsName}' --output text) 62 | 63 | echo "Environment started in http://$PUBLIC_HOSTNAME" 64 | keepgoing: false 65 | strategy: node-first 66 | uuid: 4d9ab787-d97c-4c8b-bda8-44a3e579d3f5 67 | -------------------------------------------------------------------------------- /lesson-rundeck/Dockerfile: -------------------------------------------------------------------------------- 1 | # Based on https://hub.docker.com/r/jordan/rundeck/ by https://github.com/jjethwa 2 | FROM jordan/rundeck:latest 3 | 4 | # Install AWS CLI 5 | RUN apt-get -y update && apt-get -y install python-pip && pip install awscli 6 | 7 | -------------------------------------------------------------------------------- /lesson-rundeck/README.md: -------------------------------------------------------------------------------- 1 | # Lesson: Rundeck 2 | To run the Rundeck Docker image locally: 3 | ``` 4 | docker run -p4440:4440 -e SERVER_URL=http://localhost:4440 getintodevops/rundeck 5 | ``` 6 | 7 | The Dockerfile for the above image is included in this directory. 8 | 9 | Our automation script is in inline_script.txt. The full job definition of our example job is in CreateEnvironmentJobDefinition.yml. 10 | 11 | https://getintodevops.com 12 | -------------------------------------------------------------------------------- /lesson-rundeck/inline_script.txt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export AWS_ACCESS_KEY_ID="YOUR-AWS-KEY-HERE" 3 | export AWS_SECRET_ACCESS_KEY="YOUR-AWS-SECRET-HERE" 4 | export AWS_DEFAULT_REGION="us-east-1" 5 | 6 | PROVISIONING_SCRIPT="#!/bin/bash 7 | apt-get -y update 8 | apt-get -y install nginx 9 | echo App: $RD_OPTION_APPLICATIONVERSION > /var/www/html/index.html 10 | sleep $RD_OPTION_TIMEOUT 11 | shutdown -h now 12 | " 13 | 14 | EXISTING_SG=$(aws ec2 describe-security-groups --group-names RundeckExample --query 'SecurityGroups[0].{Id:GroupId}' --output text) 15 | 16 | DEFAULT_VPC=$(aws ec2 describe-vpcs --query 'Vpcs[0].{Id:VpcId}' --output text) 17 | 18 | # Create a security group 19 | if [[ "$EXISTING_SG" == "" ]]; then 20 | echo -n "Creating a new security group..." 21 | aws ec2 create-security-group --group-name RundeckExample --description "SG for Rundeck Example" --vpc-id $DEFAULT_VPC 22 | aws ec2 authorize-security-group-ingress --group-name RundeckExample --protocol tcp --port 80 --cidr 0.0.0.0/0 23 | EXISTING_SG=$(aws ec2 describe-security-groups --group-names RundeckExample --query 'SecurityGroups[0].{Id:GroupId}' --output text) 24 | 25 | echo "$EXISTING_SG" 26 | else 27 | echo "Found Security Group $EXISTING_SG" 28 | fi 29 | 30 | # Create the instance 31 | CREATED_ID=$(aws ec2 run-instances --image-id ami-da05a4a0 --count 1 --instance-type t2.micro --security-groups RundeckExample --user-data "$PROVISIONING_SCRIPT" --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=$RD_OPTION_ENVIRONMENTNAME}]" --query 'Instances[0].{Id:InstanceId}' --output text) 32 | 33 | # Get the public hostname 34 | PUBLIC_HOSTNAME=$(aws ec2 describe-instances --instance-id $CREATED_ID --query 'Reservations[0].Instances[0].{Hostname:PublicDnsName}' --output text) 35 | 36 | echo "Environment started in http://$PUBLIC_HOSTNAME" 37 | -------------------------------------------------------------------------------- /lesson-terraform/.gitignore: -------------------------------------------------------------------------------- 1 | terraform.tfstate* 2 | .terraform/ 3 | -------------------------------------------------------------------------------- /lesson-terraform/README.md: -------------------------------------------------------------------------------- 1 | # Lesson: Terraform 2 | In this lesson, we are provisioning a load balancer and two web servers in nginx. 3 | 4 | ## To see what changes would be done 5 | ``` 6 | terraform plan -var 'aws_access_key=ACCESS_KEY_HERE' -var 'aws_secret_key=SECRET_KEY_HERE' 7 | ``` 8 | 9 | ## To apply the changes 10 | ``` 11 | terraform apply -var 'aws_access_key=ACCESS_KEY_HERE' -var 'aws_secret_key=SECRET_KEY_HERE' 12 | ``` 13 | 14 | ## To destroy all Terraform-managed infrastructure 15 | ``` 16 | terraform destroy -var 'aws_access_key=ACCESS_KEY_HERE' -var 'aws_secret_key=SECRET_KEY_HERE' 17 | ``` 18 | -------------------------------------------------------------------------------- /lesson-terraform/id_rsa_example: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEArkdqAhHRmw2C2rvrmOVXQxQH4Vg/Fxq2zV92au5RS2OIUk6o 3 | uecORfX2IBpUIUdu5zJcDZ8ONiGgXNqON+8eIymd3oEnqwXMHscN+LwM6WHUlSaJ 4 | F+ooSN7EhvpOSeQgMSBetunr1MWTwV9McJ79A3+2FqXtYiSQ+m4kS/q+65NHTEoA 5 | OXuCSiMVFTCHvtNZMwO7hy1nALD1uP4qc8digOxo4JfEa0D/sNwRYnjHwjtLZo+e 6 | ERQn9qyTl/EW/0temd6H8a0OVIQBQUrSy5btYWyD1yNsRMxS1RwiPRQSgls8YShC 7 | fB1rGJLOgLCF4Va649tTsbnvkwiuNnl0F1HEzQIDAQABAoIBAQCYlmY2UauXegjr 8 | wkTQpVv1mwAbSMxUSktrHP3tOaNrwkWE6uHHgol4r5VDHkoyqPxs6Ca4tl4W/vIM 9 | 2j5SssVMPA/A1IwKrRS+jcGp2dvkKoeZ3xeBHL8Qrhqbo2QQRe/5k0HaHI0iyapI 10 | UEv4/vq/qX2f7r8cgq981Bpre6rVTbcKeEn4cIJ1iK91DuWhHo7CFVJI3X6fizRg 11 | 6eqraNLxOMPU1BN4uZmU9hsSgv9E3QAve79LQ1zO9CDGPgroqmCpwDFs2zeGYym7 12 | Wu52p0Fy0Axnoq8ITMw4dW6TievFWK23nB6WIw11P4NfDhfvwwR5hCd6knXZRP8x 13 | hdtmf2TxAoGBAOJLvt7Lx0EH6VyRArufj37q9zPKvP1+LIsc7Q9uf8JKJS0ScHD2 14 | coTxNPDV95twcmnfuuqCat+7XZt4t1e+4ZviLOzaanDKXajb7E9XE9n5siKRmQ93 15 | LsgW4jKYEaMKu2itPIBTT8KnCetzYGPQFhBNYuN49Kk9uEpBKv+D84AjAoGBAMUn 16 | vHRtPzVUF9OwGueD/JgozOPUSELVQr5lUrWGXJonsyJnRa48KKUMyM1iTruIpFPq 17 | wCX41S7PCHF7TE09WxFGWbNNmSYNzh1L/UNKuhy6uQuF9+gmGKilsti+cBDsOkmp 18 | xrfjTc+9KVZFkyGYWd0gBi8pQPsdI+Fwi7v1NH5PAoGBAKrDGb8p6qvp+nC9hGBs 19 | oW3WhL/yZvaqtZYsN78DVSkZpkACwBKeBgDHu3lZHMWQ7uVxzKyVyWwXCSX+y+tM 20 | wRZOcDQzBUsjidWYTxP306USxRdM40FGYGjgy8P9+KEdwhnVT+hN9cwfHF8t6zim 21 | l1+p5ctdRNJJHr35uyahPagdAoGAb4C2s74plnaV9zJNNQzPqhrBLkUcDThhxB63 22 | 9VQlQUYcqONxZEY/0oD1fDsSPjvcfF1zCMa/gvayVsQd9j0yKQX5q0/CwuPh423b 23 | sdgshB0SlLLS72fEYHU+PhkOdnOzz3+GlO+oTUo1e8ZjnQd2I3p+JOQXDS6A4Xpu 24 | fQIECz8CgYAUpk/WCXsKcZordwK+qpkaDsNVH3AiyqEeDHOPIDU3jnz+lqFmRURJ 25 | fvYvZlDhN6ihZ7uSQ9cIV6rwznfYCRor5ZTbnHp5iLlmtMY8toQ9u4MHAL9bAhjh 26 | RNsDGqvQdQpswjObsW8p7Lv0NWdIfBMzso5ocQWq96KIpZZ9gDZGHg== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /lesson-terraform/id_rsa_example.pub: -------------------------------------------------------------------------------- 1 | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCuR2oCEdGbDYLau+uY5VdDFAfhWD8XGrbNX3Zq7lFLY4hSTqi55w5F9fYgGlQhR27nMlwNnw42IaBc2o437x4jKZ3egSerBcwexw34vAzpYdSVJokX6ihI3sSG+k5J5CAxIF626evUxZPBX0xwnv0Df7YWpe1iJJD6biRL+r7rk0dMSgA5e4JKIxUVMIe+01kzA7uHLWcAsPW4/ipzx2KA7Gjgl8RrQP+w3BFieMfCO0tmj54RFCf2rJOX8Rb/S16Z3ofxrQ5UhAFBStLLlu1hbIPXI2xEzFLVHCI9FBKCWzxhKEJ8HWsYks6AsIXhVrrj21Oxue+TCK42eXQXUcTN mjuuso@Miiros-MacBook.local 2 | -------------------------------------------------------------------------------- /lesson-terraform/resources.tf: -------------------------------------------------------------------------------- 1 | /* First we'll set some variables for AWS authentication. */ 2 | 3 | variable "aws_access_key" { 4 | default = "" 5 | } 6 | 7 | variable "aws_secret_key" { 8 | default = "" 9 | } 10 | 11 | 12 | /* These are dummy resources pointing to the default VPC and 13 | subnets in our AWS account. We'll be referencing these later. */ 14 | 15 | resource "aws_default_vpc" "default" { 16 | } 17 | 18 | variable "default_zones" { 19 | default = { 20 | zone0 = "eu-west-1a" 21 | zone1 = "eu-west-1b" 22 | zone2 = "eu-west-1c" 23 | } 24 | } 25 | 26 | resource "aws_default_subnet" "default" { 27 | availability_zone = "${lookup(var.default_zones, format("zone%d", count.index))}" 28 | count = 2 29 | } 30 | 31 | 32 | 33 | /* We'll be using AWS for provisioning the instances, 34 | so we'll set "aws" as the provider. */ 35 | 36 | provider "aws" { 37 | access_key = "${var.aws_access_key}" 38 | secret_key = "${var.aws_secret_key}" 39 | region = "eu-west-1" 40 | } 41 | 42 | 43 | /* Security group for the load balancer. We'll open port 80/tcp 44 | to the world. We'll allow everything outbound. */ 45 | 46 | resource "aws_security_group" "gid-lb" { 47 | name = "GID lb" 48 | description = "Security group for the load balancer" 49 | vpc_id = "${aws_default_vpc.default.id}" 50 | ingress { 51 | from_port = 80 52 | to_port = 80 53 | protocol = "tcp" 54 | cidr_blocks = ["0.0.0.0/0"] 55 | } 56 | 57 | egress { 58 | from_port = 0 59 | to_port = 0 60 | protocol = "-1" 61 | cidr_blocks = ["0.0.0.0/0"] 62 | } 63 | 64 | tags { 65 | Author = "Get into DevOps" 66 | } 67 | } 68 | 69 | 70 | /* Security group for the app nodes. We'll allow 8484/tcp from our load 71 | balancer, and 22/tcp from the world (see note below). */ 72 | 73 | resource "aws_security_group" "gid-app" { 74 | name = "gid app" 75 | description = "Security group for the app instances" 76 | vpc_id = "${aws_default_vpc.default.id}" 77 | ingress { 78 | from_port = 80 79 | to_port = 80 80 | protocol = "tcp" 81 | security_groups = ["${aws_security_group.gid-lb.id}"] 82 | } 83 | 84 | ingress { 85 | from_port = 22 86 | to_port = 22 87 | protocol = "tcp" 88 | 89 | /* Good practice would be to set the following to "your.ip.address/32" 90 | instead of "0.0.0.0/0", which will open SSH to the world. */ 91 | cidr_blocks = ["0.0.0.0/0"] 92 | } 93 | 94 | egress { 95 | from_port = 0 96 | to_port = 0 97 | protocol = "-1" 98 | cidr_blocks = ["0.0.0.0/0"] 99 | } 100 | 101 | tags { 102 | Author = "Get into DevOps" 103 | } 104 | } 105 | 106 | 107 | /* Our root SSH key pair is created by the wrapper script. */ 108 | 109 | resource "aws_key_pair" "root" { 110 | key_name = "root-key" 111 | public_key = "${file("id_rsa_example.pub")}" 112 | } 113 | 114 | 115 | /* The app nodes are t2.micros running Ubuntu 16.04 LTS 116 | for simplicity. We place the app nodes in different AZs. */ 117 | 118 | resource "aws_instance" "gid-app" { 119 | ami = "ami-eed00d97" 120 | instance_type = "t2.micro" 121 | count = 2 122 | 123 | tags { 124 | Name = "gid-app${count.index}" 125 | Author = "Get into DevOps" 126 | } 127 | 128 | subnet_id = "${element(aws_default_subnet.default.*.id, count.index)}" 129 | associate_public_ip_address = true 130 | 131 | key_name = "${aws_key_pair.root.key_name}" 132 | vpc_security_group_ids = ["${aws_security_group.gid-app.id}"] 133 | 134 | provisioner "remote-exec" { 135 | inline = [ 136 | "sudo apt update", 137 | "sudo apt -y install nginx" 138 | ] 139 | connection { 140 | user = "ubuntu" 141 | private_key = "${file("id_rsa_example")}" 142 | timeout = "60s" 143 | } 144 | } 145 | } 146 | 147 | 148 | /* This is the load balancer. */ 149 | 150 | resource "aws_lb" "gid" { 151 | name = "gid-lb" 152 | internal = false 153 | security_groups = ["${aws_security_group.gid-lb.id}"] 154 | subnets = ["${aws_default_subnet.default.*.id}"] 155 | 156 | tags { 157 | Author = "Get into DevOps" 158 | } 159 | } 160 | 161 | resource "aws_lb_listener" "gid" { 162 | load_balancer_arn = "${aws_lb.gid.arn}" 163 | port = "80" 164 | protocol = "HTTP" 165 | 166 | default_action { 167 | target_group_arn = "${aws_lb_target_group.gid.arn}" 168 | type = "forward" 169 | } 170 | } 171 | 172 | resource "aws_lb_target_group" "gid" { 173 | name = "gid-lb-tg" 174 | port = 80 175 | protocol = "HTTP" 176 | vpc_id = "${aws_default_vpc.default.id}" 177 | } 178 | 179 | resource "aws_lb_target_group_attachment" "test" { 180 | target_group_arn = "${aws_lb_target_group.gid.arn}" 181 | target_id = "${element(aws_instance.gid-app.*.id, count.index)}" 182 | port = 80 183 | count = 2 184 | } 185 | 186 | 187 | /* We'll need the load balancer hostname */ 188 | 189 | output "lb-hostname" { 190 | value = "${aws_lb.gid.dns_name}" 191 | } 192 | --------------------------------------------------------------------------------