├── README.md ├── hello-world └── hello.service ├── nginx ├── Dockerfile ├── boot.sh ├── nginx.conf.tmpl ├── nginx.service └── nginx.toml ├── sinatra ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── Procfile ├── app.rb ├── boot.sh ├── config.ru ├── sinatra.service ├── sinatra@5000.service └── sinatra@5001.service ├── stack.json └── stack.yml /README.md: -------------------------------------------------------------------------------- 1 | # CoreOS blog post code 2 | 3 | This is the source code belonging to [this blog post](http://marceldegraaf.net/2014/04/24/experimenting-with-coreos-confd-etcd-fleet-and-cloudformation.html) 4 | on CoreOS, confd, etcd, fleet, and CloudFormation. 5 | -------------------------------------------------------------------------------- /hello-world/hello.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Hello World 3 | After=docker.service 4 | Requires=docker.service 5 | 6 | [Service] 7 | EnvironmentFile=/etc/environment 8 | ExecStartPre=/usr/bin/etcdctl set /test/%m ${COREOS_PUBLIC_IPV4} 9 | ExecStart=/usr/bin/docker run --name test --rm busybox /bin/sh -c "while true; do echo Hello World; sleep 1; done" 10 | ExecStop=/usr/bin/etcdctl rm /test/%m 11 | ExecStop=/usr/bin/docker kill test 12 | -------------------------------------------------------------------------------- /nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM stackbrew/ubuntu:saucy 2 | MAINTAINER Marcel de Graaf 3 | 4 | # Install Nginx 5 | RUN apt-get install -y --force-yes software-properties-common 6 | RUN add-apt-repository ppa:nginx/stable 7 | RUN apt-get update 8 | RUN apt-get install -y --force-yes nginx curl 9 | 10 | # Install confd 11 | RUN curl -L https://github.com/kelseyhightower/confd/releases/download/v0.3.0/confd_0.3.0_linux_amd64.tar.gz | tar xz 12 | RUN mv confd /usr/local/bin/confd 13 | 14 | # Create directories 15 | RUN mkdir -p /etc/confd/conf.d 16 | RUN mkdir -p /etc/confd/templates 17 | 18 | # Add confd files 19 | ADD ./nginx.conf.tmpl /etc/confd/templates/nginx.conf.tmpl 20 | ADD ./nginx.toml /etc/confd/conf.d/nginx.toml 21 | 22 | # Remove default site 23 | RUN rm -f /etc/nginx/sites-enabled/default 24 | 25 | # Add boot script 26 | ADD ./boot.sh /opt/boot.sh 27 | RUN chmod +x /opt/boot.sh 28 | 29 | # Run the boot script 30 | CMD /opt/boot.sh 31 | -------------------------------------------------------------------------------- /nginx/boot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Fail hard and fast 4 | set -eo pipefail 5 | 6 | export ETCD_PORT=${ETCD_PORT:-4001} 7 | export HOST_IP=${HOST_IP:-172.17.42.1} 8 | export ETCD=$HOST_IP:4001 9 | 10 | echo "[nginx] booting container. ETCD: $ETCD" 11 | 12 | # Loop until confd has updated the nginx config 13 | until confd -onetime -node $ETCD -config-file /etc/confd/conf.d/nginx.toml; do 14 | echo "[nginx] waiting for confd to refresh nginx.conf" 15 | sleep 5 16 | done 17 | 18 | # Run confd in the background to watch the upstream servers 19 | confd -interval 10 -node $ETCD -config-file /etc/confd/conf.d/nginx.toml & 20 | echo "[nginx] confd is listening for changes on etcd..." 21 | 22 | # Start nginx 23 | echo "[nginx] starting nginx service..." 24 | service nginx start 25 | 26 | # Tail all nginx log files 27 | tail -f /var/log/nginx/*.log 28 | -------------------------------------------------------------------------------- /nginx/nginx.conf.tmpl: -------------------------------------------------------------------------------- 1 | upstream app { 2 | {{ range $server := .app_server }} 3 | server {{ $server.Value }}; 4 | {{ end }} 5 | } 6 | 7 | server { 8 | server_name _; 9 | 10 | location / { 11 | proxy_pass http://app; 12 | proxy_redirect off; 13 | proxy_set_header Host $host; 14 | proxy_set_header X-Real-IP $remote_addr; 15 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /nginx/nginx.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=nginx 3 | 4 | [Service] 5 | EnvironmentFile=/etc/environment 6 | ExecStartPre=/usr/bin/docker pull marceldegraaf/nginx 7 | ExecStart=/usr/bin/docker run --rm --name nginx -p 80:80 -e HOST_IP=${COREOS_PUBLIC_IPV4} marceldegraaf/nginx 8 | ExecStop=/usr/bin/docker kill nginx 9 | 10 | [X-Fleet] 11 | X-Conflicts=nginx.service 12 | -------------------------------------------------------------------------------- /nginx/nginx.toml: -------------------------------------------------------------------------------- 1 | [template] 2 | keys = [ "app/server" ] 3 | owner = "nginx" 4 | mode = "0644" 5 | src = "nginx.conf.tmpl" 6 | dest = "/etc/nginx/sites-enabled/app.conf" 7 | check_cmd = "/usr/sbin/nginx -t -c /etc/nginx/nginx.conf" 8 | reload_cmd = "/usr/sbin/service nginx reload" 9 | -------------------------------------------------------------------------------- /sinatra/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM paintedfox/ruby 2 | MAINTAINER Marcel de Graaf 3 | 4 | RUN apt-get install -y curl 5 | 6 | RUN gem install sinatra foreman thin --no-ri --no-rdoc 7 | ADD . /opt/app 8 | EXPOSE 5000 9 | 10 | CMD /opt/app/boot.sh 11 | -------------------------------------------------------------------------------- /sinatra/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | ruby "2.1.1" 4 | 5 | gem "sinatra" 6 | gem "thin" 7 | -------------------------------------------------------------------------------- /sinatra/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | daemons (1.1.9) 5 | eventmachine (1.0.3) 6 | rack (1.5.2) 7 | rack-protection (1.5.3) 8 | rack 9 | sinatra (1.4.5) 10 | rack (~> 1.4) 11 | rack-protection (~> 1.4) 12 | tilt (~> 1.3, >= 1.3.4) 13 | thin (1.6.2) 14 | daemons (>= 1.0.9) 15 | eventmachine (>= 1.0.0) 16 | rack (>= 1.0.0) 17 | tilt (1.4.1) 18 | 19 | PLATFORMS 20 | ruby 21 | 22 | DEPENDENCIES 23 | sinatra 24 | thin 25 | -------------------------------------------------------------------------------- /sinatra/Procfile: -------------------------------------------------------------------------------- 1 | web: thin start -p $PORT 2 | -------------------------------------------------------------------------------- /sinatra/app.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra' 2 | 3 | get '/' do 4 | 'Hello world!' 5 | end 6 | -------------------------------------------------------------------------------- /sinatra/boot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Fail hard and fast 4 | set -eo pipefail 5 | 6 | export PORT=${PORT:-5000} 7 | 8 | echo "[app] container booted. PORT: $PORT" 9 | echo "[app] starting foreman..." 10 | 11 | cd /opt/app && foreman start 12 | -------------------------------------------------------------------------------- /sinatra/config.ru: -------------------------------------------------------------------------------- 1 | require './app' 2 | 3 | $stdout.sync = true 4 | 5 | run Sinatra::Application 6 | -------------------------------------------------------------------------------- /sinatra/sinatra.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=sinatra 3 | 4 | [Service] 5 | EnvironmentFile=/etc/environment 6 | ExecStartPre=/usr/bin/docker pull marceldegraaf/sinatra 7 | ExecStart=/usr/bin/docker run --name sinatra-%i --rm -p %i:5000 -e PORT=5000 marceldegraaf/sinatra 8 | ExecStartPost=/usr/bin/etcdctl set /app/server/%i ${COREOS_PUBLIC_IPV4}:%i 9 | ExecStop=/usr/bin/docker kill sinatra-%i 10 | ExecStopPost=/usr/bin/etcdctl rm /app/server/%i 11 | 12 | [X-Fleet] 13 | X-Conflicts=sinatra@%i.service 14 | -------------------------------------------------------------------------------- /sinatra/sinatra@5000.service: -------------------------------------------------------------------------------- 1 | sinatra.service -------------------------------------------------------------------------------- /sinatra/sinatra@5001.service: -------------------------------------------------------------------------------- 1 | sinatra.service -------------------------------------------------------------------------------- /stack.json: -------------------------------------------------------------------------------- 1 | {"AWSTemplateFormatVersion":"2010-09-09","Description":"CoreOS stack","Mappings":{"RegionMap":{"eu-west-1":{"ami":"ami-8368adf4"},"us-east-1":{"ami":"ami-3becf652"},"us-west-1":{"ami":"ami-e4dde5a1"},"us-west-2":{"ami":"ami-388bfe08"},"ap-southeast-1":{"ami":"ami-28bdee7a"},"ap-southeast-2":{"ami":"ami-7d2fb747"},"ap-northeast-1":{"ami":"ami-fb5822fa"},"sa-east-1":{"ami":"ami-83c76a9e"}}},"Parameters":{"InstanceType":{"Description":"EC2 instance type (e.g. m1.small, c1.medium, ...)","Type":"String","Default":"c3.large","AllowedValues":["t1.micro","m1.small","m1.medium","m1.large","m1.xlarge","m3.xlarge","m3.2xlarge","m2.xlarge","m2.2xlarge","m2.4xlarge","c1.medium","c1.xlarge","cc1.4xlarge","cc2.8xlarge","cg1.4xlarge","hi1.4xlarge","hs1.8xlarge","c3.large","c3.xlarge"],"ConstraintDescription":"Must be a valid EC2 instance type"},"ClusterSize":{"Description":"Number of nodes in your cluster (3-12)","Type":"Number","Default":2,"MinValue":2,"MaxValue":12},"DiscoveryURL":{"Description":"An unique etcd cluster discovery URL. Grab a new token from https://discovery.etcd.io/new","Type":"String"},"AdvertisedIPAddress":{"Type":"String","Description":"Use 'private' if your etcd cluster is within one region or 'public' if it spans regions or cloud providers.","Default":"private","AllowedValues":["private","public"]},"AllowSSHFrom":{"Description":"The net block (CIDR) that SSH is available to.","Default":"0.0.0.0/0","Type":"String"},"KeyPair":{"Description":"The name of an existing EC2 Key Pair to allow SSH access to the instance.","Type":"String"}},"Resources":{"CoreOSELB":{"Type":"AWS::ElasticLoadBalancing::LoadBalancer","Properties":{"CrossZone":true,"Listeners":[{"InstancePort":80,"Protocol":"HTTP","LoadBalancerPort":80}],"HealthCheck":{"HealthyThreshold":2,"Timeout":9,"Interval":10,"UnhealthyThreshold":5,"Target":"HTTP:80/"},"AvailabilityZones":{"Fn::GetAZs":{"Ref":"AWS::Region"}}}},"CoreOSAccessKey":{"Type":"AWS::IAM::AccessKey","Properties":{"UserName":{"Ref":"CoreOSUser"}}},"CoreOSUser":{"DependsOn":"CoreOSELB","Type":"AWS::IAM::User","Properties":{"Policies":[{"PolicyName":"ebs","PolicyDocument":{"Statement":[{"Effect":"Allow","Action":"elb:*","Resource":"*"}]}}]}},"CoreOSSecurityGroup":{"DependsOn":"CoreOSELB","Type":"AWS::EC2::SecurityGroup","Properties":{"GroupDescription":"CoreOS SecurityGroup","SecurityGroupIngress":[{"IpProtocol":"tcp","FromPort":22,"ToPort":22,"CidrIp":{"Ref":"AllowSSHFrom"}},{"FromPort":80,"ToPort":80,"IpProtocol":"tcp","SourceSecurityGroupOwnerId":{"Fn::GetAtt":["CoreOSELB","SourceSecurityGroup.OwnerAlias"]},"SourceSecurityGroupName":{"Fn::GetAtt":["CoreOSELB","SourceSecurityGroup.GroupName"]}}]}},"Ingress4001":{"Type":"AWS::EC2::SecurityGroupIngress","Properties":{"GroupName":{"Ref":"CoreOSSecurityGroup"},"IpProtocol":"tcp","FromPort":4001,"ToPort":4001,"SourceSecurityGroupId":{"Fn::GetAtt":["CoreOSSecurityGroup","GroupId"]}}},"Ingress7001":{"Type":"AWS::EC2::SecurityGroupIngress","Properties":{"GroupName":{"Ref":"CoreOSSecurityGroup"},"IpProtocol":"tcp","FromPort":7001,"ToPort":7001,"SourceSecurityGroupId":{"Fn::GetAtt":["CoreOSSecurityGroup","GroupId"]}}},"IngressApps":{"Type":"AWS::EC2::SecurityGroupIngress","Properties":{"GroupName":{"Ref":"CoreOSSecurityGroup"},"IpProtocol":"tcp","FromPort":5000,"ToPort":6000,"SourceSecurityGroupId":{"Fn::GetAtt":["CoreOSSecurityGroup","GroupId"]}}},"CoreOSServerAutoScale":{"Type":"AWS::AutoScaling::AutoScalingGroup","Properties":{"AvailabilityZones":{"Fn::GetAZs":{"Ref":"AWS::Region"}},"LaunchConfigurationName":{"Ref":"CoreOSServerLaunchConfig"},"MinSize":2,"MaxSize":12,"DesiredCapacity":{"Ref":"ClusterSize"},"LoadBalancerNames":[{"Ref":"CoreOSELB"}],"Tags":[{"Key":"Name","Value":{"Ref":"AWS::StackName"},"PropagateAtLaunch":true}]}},"CoreOSServerLaunchConfig":{"Type":"AWS::AutoScaling::LaunchConfiguration","Properties":{"ImageId":{"Fn::FindInMap":["RegionMap",{"Ref":"AWS::Region"},"ami"]},"InstanceType":{"Ref":"InstanceType"},"KeyName":{"Ref":"KeyPair"},"SecurityGroups":[{"Ref":"CoreOSSecurityGroup"}],"UserData":{"Fn::Base64":{"Fn::Join":["",["#cloud-config\n\n","coreos:\n"," etcd:\n"," discovery: ",{"Ref":"DiscoveryURL"},"\n"," addr: $",{"Ref":"AdvertisedIPAddress"},"_ipv4:4001\n"," peer-addr: $",{"Ref":"AdvertisedIPAddress"},"_ipv4:7001\n"," units:\n"," - name: etcd.service\n"," command: start\n"," - name: fleet.service\n"," command: start\n"]]}}}}},"Outputs":{"AccessKeyId":{"Description":"AWS AccessKeyId for the coreos IAM user","Value":{"Ref":"CoreOSAccessKey"}},"SecretAccessKey":{"Description":"AWS SecretAccessKey for the coreos IAM user","Value":{"Fn::GetAtt":["CoreOSAccessKey","SecretAccessKey"]}}}} 2 | -------------------------------------------------------------------------------- /stack.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Description: CoreOS stack 3 | 4 | # Mappings 5 | # ================================================= 6 | 7 | Mappings: 8 | RegionMap: 9 | eu-west-1: 10 | ami: ami-8368adf4 11 | us-east-1: 12 | ami: ami-3becf652 13 | us-west-1: 14 | ami: ami-e4dde5a1 15 | us-west-2: 16 | ami: ami-388bfe08 17 | ap-southeast-1: 18 | ami: ami-28bdee7a 19 | ap-southeast-2: 20 | ami: ami-7d2fb747 21 | ap-northeast-1: 22 | ami: ami-fb5822fa 23 | sa-east-1: 24 | ami: ami-83c76a9e 25 | 26 | 27 | # Parameters 28 | # ================================================= 29 | 30 | Parameters: 31 | InstanceType: 32 | Description: "EC2 instance type (e.g. m1.small, c1.medium, ...)" 33 | Type: String 34 | Default: c3.large 35 | AllowedValues: [ t1.micro, m1.small, m1.medium, m1.large, m1.xlarge, m3.xlarge, m3.2xlarge, m2.xlarge, m2.2xlarge, m2.4xlarge, c1.medium, c1.xlarge, cc1.4xlarge, cc2.8xlarge, cg1.4xlarge, hi1.4xlarge, hs1.8xlarge, c3.large, c3.xlarge ] 36 | ConstraintDescription: Must be a valid EC2 instance type 37 | 38 | ClusterSize: 39 | Description: Number of nodes in your cluster (3-12) 40 | Type: Number 41 | Default: 2 42 | MinValue: 2 43 | MaxValue: 12 44 | 45 | DiscoveryURL: 46 | Description: An unique etcd cluster discovery URL. Grab a new token from https://discovery.etcd.io/new 47 | Type: String 48 | 49 | AdvertisedIPAddress: 50 | Type: String 51 | Description: "Use 'private' if your etcd cluster is within one region or 'public' if it spans regions or cloud providers." 52 | Default: private 53 | AllowedValues: [ private, public ] 54 | 55 | AllowSSHFrom: 56 | Description: The net block (CIDR) that SSH is available to. 57 | Default: "0.0.0.0/0" 58 | Type: String 59 | 60 | KeyPair: 61 | Description: The name of an existing EC2 Key Pair to allow SSH access to the instance. 62 | Type: String 63 | 64 | 65 | # Resources 66 | # ================================================= 67 | 68 | Resources: 69 | 70 | CoreOSELB: 71 | Type: AWS::ElasticLoadBalancing::LoadBalancer 72 | Properties: 73 | CrossZone: true 74 | Listeners: 75 | - InstancePort: 80 76 | Protocol: HTTP 77 | LoadBalancerPort: 80 78 | HealthCheck: 79 | HealthyThreshold: 2 80 | Timeout: 9 81 | Interval: 10 82 | UnhealthyThreshold: 5 83 | Target: "HTTP:80/" 84 | AvailabilityZones: 85 | "Fn::GetAZs": 86 | Ref: "AWS::Region" 87 | 88 | CoreOSAccessKey: 89 | Type: AWS::IAM::AccessKey 90 | Properties: 91 | UserName: 92 | Ref: CoreOSUser 93 | 94 | CoreOSUser: 95 | DependsOn: CoreOSELB 96 | Type: AWS::IAM::User 97 | Properties: 98 | Policies: 99 | - PolicyName: ebs 100 | PolicyDocument: 101 | Statement: 102 | - Effect: Allow 103 | Action: "elb:*" 104 | Resource: "*" 105 | 106 | CoreOSSecurityGroup: 107 | DependsOn: CoreOSELB 108 | Type: AWS::EC2::SecurityGroup 109 | Properties: 110 | GroupDescription: CoreOS SecurityGroup 111 | SecurityGroupIngress: 112 | - IpProtocol: tcp 113 | FromPort: 22 114 | ToPort: 22 115 | CidrIp: 116 | Ref: AllowSSHFrom 117 | 118 | - FromPort: 80 119 | ToPort: 80 120 | IpProtocol: tcp 121 | SourceSecurityGroupOwnerId: 122 | "Fn::GetAtt": 123 | - CoreOSELB 124 | - "SourceSecurityGroup.OwnerAlias" 125 | SourceSecurityGroupName: 126 | "Fn::GetAtt": 127 | - CoreOSELB 128 | - "SourceSecurityGroup.GroupName" 129 | 130 | Ingress4001: 131 | Type: AWS::EC2::SecurityGroupIngress 132 | Properties: 133 | GroupName: 134 | Ref: CoreOSSecurityGroup 135 | IpProtocol: tcp 136 | FromPort: 4001 137 | ToPort: 4001 138 | SourceSecurityGroupId: 139 | "Fn::GetAtt": 140 | - CoreOSSecurityGroup 141 | - GroupId 142 | 143 | Ingress7001: 144 | Type: AWS::EC2::SecurityGroupIngress 145 | Properties: 146 | GroupName: 147 | Ref: CoreOSSecurityGroup 148 | IpProtocol: tcp 149 | FromPort: 7001 150 | ToPort: 7001 151 | SourceSecurityGroupId: 152 | "Fn::GetAtt": 153 | - CoreOSSecurityGroup 154 | - GroupId 155 | 156 | IngressApps: 157 | Type: AWS::EC2::SecurityGroupIngress 158 | Properties: 159 | GroupName: 160 | Ref: CoreOSSecurityGroup 161 | IpProtocol: tcp 162 | FromPort: 5000 163 | ToPort: 6000 164 | SourceSecurityGroupId: 165 | "Fn::GetAtt": 166 | - CoreOSSecurityGroup 167 | - GroupId 168 | 169 | CoreOSServerAutoScale: 170 | Type: AWS::AutoScaling::AutoScalingGroup 171 | Properties: 172 | AvailabilityZones: 173 | "Fn::GetAZs": 174 | Ref: "AWS::Region" 175 | LaunchConfigurationName: 176 | Ref: CoreOSServerLaunchConfig 177 | MinSize: 2 178 | MaxSize: 12 179 | DesiredCapacity: 180 | Ref: ClusterSize 181 | LoadBalancerNames: 182 | - Ref: CoreOSELB 183 | Tags: 184 | - Key: Name 185 | Value: 186 | Ref: "AWS::StackName" 187 | PropagateAtLaunch: true 188 | 189 | CoreOSServerLaunchConfig: 190 | Type: AWS::AutoScaling::LaunchConfiguration 191 | Properties: 192 | ImageId: 193 | "Fn::FindInMap": 194 | - RegionMap 195 | - Ref: AWS::Region 196 | - ami 197 | InstanceType: 198 | Ref: InstanceType 199 | KeyName: 200 | Ref: KeyPair 201 | SecurityGroups: 202 | - Ref: CoreOSSecurityGroup 203 | UserData: 204 | "Fn::Base64": 205 | "Fn::Join": 206 | - "" 207 | - 208 | - "#cloud-config\n\n" 209 | - "coreos:\n" 210 | - " etcd:\n" 211 | - " discovery: " 212 | - Ref: DiscoveryURL 213 | - "\n" 214 | - " addr: $" 215 | - Ref: AdvertisedIPAddress 216 | - "_ipv4:4001\n" 217 | - " peer-addr: $" 218 | - Ref: AdvertisedIPAddress 219 | - "_ipv4:7001\n" 220 | - " units:\n" 221 | - " - name: etcd.service\n" 222 | - " command: start\n" 223 | - " - name: fleet.service\n" 224 | - " command: start\n" 225 | 226 | 227 | # Outputs 228 | # ================================================= 229 | 230 | Outputs: 231 | AccessKeyId: 232 | Description: AWS AccessKeyId for the coreos IAM user 233 | Value: 234 | Ref: CoreOSAccessKey 235 | 236 | SecretAccessKey: 237 | Description: AWS SecretAccessKey for the coreos IAM user 238 | Value: 239 | "Fn::GetAtt": 240 | - CoreOSAccessKey 241 | - SecretAccessKey 242 | --------------------------------------------------------------------------------