diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0868f5f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.terraform/ +*.tfstate +*.tfstate.backup +*.plan +*.tfvars diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..867a45d --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +LAST_TAG := $(shell git describe --abbrev=0 --tags) +COMMITS := $(shell git rev-list -1 $(LAST_TAG)..HEAD) + +patch: NEXT_VERSION = $(shell echo $(LAST_TAG) | awk -F'[v.]' '{$$4++; print $$2"."$$3"."$$4}') +minor: NEXT_VERSION = $(shell echo $(LAST_TAG) | awk -F'[v.]' '{$$3++; print $$2"."$$3".0"}') +major: NEXT_VERSION = $(shell echo $(LAST_TAG) | awk -F'[v.]' '{$$2++; print $$2".0.0"}') + +patch minor major: graph + @if [ -z "${COMMITS}" ]; then echo "No new commits found after ${LAST_TAG}, aborting."; fi + @if [ -n "${COMMITS}" ]; then git tag -s "v${NEXT_VERSION}" -m "Version ${NEXT_VERSION}"; fi + +release: check + @git push origin HEAD:master + @git push --tags origin HEAD:master + @hub release create "v${NEXT_VERSION}" + +check: + @if ! which hub terraform dot > /dev/null; then echo "Missing dependency. Required: hub, terraform, dot." && exit 1; fi; + +.PHONY: patch minor major release check diff --git a/README.md b/README.md new file mode 100644 index 0000000..7bd7700 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# DCOS Terraform Module + +Using this [Terraform][] [module][], you can launch your own [DCOS][] cluster. + +## Configurables + +See [`variables.tf`](variables.tf) for a list of configurable parameters. + +[Terraform]: https://www.terraform.io +[module]: https://www.terraform.io/docs/modules/index.html +[DCOS]: https://mesosphere.com/learn/ + +## Module Instructions + +To include this module in your Terraform code-base, use the following snippet: + +```terraform +module "dcos" { + source = "github.com/jeanmertz/terraform-dcos" + + aws_access_key = "..." + aws_secret_key = "..." + aws_region = "eu-central-1" + ssh_public_key = "ssh-rsa ..." + + ... +} +``` + +Then run `terraform get` to retrieve this module. + +## Stand-Alone Instructions + +Any Terraform module can also be used on its own. To do so, follow these +instructions: + +* clone the repository +* create a `terraform.tfvars` file with all the (required) variables +* *optionslly* run `terraform plan -out terraform.plan` +* run `terraform apply [terraform.plan]` + +## Origin + +This module is an implementation of the official "Single Master" +[AWS Cloud Formation template][]. + +The [CF JSON file](origin.json) is included in this repository, to more easily +track updates and implement those in the Terraform implementation. + +[AWS Cloud Formation template]: https://s3.amazonaws.com/downloads.mesosphere.io/dcos/stable/single-master.cloudformation.json diff --git a/admin_security_group.tf b/admin_security_group.tf new file mode 100644 index 0000000..2b7d555 --- /dev/null +++ b/admin_security_group.tf @@ -0,0 +1,26 @@ +resource "aws_security_group" "admin" { + name = "admin" + description = "Enable admin access to servers" + + vpc_id = "${aws_vpc.dcos.id}" +} + +resource "aws_security_group_rule" "admin_ingress_all" { + security_group_id = "${aws_security_group.admin.id}" + + type = "ingress" + from_port = 0 + to_port = 65535 + protocol = "-1" + cidr_blocks = ["${var.admin_location}"] +} + +resource "aws_security_group_rule" "admin_egress_all" { + security_group_id = "${aws_security_group.admin.id}" + + type = "egress" + from_port = 0 + to_port = 65535 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] +} diff --git a/dhcp_options.tf b/dhcp_options.tf new file mode 100644 index 0000000..c80477f --- /dev/null +++ b/dhcp_options.tf @@ -0,0 +1,9 @@ +resource "aws_vpc_dhcp_options" "dcos" { + domain_name = "${var.aws_region}.compute.internal" + domain_name_servers = ["AmazonProvidedDNS"] +} + +resource "aws_vpc_dhcp_options_association" "dcos" { + vpc_id = "${aws_vpc.dcos.id}" + dhcp_options_id = "${aws_vpc_dhcp_options.dcos.id}" +} diff --git a/host_keys.tf b/host_keys.tf new file mode 100644 index 0000000..2f1a962 --- /dev/null +++ b/host_keys.tf @@ -0,0 +1,52 @@ +resource "aws_iam_access_key" "host_keys" { + user = "${aws_iam_user.dcos.name}" +} + +resource "aws_iam_user" "dcos" { + name = "dcos" +} + +resource "aws_iam_user_policy" "dcos" { + name = "dcos" + user = "${aws_iam_user.dcos.name}" + policy = <> /etc/mesosphere/setup-packages/dcos-config--setup/etc/cloudenv" + ExecStart=/usr/bin/bash -c "echo MARATHON_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/cloudenv" + ExecStart=/usr/bin/bash -c "echo MESOS_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-master" + ExecStart=/usr/bin/bash -c "echo MESOS_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-slave" + - name: link-env.service + command: start + content: | + [Unit] + Before=dcos.target + [Service] + Type=oneshot + ExecStartPre=/usr/bin/mkdir -p /etc/profile.d + ExecStart=/usr/bin/ln -sf /opt/mesosphere/environment.export /etc/profile.d/dcos.sh + - name: dcos-download.service + content: | + [Unit] + Description=Download the DCOS + After=network-online.target + Wants=network-online.target + ConditionPathExists=!/opt/mesosphere/ + [Service] + Type=oneshot + ExecStartPre=/usr/bin/bash -c 'until wget --progress=dot -e dotbytes=10M --continue ${bootstrap_repo_root}/bootstrap.tar.xz -O /tmp/bootstrap.tar.xz; do echo "failed to download"; sleep 5; done' + ExecStartPre=/usr/bin/mkdir -p /opt/mesosphere + ExecStart=/usr/bin/tar -axf /tmp/bootstrap.tar.xz -C /opt/mesosphere + - name: dcos-setup.service + command: start + enable: true + content: | + [Unit] + Description=Prep the Pkgpanda working directories for this host. + Requires=dcos-download.service + After=dcos-download.service + [Service] + Type=oneshot + EnvironmentFile=/opt/mesosphere/environment + ExecStart=/opt/mesosphere/bin/pkgpanda setup --no-block-systemd + [Install] + WantedBy=multi-user.target + - name: marathon.service + command: start diff --git a/nat_instance.tf b/nat_instance.tf new file mode 100644 index 0000000..f30f7f6 --- /dev/null +++ b/nat_instance.tf @@ -0,0 +1,14 @@ +resource "aws_instance" "nat" { + ami = "${var.nat_ami}" + instance_type = "m3.medium" + subnet_id = "${aws_subnet.public.id}" + source_dest_check = false + associate_public_ip_address = true + key_name = "${aws_key_pair.dcos.key_name}" + + vpc_security_group_ids = [ + "${aws_security_group.slave.id}", + "${aws_security_group.master.id}", + "${aws_security_group.admin.id}" + ] +} diff --git a/origin.json b/origin.json new file mode 100644 index 0000000..798f1ff --- /dev/null +++ b/origin.json @@ -0,0 +1,2501 @@ +{ + "Metadata": { + "DcosImageCommit": "fb58e5c0a02fe44e8df2baf92de72bea3030f34b", + "TemplateGenerationDate": "2015-06-05 23:02:57.870011" + }, + "Description": "Launching the Mesosphere DCOS cluster", + "Parameters": { + "AcceptEULA": { + "Type": "String", + "Description": "Please read and agree to our EULA: https://docs.mesosphere.com/community-edition-eula/", + "AllowedValues": [ + "Yes" + ] + }, + "KeyName": { + "Type": "AWS::EC2::KeyPair::KeyName", + "Description": "Name of SSH key to link" + }, + "PublicSlaveInstanceCount": { + "Type": "Number", + "Description": "Number of public slave nodes to launch", + "Default": "1" + }, + "SlaveInstanceCount": { + "Type": "Number", + "Description": "Number of slave nodes to launch", + "Default": "5" + }, + "AdminLocation": { + "MinLength": "9", + "AllowedPattern": "^([0-9]+\\.){3}[0-9]+\\/[0-9]+$", + "ConstraintDescription": "must be a valid CIDR.", + "Type": "String", + "Description": "The IP range to whitelist for admin access.", + "Default": "0.0.0.0/0", + "MaxLength": "18" + } + }, + "Mappings": { + "NATAmi": { + "eu-central-1": { + "default": "ami-204c7a3d" + }, + "eu-west-1": { + "default": "ami-3760b040" + }, + "ap-southeast-1": { + "default": "ami-b082dae2" + }, + "ap-southeast-2": { + "default": "ami-996402a3" + }, + "us-east-1": { + "default": "ami-4c9e4b24" + }, + "sa-east-1": { + "default": "ami-b972dba4" + }, + "ap-northeast-1": { + "default": "ami-55c29e54" + }, + "us-west-2": { + "default": "ami-bb69128b" + }, + "us-west-1": { + "default": "ami-2b2b296e" + } + }, + "RegionToAmi": { + "eu-central-1": { + "stable": "ami-92003c8f" + }, + "eu-west-1": { + "stable": "ami-21422356" + }, + "ap-southeast-1": { + "stable": "ami-a0cefcf2" + }, + "ap-southeast-2": { + "stable": "ami-cb3845f1" + }, + "us-east-1": { + "stable": "ami-d2033bba" + }, + "sa-east-1": { + "stable": "ami-99e66384" + }, + "ap-northeast-1": { + "stable": "ami-9cb9439c" + }, + "us-west-2": { + "stable": "ami-37280207" + }, + "us-west-1": { + "stable": "ami-43f91b07" + } + }, + "Parameters": { + "PublicSlaveInstanceType": { + "default": "m3.xlarge" + }, + "MasterInstanceType": { + "default": "m3.xlarge" + }, + "VPCSubnetRange": { + "default": "10.0.0.0/16" + }, + "SlaveInstanceType": { + "default": "m3.xlarge" + }, + "BootstrapRepoRoot": { + "default": "https://downloads.mesosphere.io/dcos/stable" + }, + "StackCreationTimeout": { + "default": "PT30M" + }, + "MasterQuorumCount": { + "default": "1" + }, + "PrivateSubnetRange": { + "default": "10.0.0.0/22" + }, + "FallbackDNS": { + "default": "10.0.0.2" + }, + "MasterInstanceCount": { + "default": "1" + }, + "PublicSubnetRange": { + "default": "10.0.4.0/22" + } + } + }, + "Resources": { + "InternetGateway": { + "Type": "AWS::EC2::InternetGateway", + "Properties": { + "Tags": [ + { + "Key": "Application", + "Value": { + "Ref": "AWS::StackName" + } + }, + { + "Key": "Network", + "Value": "Public" + } + ] + } + }, + "PrivateSubnetNetworkAclAssociation": { + "Type": "AWS::EC2::SubnetNetworkAclAssociation", + "Properties": { + "SubnetId": { + "Ref": "PrivateSubnet" + }, + "NetworkAclId": { + "Ref": "PrivateNetworkAcl" + } + } + }, + "PublicSlaveIngressTwo": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "GroupId": { + "Ref": "PublicSlaveSecurityGroup" + }, + "IpProtocol": "tcp", + "ToPort": "5050", + "CidrIp": "0.0.0.0/0", + "FromPort": "23" + } + }, + "MasterToMasterIngress": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "SourceSecurityGroupId": { + "Ref": "MasterSecurityGroup" + }, + "GroupId": { + "Ref": "MasterSecurityGroup" + }, + "IpProtocol": "-1", + "ToPort": "65535", + "FromPort": "0" + } + }, + "SlaveLaunchConfig": { + "Type": "AWS::AutoScaling::LaunchConfiguration", + "Properties": { + "SecurityGroups": [ + { + "Ref": "SlaveSecurityGroup" + } + ], + "ImageId": { + "Fn::FindInMap": [ + "RegionToAmi", + { + "Ref": "AWS::Region" + }, + "stable" + ] + }, + "InstanceType": { + "Fn::FindInMap": [ + "Parameters", + "SlaveInstanceType", + "default" + ] + }, + "KeyName": { + "Ref": "KeyName" + }, + "UserData": { + "Fn::Base64": { + "Fn::Join": [ + "", + [ + "#cloud-config\n", + "write_files:\n", + " - path: /etc/mesosphere/setup-flags/repository-url\n", + " permissions: 0644\n", + " owner: root\n", + " content: |\n", + " ", + { + "Fn::FindInMap": [ + "Parameters", + "BootstrapRepoRoot", + "default" + ] + }, + "", + "\n", + " \n", + " - path: /etc/mesosphere/roles/slave\n", + " \n", + " - path: /etc/mesosphere/setup-packages/dcos-config--setup/pkginfo.json\n", + " content: '{}'\n", + " - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-dns.json\n", + " content: |\n", + " {\n", + " \"zk\": \"zk://127.0.0.1:2181/mesos\",\n", + " \"refreshSeconds\": 30,\n", + " \"ttl\": 60,\n", + " \"domain\": \"mesos\",\n", + " \"port\": 53,\n", + " \"resolvers\": [\"", + { + "Fn::FindInMap": [ + "Parameters", + "FallbackDNS", + "default" + ] + }, + "\"],", + "\n", + " \"timeout\": 5,\n", + " \"listener\": \"0.0.0.0\",\n", + " \"email\": \"root.mesos-dns.mesos\"\n", + " }\n", + " - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-master\n", + " content: |\n", + " MESOS_LOG_DIR=/var/log/mesos\n", + " MESOS_WORK_DIR=/var/lib/mesos/master\n", + " MESOS_ZK=zk://127.0.0.1:2181/mesos\n", + " MESOS_QUORUM=", + { + "Fn::FindInMap": [ + "Parameters", + "MasterQuorumCount", + "default" + ] + }, + "", + "\n", + " MESOS_CLUSTER=", + { + "Ref": "AWS::StackName" + }, + "", + "\n", + " MESOS_ROLES=slave_public\n", + " - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-slave\n", + " content: |\n", + " MESOS_MASTER=zk://leader.mesos:2181/mesos\n", + " MESOS_CONTAINERIZERS=docker,mesos\n", + " MESOS_LOG_DIR=/var/log/mesos\n", + " MESOS_EXECUTOR_REGISTRATION_TIMEOUT=5mins\n", + " MESOS_ISOLATION=cgroups/cpu,cgroups/mem\n", + " MESOS_WORK_DIR=/var/lib/mesos/slave\n", + " MESOS_RESOURCES=ports:[1025-2180,2182-3887,3889-5049,5052-8079,8082-8180,8182-65535]\n", + " MESOS_SLAVE_SUBSYSTEMS=cpu,memory\n", + " - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-slave-public\n", + " content: |\n", + " MESOS_MASTER=zk://leader.mesos:2181/mesos\n", + " MESOS_CONTAINERIZERS=docker,mesos\n", + " MESOS_LOG_DIR=/var/log/mesos\n", + " MESOS_EXECUTOR_REGISTRATION_TIMEOUT=5mins\n", + " MESOS_ISOLATION=cgroups/cpu,cgroups/mem\n", + " MESOS_WORK_DIR=/var/lib/mesos/slave\n", + " MESOS_RESOURCES=ports:[1-21,23-5050,5052-65535]\n", + " MESOS_SLAVE_SUBSYSTEMS=cpu,memory\n", + " MESOS_DEFAULT_ROLE=slave_public\n", + " MESOS_ATTRIBUTES=public_ip:true\n", + "\n", + " - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/cloudenv\n", + " content: |\n", + " AWS_REGION=", + { + "Ref": "AWS::Region" + }, + "", + "\n", + " AWS_STACK_ID=", + { + "Ref": "AWS::StackId" + }, + "", + "\n", + " AWS_STACK_NAME=", + { + "Ref": "AWS::StackName" + }, + "", + "\n", + " AWS_ACCESS_KEY_ID=", + { + "Ref": "HostKeys" + }, + "", + "\n", + " AWS_SECRET_ACCESS_KEY=", + { + "Fn::GetAtt": [ + "HostKeys", + "SecretAccessKey" + ] + }, + "", + "\n", + " ZOOKEEPER_CLUSTER_SIZE=", + { + "Fn::FindInMap": [ + "Parameters", + "MasterInstanceCount", + "default" + ] + }, + "", + "\n", + " MASTER_ELB=", + { + "Fn::GetAtt": [ + "InternalMasterLoadBalancer", + "DNSName" + ] + }, + "", + "\n", + " EXTERNAL_ELB=", + { + "Fn::GetAtt": [ + "ElasticLoadBalancer", + "DNSName" + ] + }, + "", + "\n", + " # Must set FALLBACK_DNS to an AWS region-specific DNS server which returns\n", + " # the internal IP when doing lookups on AWS public hostnames.\n", + " FALLBACK_DNS=", + { + "Fn::FindInMap": [ + "Parameters", + "FallbackDNS", + "default" + ] + }, + "", + "\n", + " - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/exhibitor\n", + " content: |\n", + " AWS_S3_BUCKET=", + { + "Ref": "ExhibitorS3Bucket" + }, + "", + "\n", + " AWS_S3_PREFIX=", + { + "Ref": "AWS::StackName" + }, + "", + "\n", + " EXHIBITOR_WEB_UI_PORT=8181\n", + "\n", + "coreos:\n", + " update:\n", + " reboot-strategy: off\n", + " units:\n", + " - name: format-var-lib-ephemeral.service\n", + " command: start\n", + " content: |\n", + " [Unit]\n", + " Description=Formats the /var/lib ephemeral drive\n", + " Before=var-lib.mount dbus.service\n", + " [Service]\n", + " Type=oneshot\n", + " RemainAfterExit=yes\n", + " ExecStart=/bin/bash -c '(blkid -t TYPE=ext4 | grep xvdb) || (/usr/sbin/mkfs.ext4 -F /dev/xvdb)'\n", + " - name: var-lib.mount\n", + " command: start\n", + " content: |\n", + " [Unit]\n", + " Description=Mount /var/lib\n", + " Before=dbus.service\n", + " [Mount]\n", + " What=/dev/xvdb\n", + " Where=/var/lib\n", + " Type=ext4\n", + "\n", + " - name: etcd.service\n", + " mask: true\n", + " command: stop\n", + " - name: update-engine.service\n", + " mask: true\n", + " command: stop\n", + " - name: locksmithd.service\n", + " mask: true\n", + " command: stop\n", + " - name: systemd-resolved.service\n", + " command: stop\n", + " - name: config-writer.service\n", + " command: start\n", + " content: |\n", + " [Unit]\n", + " Description=Write out dynamic config values\n", + " [Service]\n", + " Type=oneshot\n", + " ExecStart=/usr/bin/bash -c \"echo EXHIBITOR_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/cloudenv\"\n", + " ExecStart=/usr/bin/bash -c \"echo MARATHON_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/cloudenv\"\n", + " ExecStart=/usr/bin/bash -c \"echo MESOS_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-master\"\n", + " ExecStart=/usr/bin/bash -c \"echo MESOS_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-slave\"\n", + "\n", + " - name: link-env.service\n", + " command: start\n", + " content: |\n", + " [Unit]\n", + " Before=dcos.target\n", + " [Service]\n", + " Type=oneshot\n", + " ExecStartPre=/usr/bin/mkdir -p /etc/profile.d\n", + " ExecStart=/usr/bin/ln -sf /opt/mesosphere/environment.export /etc/profile.d/dcos.sh\n", + " - name: dcos-download.service\n", + " content: |\n", + " [Unit]\n", + " Description=Download the DCOS\n", + " After=network-online.target\n", + " Wants=network-online.target\n", + " ConditionPathExists=!/opt/mesosphere/\n", + " [Service]\n", + " Type=oneshot\n", + " ExecStartPre=/usr/bin/bash -c 'until wget --progress=dot -e dotbytes=10M --continue ", + { + "Fn::FindInMap": [ + "Parameters", + "BootstrapRepoRoot", + "default" + ] + }, + "/bootstrap.tar.xz -O /tmp/bootstrap.tar.xz; do echo \"failed to download\"; sleep 5; done'", + "\n", + " ExecStartPre=/usr/bin/mkdir -p /opt/mesosphere\n", + " ExecStart=/usr/bin/tar -axf /tmp/bootstrap.tar.xz -C /opt/mesosphere\n", + " - name: dcos-setup.service\n", + " command: start\n", + " enable: true\n", + " content: |\n", + " [Unit]\n", + " Description=Prep the Pkgpanda working directories for this host.\n", + " Requires=dcos-download.service\n", + " After=dcos-download.service\n", + " [Service]\n", + " Type=oneshot\n", + " EnvironmentFile=/opt/mesosphere/environment\n", + " ExecStart=/opt/mesosphere/bin/pkgpanda setup --no-block-systemd\n", + " [Install]\n", + " WantedBy=multi-user.target\n", + " - name: cfn-signal.service\n", + " command: start\n", + " content: |\n", + " [Unit]\n", + " Description=Signal CloudFormation Success\n", + " After=dcos.target\n", + " Requires=dcos.target\n", + " ConditionPathExists=!/var/lib/cfn-signal\n", + " [Service]\n", + " Type=simple\n", + " Restart=on-failure\n", + " StartLimitInterval=0\n", + " RestartSec=15s\n", + " ExecStartPre=/usr/bin/docker pull mbabineau/cfn-bootstrap\n", + " ExecStartPre=/bin/ping -c1 leader.mesos\n", + " ExecStartPre=/usr/bin/docker run --rm mbabineau/cfn-bootstrap \\\n", + " cfn-signal -e 0 \\\n", + " --resource SlaveServerGroup \\\n", + " --stack ", + { + "Ref": "AWS::StackName" + }, + " \\", + "\n", + " --region ", + { + "Ref": "AWS::Region" + }, + "", + "\n", + " ExecStart=/usr/bin/touch /var/lib/cfn-signal\n" + ] + ] + } + }, + "AssociatePublicIpAddress": "false", + "BlockDeviceMappings": [ + { + "VirtualName": "ephemeral0", + "DeviceName": "/dev/sdb" + } + ] + } + }, + "PublicSlaveToSlaveIngress": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "SourceSecurityGroupId": { + "Ref": "PublicSlaveSecurityGroup" + }, + "GroupId": { + "Ref": "SlaveSecurityGroup" + }, + "IpProtocol": "-1", + "ToPort": "65535", + "FromPort": "0" + } + }, + "PublicRouteTable": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc" + }, + "Tags": [ + { + "Key": "Application", + "Value": { + "Ref": "AWS::StackName" + } + }, + { + "Key": "Network", + "Value": "Public" + } + ] + } + }, + "ElasticLoadBalancer": { + "Type": "AWS::ElasticLoadBalancing::LoadBalancer", + "Properties": { + "SecurityGroups": [ + { + "Ref": "LbSecurityGroup" + }, + { + "Ref": "AdminSecurityGroup" + } + ], + "Listeners": [ + { + "Protocol": "HTTP", + "InstanceProtocol": "HTTP", + "LoadBalancerPort": "5050", + "InstancePort": "5050" + }, + { + "Protocol": "TCP", + "InstanceProtocol": "TCP", + "LoadBalancerPort": "2181", + "InstancePort": "2181" + }, + { + "Protocol": "HTTP", + "InstanceProtocol": "HTTP", + "LoadBalancerPort": "8181", + "InstancePort": "8181" + }, + { + "Protocol": "HTTP", + "InstanceProtocol": "HTTP", + "LoadBalancerPort": "80", + "InstancePort": "80" + }, + { + "Protocol": "TCP", + "InstanceProtocol": "TCP", + "LoadBalancerPort": "443", + "InstancePort": "443" + }, + { + "Protocol": "HTTP", + "InstanceProtocol": "HTTP", + "LoadBalancerPort": "8080", + "InstancePort": "8080" + } + ], + "Subnets": [ + { + "Ref": "PublicSubnet" + } + ], + "HealthCheck": { + "Interval": "30", + "UnhealthyThreshold": "2", + "Timeout": "5", + "HealthyThreshold": "2", + "Target": "HTTP:5050/health" + } + } + }, + "HostKeys": { + "Type": "AWS::IAM::AccessKey", + "Properties": { + "UserName": { + "Ref": "IAMUser" + } + } + }, + "PrivateSubnetRouteTableAssociation": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "SubnetId": { + "Ref": "PrivateSubnet" + }, + "RouteTableId": { + "Ref": "PrivateRouteTable" + } + } + }, + "PublicSlaveSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Mesos Slaves Public", + "VpcId": { + "Ref": "Vpc" + } + } + }, + "MasterToSlaveIngress": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "SourceSecurityGroupId": { + "Ref": "MasterSecurityGroup" + }, + "GroupId": { + "Ref": "SlaveSecurityGroup" + }, + "IpProtocol": "-1", + "ToPort": "65535", + "FromPort": "0" + } + }, + "PublicSlaveLaunchConfig": { + "Type": "AWS::AutoScaling::LaunchConfiguration", + "Properties": { + "SecurityGroups": [ + { + "Ref": "PublicSlaveSecurityGroup" + } + ], + "ImageId": { + "Fn::FindInMap": [ + "RegionToAmi", + { + "Ref": "AWS::Region" + }, + "stable" + ] + }, + "InstanceType": { + "Fn::FindInMap": [ + "Parameters", + "PublicSlaveInstanceType", + "default" + ] + }, + "KeyName": { + "Ref": "KeyName" + }, + "UserData": { + "Fn::Base64": { + "Fn::Join": [ + "", + [ + "#cloud-config\n", + "write_files:\n", + " - path: /etc/mesosphere/setup-flags/repository-url\n", + " permissions: 0644\n", + " owner: root\n", + " content: |\n", + " ", + { + "Fn::FindInMap": [ + "Parameters", + "BootstrapRepoRoot", + "default" + ] + }, + "", + "\n", + " \n", + " - path: /etc/mesosphere/roles/slave_public\n", + " \n", + " - path: /etc/mesosphere/setup-packages/dcos-config--setup/pkginfo.json\n", + " content: '{}'\n", + " - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-dns.json\n", + " content: |\n", + " {\n", + " \"zk\": \"zk://127.0.0.1:2181/mesos\",\n", + " \"refreshSeconds\": 30,\n", + " \"ttl\": 60,\n", + " \"domain\": \"mesos\",\n", + " \"port\": 53,\n", + " \"resolvers\": [\"", + { + "Fn::FindInMap": [ + "Parameters", + "FallbackDNS", + "default" + ] + }, + "\"],", + "\n", + " \"timeout\": 5,\n", + " \"listener\": \"0.0.0.0\",\n", + " \"email\": \"root.mesos-dns.mesos\"\n", + " }\n", + " - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-master\n", + " content: |\n", + " MESOS_LOG_DIR=/var/log/mesos\n", + " MESOS_WORK_DIR=/var/lib/mesos/master\n", + " MESOS_ZK=zk://127.0.0.1:2181/mesos\n", + " MESOS_QUORUM=", + { + "Fn::FindInMap": [ + "Parameters", + "MasterQuorumCount", + "default" + ] + }, + "", + "\n", + " MESOS_CLUSTER=", + { + "Ref": "AWS::StackName" + }, + "", + "\n", + " MESOS_ROLES=slave_public\n", + " - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-slave\n", + " content: |\n", + " MESOS_MASTER=zk://leader.mesos:2181/mesos\n", + " MESOS_CONTAINERIZERS=docker,mesos\n", + " MESOS_LOG_DIR=/var/log/mesos\n", + " MESOS_EXECUTOR_REGISTRATION_TIMEOUT=5mins\n", + " MESOS_ISOLATION=cgroups/cpu,cgroups/mem\n", + " MESOS_WORK_DIR=/var/lib/mesos/slave\n", + " MESOS_RESOURCES=ports:[1025-2180,2182-3887,3889-5049,5052-8079,8082-8180,8182-65535]\n", + " MESOS_SLAVE_SUBSYSTEMS=cpu,memory\n", + " - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-slave-public\n", + " content: |\n", + " MESOS_MASTER=zk://leader.mesos:2181/mesos\n", + " MESOS_CONTAINERIZERS=docker,mesos\n", + " MESOS_LOG_DIR=/var/log/mesos\n", + " MESOS_EXECUTOR_REGISTRATION_TIMEOUT=5mins\n", + " MESOS_ISOLATION=cgroups/cpu,cgroups/mem\n", + " MESOS_WORK_DIR=/var/lib/mesos/slave\n", + " MESOS_RESOURCES=ports:[1-21,23-5050,5052-65535]\n", + " MESOS_SLAVE_SUBSYSTEMS=cpu,memory\n", + " MESOS_DEFAULT_ROLE=slave_public\n", + " MESOS_ATTRIBUTES=public_ip:true\n", + "\n", + " - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/cloudenv\n", + " content: |\n", + " AWS_REGION=", + { + "Ref": "AWS::Region" + }, + "", + "\n", + " AWS_STACK_ID=", + { + "Ref": "AWS::StackId" + }, + "", + "\n", + " AWS_STACK_NAME=", + { + "Ref": "AWS::StackName" + }, + "", + "\n", + " AWS_ACCESS_KEY_ID=", + { + "Ref": "HostKeys" + }, + "", + "\n", + " AWS_SECRET_ACCESS_KEY=", + { + "Fn::GetAtt": [ + "HostKeys", + "SecretAccessKey" + ] + }, + "", + "\n", + " ZOOKEEPER_CLUSTER_SIZE=", + { + "Fn::FindInMap": [ + "Parameters", + "MasterInstanceCount", + "default" + ] + }, + "", + "\n", + " MASTER_ELB=", + { + "Fn::GetAtt": [ + "InternalMasterLoadBalancer", + "DNSName" + ] + }, + "", + "\n", + " EXTERNAL_ELB=", + { + "Fn::GetAtt": [ + "ElasticLoadBalancer", + "DNSName" + ] + }, + "", + "\n", + " # Must set FALLBACK_DNS to an AWS region-specific DNS server which returns\n", + " # the internal IP when doing lookups on AWS public hostnames.\n", + " FALLBACK_DNS=", + { + "Fn::FindInMap": [ + "Parameters", + "FallbackDNS", + "default" + ] + }, + "", + "\n", + " - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/exhibitor\n", + " content: |\n", + " AWS_S3_BUCKET=", + { + "Ref": "ExhibitorS3Bucket" + }, + "", + "\n", + " AWS_S3_PREFIX=", + { + "Ref": "AWS::StackName" + }, + "", + "\n", + " EXHIBITOR_WEB_UI_PORT=8181\n", + "\n", + "coreos:\n", + " update:\n", + " reboot-strategy: off\n", + " units:\n", + " - name: format-var-lib-ephemeral.service\n", + " command: start\n", + " content: |\n", + " [Unit]\n", + " Description=Formats the /var/lib ephemeral drive\n", + " Before=var-lib.mount dbus.service\n", + " [Service]\n", + " Type=oneshot\n", + " RemainAfterExit=yes\n", + " ExecStart=/bin/bash -c '(blkid -t TYPE=ext4 | grep xvdb) || (/usr/sbin/mkfs.ext4 -F /dev/xvdb)'\n", + " - name: var-lib.mount\n", + " command: start\n", + " content: |\n", + " [Unit]\n", + " Description=Mount /var/lib\n", + " Before=dbus.service\n", + " [Mount]\n", + " What=/dev/xvdb\n", + " Where=/var/lib\n", + " Type=ext4\n", + "\n", + " - name: etcd.service\n", + " mask: true\n", + " command: stop\n", + " - name: update-engine.service\n", + " mask: true\n", + " command: stop\n", + " - name: locksmithd.service\n", + " mask: true\n", + " command: stop\n", + " - name: systemd-resolved.service\n", + " command: stop\n", + " - name: config-writer.service\n", + " command: start\n", + " content: |\n", + " [Unit]\n", + " Description=Write out dynamic config values\n", + " [Service]\n", + " Type=oneshot\n", + " ExecStart=/usr/bin/bash -c \"echo EXHIBITOR_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/cloudenv\"\n", + " ExecStart=/usr/bin/bash -c \"echo MARATHON_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/cloudenv\"\n", + " ExecStart=/usr/bin/bash -c \"echo MESOS_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-master\"\n", + " ExecStart=/usr/bin/bash -c \"echo MESOS_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-slave\"\n", + "\n", + " - name: link-env.service\n", + " command: start\n", + " content: |\n", + " [Unit]\n", + " Before=dcos.target\n", + " [Service]\n", + " Type=oneshot\n", + " ExecStartPre=/usr/bin/mkdir -p /etc/profile.d\n", + " ExecStart=/usr/bin/ln -sf /opt/mesosphere/environment.export /etc/profile.d/dcos.sh\n", + " - name: dcos-download.service\n", + " content: |\n", + " [Unit]\n", + " Description=Download the DCOS\n", + " After=network-online.target\n", + " Wants=network-online.target\n", + " ConditionPathExists=!/opt/mesosphere/\n", + " [Service]\n", + " Type=oneshot\n", + " ExecStartPre=/usr/bin/bash -c 'until wget --progress=dot -e dotbytes=10M --continue ", + { + "Fn::FindInMap": [ + "Parameters", + "BootstrapRepoRoot", + "default" + ] + }, + "/bootstrap.tar.xz -O /tmp/bootstrap.tar.xz; do echo \"failed to download\"; sleep 5; done'", + "\n", + " ExecStartPre=/usr/bin/mkdir -p /opt/mesosphere\n", + " ExecStart=/usr/bin/tar -axf /tmp/bootstrap.tar.xz -C /opt/mesosphere\n", + " - name: dcos-setup.service\n", + " command: start\n", + " enable: true\n", + " content: |\n", + " [Unit]\n", + " Description=Prep the Pkgpanda working directories for this host.\n", + " Requires=dcos-download.service\n", + " After=dcos-download.service\n", + " [Service]\n", + " Type=oneshot\n", + " EnvironmentFile=/opt/mesosphere/environment\n", + " ExecStart=/opt/mesosphere/bin/pkgpanda setup --no-block-systemd\n", + " [Install]\n", + " WantedBy=multi-user.target\n", + " - name: cfn-signal.service\n", + " command: start\n", + " content: |\n", + " [Unit]\n", + " Description=Signal CloudFormation Success\n", + " After=dcos.target\n", + " Requires=dcos.target\n", + " ConditionPathExists=!/var/lib/cfn-signal\n", + " [Service]\n", + " Type=simple\n", + " Restart=on-failure\n", + " StartLimitInterval=0\n", + " RestartSec=15s\n", + " ExecStartPre=/usr/bin/docker pull mbabineau/cfn-bootstrap\n", + " ExecStartPre=/bin/ping -c1 leader.mesos\n", + " ExecStartPre=/usr/bin/docker run --rm mbabineau/cfn-bootstrap \\\n", + " cfn-signal -e 0 \\\n", + " --resource PublicSlaveServerGroup \\\n", + " --stack ", + { + "Ref": "AWS::StackName" + }, + " \\", + "\n", + " --region ", + { + "Ref": "AWS::Region" + }, + "", + "\n", + " ExecStart=/usr/bin/touch /var/lib/cfn-signal\n" + ] + ] + } + }, + "AssociatePublicIpAddress": "true", + "BlockDeviceMappings": [ + { + "VirtualName": "ephemeral0", + "DeviceName": "/dev/sdb" + } + ] + } + }, + "InternalMasterLoadBalancer": { + "Type": "AWS::ElasticLoadBalancing::LoadBalancer", + "Properties": { + "Scheme": "internal", + "SecurityGroups": [ + { + "Ref": "LbSecurityGroup" + }, + { + "Ref": "AdminSecurityGroup" + }, + { + "Ref": "SlaveSecurityGroup" + }, + { + "Ref": "PublicSlaveSecurityGroup" + }, + { + "Ref": "MasterSecurityGroup" + } + ], + "Listeners": [ + { + "Protocol": "HTTP", + "InstanceProtocol": "HTTP", + "LoadBalancerPort": "5050", + "InstancePort": "5050" + }, + { + "Protocol": "TCP", + "InstanceProtocol": "TCP", + "LoadBalancerPort": "2181", + "InstancePort": "2181" + }, + { + "Protocol": "HTTP", + "InstanceProtocol": "HTTP", + "LoadBalancerPort": "8181", + "InstancePort": "8181" + }, + { + "Protocol": "HTTP", + "InstanceProtocol": "HTTP", + "LoadBalancerPort": "80", + "InstancePort": "80" + }, + { + "Protocol": "TCP", + "InstanceProtocol": "TCP", + "LoadBalancerPort": "443", + "InstancePort": "443" + }, + { + "Protocol": "HTTP", + "InstanceProtocol": "HTTP", + "LoadBalancerPort": "8080", + "InstancePort": "8080" + } + ], + "Subnets": [ + { + "Ref": "PublicSubnet" + } + ], + "HealthCheck": { + "Interval": "30", + "UnhealthyThreshold": "2", + "Timeout": "5", + "HealthyThreshold": "2", + "Target": "HTTP:5050/health" + } + } + }, + "PrivateInboundNetworkAclEntry": { + "Type": "AWS::EC2::NetworkAclEntry", + "Properties": { + "Protocol": "-1", + "PortRange": { + "From": "0", + "To": "65535" + }, + "Egress": "false", + "CidrBlock": "0.0.0.0/0", + "RuleNumber": "100", + "RuleAction": "allow", + "NetworkAclId": { + "Ref": "PrivateNetworkAcl" + } + } + }, + "PublicSlaveToMasterIngress": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "SourceSecurityGroupId": { + "Ref": "PublicSlaveSecurityGroup" + }, + "GroupId": { + "Ref": "MasterSecurityGroup" + }, + "IpProtocol": "-1", + "ToPort": "65535", + "FromPort": "0" + } + }, + "PublicRoute": { + "Type": "AWS::EC2::Route", + "DependsOn": "GatewayToInternet", + "Properties": { + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "InternetGateway" + }, + "RouteTableId": { + "Ref": "PublicRouteTable" + } + } + }, + "PublicSubnet": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": { + "Fn::FindInMap": [ + "Parameters", + "PublicSubnetRange", + "default" + ] + }, + "VpcId": { + "Ref": "Vpc" + }, + "Tags": [ + { + "Key": "Application", + "Value": { + "Ref": "AWS::StackName" + } + }, + { + "Key": "Network", + "Value": "Public" + } + ] + } + }, + "PublicSlaveServerGroup": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "CreationPolicy": { + "ResourceSignal": { + "Timeout": { + "Fn::FindInMap": [ + "Parameters", + "StackCreationTimeout", + "default" + ] + }, + "Count": { + "Ref": "PublicSlaveInstanceCount" + } + } + }, + "Properties": { + "LoadBalancerNames": [ + { + "Ref": "PublicSlaveLoadBalancer" + } + ], + "AvailabilityZones": [ + { + "Fn::GetAtt": [ + "PublicSubnet", + "AvailabilityZone" + ] + } + ], + "MaxSize": { + "Ref": "PublicSlaveInstanceCount" + }, + "VPCZoneIdentifier": [ + { + "Ref": "PublicSubnet" + } + ], + "LaunchConfigurationName": { + "Ref": "PublicSlaveLaunchConfig" + }, + "DesiredCapacity": { + "Ref": "PublicSlaveInstanceCount" + }, + "MinSize": { + "Ref": "PublicSlaveInstanceCount" + }, + "Tags": [ + { + "PropagateAtLaunch": "true", + "Key": "role", + "Value": "mesos-slave" + } + ] + } + }, + "MasterToPublicSlaveIngress": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "SourceSecurityGroupId": { + "Ref": "MasterSecurityGroup" + }, + "GroupId": { + "Ref": "PublicSlaveSecurityGroup" + }, + "IpProtocol": "-1", + "ToPort": "65535", + "FromPort": "0" + } + }, + "PrivateNetworkAcl": { + "Type": "AWS::EC2::NetworkAcl", + "Properties": { + "VpcId": { + "Ref": "Vpc" + }, + "Tags": [ + { + "Key": "Application", + "Value": { + "Ref": "AWS::StackName" + } + }, + { + "Key": "Network", + "Value": "Public" + } + ] + } + }, + "PublicSlaveIngressFour": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "GroupId": { + "Ref": "PublicSlaveSecurityGroup" + }, + "IpProtocol": "udp", + "ToPort": "21", + "CidrIp": "0.0.0.0/0", + "FromPort": "0" + } + }, + "GatewayToInternet": { + "Type": "AWS::EC2::VPCGatewayAttachment", + "Properties": { + "InternetGatewayId": { + "Ref": "InternetGateway" + }, + "VpcId": { + "Ref": "Vpc" + } + } + }, + "SlaveServerGroup": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "CreationPolicy": { + "ResourceSignal": { + "Timeout": { + "Fn::FindInMap": [ + "Parameters", + "StackCreationTimeout", + "default" + ] + }, + "Count": { + "Ref": "SlaveInstanceCount" + } + } + }, + "Properties": { + "MaxSize": { + "Ref": "SlaveInstanceCount" + }, + "AvailabilityZones": [ + { + "Fn::GetAtt": [ + "PrivateSubnet", + "AvailabilityZone" + ] + } + ], + "VPCZoneIdentifier": [ + { + "Ref": "PrivateSubnet" + } + ], + "LaunchConfigurationName": { + "Ref": "SlaveLaunchConfig" + }, + "DesiredCapacity": { + "Ref": "SlaveInstanceCount" + }, + "MinSize": { + "Ref": "SlaveInstanceCount" + }, + "Tags": [ + { + "PropagateAtLaunch": "true", + "Key": "role", + "Value": "mesos-slave" + } + ] + } + }, + "DHCPOptions": { + "Type": "AWS::EC2::DHCPOptions", + "Properties": { + "DomainName": { + "Fn::If": [ + "RegionIsUsEast1", + "ec2.internal", + { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::Region" + }, + ".compute.internal" + ] + ] + } + ] + }, + "DomainNameServers": [ + "AmazonProvidedDNS" + ] + } + }, + "IAMUser": { + "Type": "AWS::IAM::User", + "Properties": { + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "ExhibitorS3Bucket" + }, + "/*" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "ExhibitorS3Bucket" + } + ] + ] + } + ], + "Action": [ + "s3:AbortMultipartUpload", + "s3:DeleteObject", + "s3:GetBucketAcl", + "s3:GetBucketPolicy", + "s3:GetObject", + "s3:GetObjectAcl", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:ListMultipartUploadParts", + "s3:PutObject", + "s3:PutObjectAcl" + ], + "Effect": "Allow" + }, + { + "Resource": [ + { + "Ref": "AWS::StackId" + }, + { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::StackId" + }, + "/*" + ] + ] + } + ], + "Action": [ + "cloudformation:*" + ], + "Effect": "Allow" + }, + { + "Resource": "*", + "Action": [ + "ec2:DescribeKeyPairs", + "ec2:DescribeSubnets", + "autoscaling:DescribeLaunchConfigurations", + "autoscaling:UpdateAutoScalingGroup", + "autoscaling:DescribeAutoScalingGroups", + "autoscaling:DescribeScalingActivities", + "elasticloadbalancing:DescribeLoadBalancers" + ], + "Effect": "Allow" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "root" + } + ] + } + }, + "PublicSubnetRouteTableAssociation": { + "Type": "AWS::EC2::SubnetRouteTableAssociation", + "Properties": { + "SubnetId": { + "Ref": "PublicSubnet" + }, + "RouteTableId": { + "Ref": "PublicRouteTable" + } + } + }, + "SlaveSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Mesos Slaves", + "VpcId": { + "Ref": "Vpc" + } + } + }, + "MasterRole": { + "Type": "AWS::IAM::Role", + "Properties": { + "Path": "/", + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Principal": { + "Service": [ + "ec2.amazonaws.com" + ] + }, + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow" + } + ], + "Version": "2012-10-17" + }, + "Policies": [ + { + "PolicyDocument": { + "Statement": [ + { + "Resource": [ + { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "ExhibitorS3Bucket" + }, + "/*" + ] + ] + }, + { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "ExhibitorS3Bucket" + } + ] + ] + } + ], + "Action": [ + "s3:AbortMultipartUpload", + "s3:DeleteObject", + "s3:GetBucketAcl", + "s3:GetBucketPolicy", + "s3:GetObject", + "s3:GetObjectAcl", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:ListMultipartUploadParts", + "s3:PutObject", + "s3:PutObjectAcl" + ], + "Effect": "Allow" + }, + { + "Resource": [ + { + "Ref": "AWS::StackId" + }, + { + "Fn::Join": [ + "", + [ + { + "Ref": "AWS::StackId" + }, + "/*" + ] + ] + } + ], + "Action": [ + "cloudformation:*" + ], + "Effect": "Allow" + }, + { + "Resource": "*", + "Action": [ + "ec2:DescribeKeyPairs", + "ec2:DescribeSubnets", + "autoscaling:DescribeLaunchConfigurations", + "autoscaling:UpdateAutoScalingGroup", + "autoscaling:DescribeAutoScalingGroups", + "autoscaling:DescribeScalingActivities", + "elasticloadbalancing:DescribeLoadBalancers" + ], + "Effect": "Allow" + } + ], + "Version": "2012-10-17" + }, + "PolicyName": "root" + } + ] + } + }, + "PublicSlaveLoadBalancer": { + "Type": "AWS::ElasticLoadBalancing::LoadBalancer", + "Properties": { + "SecurityGroups": [ + { + "Ref": "PublicSlaveSecurityGroup" + } + ], + "Listeners": [ + { + "Protocol": "HTTP", + "InstanceProtocol": "HTTP", + "LoadBalancerPort": "80", + "InstancePort": "80" + }, + { + "Protocol": "TCP", + "InstanceProtocol": "TCP", + "LoadBalancerPort": "443", + "InstancePort": "443" + } + ], + "Subnets": [ + { + "Ref": "PublicSubnet" + } + ], + "HealthCheck": { + "Interval": "30", + "UnhealthyThreshold": "2", + "Timeout": "5", + "HealthyThreshold": "2", + "Target": "HTTP:80/" + } + } + }, + "PrivateOutboundNetworkAclEntry": { + "Type": "AWS::EC2::NetworkAclEntry", + "Properties": { + "Protocol": "-1", + "PortRange": { + "From": "0", + "To": "65535" + }, + "Egress": "true", + "CidrBlock": "0.0.0.0/0", + "RuleNumber": "100", + "RuleAction": "allow", + "NetworkAclId": { + "Ref": "PrivateNetworkAcl" + } + } + }, + "PublicSlaveToPublicSlaveIngress": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "SourceSecurityGroupId": { + "Ref": "PublicSlaveSecurityGroup" + }, + "GroupId": { + "Ref": "PublicSlaveSecurityGroup" + }, + "IpProtocol": "-1", + "ToPort": "65535", + "FromPort": "0" + } + }, + "LbSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Mesos Master LB", + "VpcId": { + "Ref": "Vpc" + } + } + }, + "PrivateSubnet": { + "Type": "AWS::EC2::Subnet", + "Properties": { + "CidrBlock": { + "Fn::FindInMap": [ + "Parameters", + "PrivateSubnetRange", + "default" + ] + }, + "VpcId": { + "Ref": "Vpc" + }, + "Tags": [ + { + "Key": "Application", + "Value": { + "Ref": "AWS::StackName" + } + }, + { + "Key": "Network", + "Value": "Private" + } + ] + } + }, + "PrivateRoute": { + "Type": "AWS::EC2::Route", + "Properties": { + "InstanceId": { + "Ref": "NATInstance" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "RouteTableId": { + "Ref": "PrivateRouteTable" + } + } + }, + "AdminSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Enable admin access to servers", + "VpcId": { + "Ref": "Vpc" + }, + "SecurityGroupIngress": [ + { + "FromPort": "0", + "IpProtocol": "-1", + "ToPort": "65535", + "CidrIp": { + "Ref": "AdminLocation" + } + } + ] + } + }, + "SlaveToPublicSlaveIngress": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "SourceSecurityGroupId": { + "Ref": "SlaveSecurityGroup" + }, + "GroupId": { + "Ref": "PublicSlaveSecurityGroup" + }, + "IpProtocol": "-1", + "ToPort": "65535", + "FromPort": "0" + } + }, + "SlaveToMasterLBIngress": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "SourceSecurityGroupId": { + "Ref": "SlaveSecurityGroup" + }, + "GroupId": { + "Ref": "LbSecurityGroup" + }, + "IpProtocol": "tcp", + "ToPort": "2181", + "FromPort": "2181" + } + }, + "VPCDHCPOptionsAssociation": { + "Type": "AWS::EC2::VPCDHCPOptionsAssociation", + "Properties": { + "DhcpOptionsId": { + "Ref": "DHCPOptions" + }, + "VpcId": { + "Ref": "Vpc" + } + } + }, + "NATInstance": { + "Type": "AWS::EC2::Instance", + "DependsOn": "GatewayToInternet", + "Properties": { + "KeyName": { + "Ref": "KeyName" + }, + "SourceDestCheck": "false", + "ImageId": { + "Fn::FindInMap": [ + "NATAmi", + { + "Ref": "AWS::Region" + }, + "default" + ] + }, + "InstanceType": "m3.medium", + "NetworkInterfaces": [ + { + "GroupSet": [ + { + "Ref": "SlaveSecurityGroup" + }, + { + "Ref": "MasterSecurityGroup" + }, + { + "Ref": "AdminSecurityGroup" + } + ], + "DeleteOnTermination": "true", + "AssociatePublicIpAddress": "true", + "SubnetId": { + "Ref": "PublicSubnet" + }, + "DeviceIndex": "0" + } + ] + } + }, + "MasterSecurityGroup": { + "Type": "AWS::EC2::SecurityGroup", + "Properties": { + "GroupDescription": "Mesos Masters", + "VpcId": { + "Ref": "Vpc" + }, + "SecurityGroupIngress": [ + { + "SourceSecurityGroupId": { + "Ref": "LbSecurityGroup" + }, + "FromPort": "5050", + "IpProtocol": "tcp", + "ToPort": "5050" + }, + { + "SourceSecurityGroupId": { + "Ref": "LbSecurityGroup" + }, + "FromPort": "80", + "IpProtocol": "tcp", + "ToPort": "80" + }, + { + "SourceSecurityGroupId": { + "Ref": "LbSecurityGroup" + }, + "FromPort": "8080", + "IpProtocol": "tcp", + "ToPort": "8080" + }, + { + "SourceSecurityGroupId": { + "Ref": "LbSecurityGroup" + }, + "FromPort": "8181", + "IpProtocol": "tcp", + "ToPort": "8181" + }, + { + "SourceSecurityGroupId": { + "Ref": "LbSecurityGroup" + }, + "FromPort": "2181", + "IpProtocol": "tcp", + "ToPort": "2181" + } + ] + } + }, + "PublicSlaveIngressFive": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "GroupId": { + "Ref": "PublicSlaveSecurityGroup" + }, + "IpProtocol": "udp", + "ToPort": "5050", + "CidrIp": "0.0.0.0/0", + "FromPort": "23" + } + }, + "PrivateRouteTable": { + "Type": "AWS::EC2::RouteTable", + "Properties": { + "VpcId": { + "Ref": "Vpc" + }, + "Tags": [ + { + "Key": "Application", + "Value": { + "Ref": "AWS::StackName" + } + }, + { + "Key": "Network", + "Value": "Public" + } + ] + } + }, + "MasterLaunchConfig": { + "Type": "AWS::AutoScaling::LaunchConfiguration", + "Properties": { + "IamInstanceProfile": { + "Ref": "MasterInstanceProfile" + }, + "SecurityGroups": [ + { + "Ref": "MasterSecurityGroup" + }, + { + "Ref": "AdminSecurityGroup" + } + ], + "ImageId": { + "Fn::FindInMap": [ + "RegionToAmi", + { + "Ref": "AWS::Region" + }, + "stable" + ] + }, + "InstanceType": { + "Fn::FindInMap": [ + "Parameters", + "MasterInstanceType", + "default" + ] + }, + "KeyName": { + "Ref": "KeyName" + }, + "UserData": { + "Fn::Base64": { + "Fn::Join": [ + "", + [ + "#cloud-config\n", + "write_files:\n", + " - path: /etc/mesosphere/setup-flags/repository-url\n", + " permissions: 0644\n", + " owner: root\n", + " content: |\n", + " ", + { + "Fn::FindInMap": [ + "Parameters", + "BootstrapRepoRoot", + "default" + ] + }, + "", + "\n", + " \n", + " - path: /etc/mesosphere/roles/master\n", + " \n", + " - path: /etc/mesosphere/setup-packages/dcos-config--setup/pkginfo.json\n", + " content: '{}'\n", + " - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-dns.json\n", + " content: |\n", + " {\n", + " \"zk\": \"zk://127.0.0.1:2181/mesos\",\n", + " \"refreshSeconds\": 30,\n", + " \"ttl\": 60,\n", + " \"domain\": \"mesos\",\n", + " \"port\": 53,\n", + " \"resolvers\": [\"", + { + "Fn::FindInMap": [ + "Parameters", + "FallbackDNS", + "default" + ] + }, + "\"],", + "\n", + " \"timeout\": 5,\n", + " \"listener\": \"0.0.0.0\",\n", + " \"email\": \"root.mesos-dns.mesos\"\n", + " }\n", + " - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-master\n", + " content: |\n", + " MESOS_LOG_DIR=/var/log/mesos\n", + " MESOS_WORK_DIR=/var/lib/mesos/master\n", + " MESOS_ZK=zk://127.0.0.1:2181/mesos\n", + " MESOS_QUORUM=", + { + "Fn::FindInMap": [ + "Parameters", + "MasterQuorumCount", + "default" + ] + }, + "", + "\n", + " MESOS_CLUSTER=", + { + "Ref": "AWS::StackName" + }, + "", + "\n", + " MESOS_ROLES=slave_public\n", + " - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-slave\n", + " content: |\n", + " MESOS_MASTER=zk://leader.mesos:2181/mesos\n", + " MESOS_CONTAINERIZERS=docker,mesos\n", + " MESOS_LOG_DIR=/var/log/mesos\n", + " MESOS_EXECUTOR_REGISTRATION_TIMEOUT=5mins\n", + " MESOS_ISOLATION=cgroups/cpu,cgroups/mem\n", + " MESOS_WORK_DIR=/var/lib/mesos/slave\n", + " MESOS_RESOURCES=ports:[1025-2180,2182-3887,3889-5049,5052-8079,8082-8180,8182-65535]\n", + " MESOS_SLAVE_SUBSYSTEMS=cpu,memory\n", + " - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-slave-public\n", + " content: |\n", + " MESOS_MASTER=zk://leader.mesos:2181/mesos\n", + " MESOS_CONTAINERIZERS=docker,mesos\n", + " MESOS_LOG_DIR=/var/log/mesos\n", + " MESOS_EXECUTOR_REGISTRATION_TIMEOUT=5mins\n", + " MESOS_ISOLATION=cgroups/cpu,cgroups/mem\n", + " MESOS_WORK_DIR=/var/lib/mesos/slave\n", + " MESOS_RESOURCES=ports:[1-21,23-5050,5052-65535]\n", + " MESOS_SLAVE_SUBSYSTEMS=cpu,memory\n", + " MESOS_DEFAULT_ROLE=slave_public\n", + " MESOS_ATTRIBUTES=public_ip:true\n", + "\n", + " - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/cloudenv\n", + " content: |\n", + " AWS_REGION=", + { + "Ref": "AWS::Region" + }, + "", + "\n", + " AWS_STACK_ID=", + { + "Ref": "AWS::StackId" + }, + "", + "\n", + " AWS_STACK_NAME=", + { + "Ref": "AWS::StackName" + }, + "", + "\n", + " AWS_ACCESS_KEY_ID=", + { + "Ref": "HostKeys" + }, + "", + "\n", + " AWS_SECRET_ACCESS_KEY=", + { + "Fn::GetAtt": [ + "HostKeys", + "SecretAccessKey" + ] + }, + "", + "\n", + " ZOOKEEPER_CLUSTER_SIZE=", + { + "Fn::FindInMap": [ + "Parameters", + "MasterInstanceCount", + "default" + ] + }, + "", + "\n", + " MASTER_ELB=", + { + "Fn::GetAtt": [ + "InternalMasterLoadBalancer", + "DNSName" + ] + }, + "", + "\n", + " EXTERNAL_ELB=", + { + "Fn::GetAtt": [ + "ElasticLoadBalancer", + "DNSName" + ] + }, + "", + "\n", + " # Must set FALLBACK_DNS to an AWS region-specific DNS server which returns\n", + " # the internal IP when doing lookups on AWS public hostnames.\n", + " FALLBACK_DNS=", + { + "Fn::FindInMap": [ + "Parameters", + "FallbackDNS", + "default" + ] + }, + "", + "\n", + " - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/exhibitor\n", + " content: |\n", + " AWS_S3_BUCKET=", + { + "Ref": "ExhibitorS3Bucket" + }, + "", + "\n", + " AWS_S3_PREFIX=", + { + "Ref": "AWS::StackName" + }, + "", + "\n", + " EXHIBITOR_WEB_UI_PORT=8181\n", + "\n", + "coreos:\n", + " update:\n", + " reboot-strategy: off\n", + " units:\n", + " - name: format-var-lib-ephemeral.service\n", + " command: start\n", + " content: |\n", + " [Unit]\n", + " Description=Formats the /var/lib ephemeral drive\n", + " Before=var-lib.mount dbus.service\n", + " [Service]\n", + " Type=oneshot\n", + " RemainAfterExit=yes\n", + " ExecStart=/bin/bash -c '(blkid -t TYPE=ext4 | grep xvdb) || (/usr/sbin/mkfs.ext4 -F /dev/xvdb)'\n", + " - name: var-lib.mount\n", + " command: start\n", + " content: |\n", + " [Unit]\n", + " Description=Mount /var/lib\n", + " Before=dbus.service\n", + " [Mount]\n", + " What=/dev/xvdb\n", + " Where=/var/lib\n", + " Type=ext4\n", + "\n", + " - name: etcd.service\n", + " mask: true\n", + " command: stop\n", + " - name: update-engine.service\n", + " mask: true\n", + " command: stop\n", + " - name: locksmithd.service\n", + " mask: true\n", + " command: stop\n", + " - name: systemd-resolved.service\n", + " command: stop\n", + " - name: config-writer.service\n", + " command: start\n", + " content: |\n", + " [Unit]\n", + " Description=Write out dynamic config values\n", + " [Service]\n", + " Type=oneshot\n", + " ExecStart=/usr/bin/bash -c \"echo EXHIBITOR_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/cloudenv\"\n", + " ExecStart=/usr/bin/bash -c \"echo MARATHON_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/cloudenv\"\n", + " ExecStart=/usr/bin/bash -c \"echo MESOS_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-master\"\n", + " ExecStart=/usr/bin/bash -c \"echo MESOS_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-slave\"\n", + "\n", + " - name: link-env.service\n", + " command: start\n", + " content: |\n", + " [Unit]\n", + " Before=dcos.target\n", + " [Service]\n", + " Type=oneshot\n", + " ExecStartPre=/usr/bin/mkdir -p /etc/profile.d\n", + " ExecStart=/usr/bin/ln -sf /opt/mesosphere/environment.export /etc/profile.d/dcos.sh\n", + " - name: dcos-download.service\n", + " content: |\n", + " [Unit]\n", + " Description=Download the DCOS\n", + " After=network-online.target\n", + " Wants=network-online.target\n", + " ConditionPathExists=!/opt/mesosphere/\n", + " [Service]\n", + " Type=oneshot\n", + " ExecStartPre=/usr/bin/bash -c 'until wget --progress=dot -e dotbytes=10M --continue ", + { + "Fn::FindInMap": [ + "Parameters", + "BootstrapRepoRoot", + "default" + ] + }, + "/bootstrap.tar.xz -O /tmp/bootstrap.tar.xz; do echo \"failed to download\"; sleep 5; done'", + "\n", + " ExecStartPre=/usr/bin/mkdir -p /opt/mesosphere\n", + " ExecStart=/usr/bin/tar -axf /tmp/bootstrap.tar.xz -C /opt/mesosphere\n", + " - name: dcos-setup.service\n", + " command: start\n", + " enable: true\n", + " content: |\n", + " [Unit]\n", + " Description=Prep the Pkgpanda working directories for this host.\n", + " Requires=dcos-download.service\n", + " After=dcos-download.service\n", + " [Service]\n", + " Type=oneshot\n", + " EnvironmentFile=/opt/mesosphere/environment\n", + " ExecStart=/opt/mesosphere/bin/pkgpanda setup --no-block-systemd\n", + " [Install]\n", + " WantedBy=multi-user.target\n", + " - name: cfn-signal.service\n", + " command: start\n", + " content: |\n", + " [Unit]\n", + " Description=Signal CloudFormation Success\n", + " After=dcos.target\n", + " Requires=dcos.target\n", + " ConditionPathExists=!/var/lib/cfn-signal\n", + " [Service]\n", + " Type=simple\n", + " Restart=on-failure\n", + " StartLimitInterval=0\n", + " RestartSec=15s\n", + " ExecStartPre=/usr/bin/docker pull mbabineau/cfn-bootstrap\n", + " ExecStartPre=/bin/ping -c1 leader.mesos\n", + " ExecStartPre=/usr/bin/docker run --rm mbabineau/cfn-bootstrap \\\n", + " cfn-signal -e 0 \\\n", + " --resource MasterServerGroup \\\n", + " --stack ", + { + "Ref": "AWS::StackName" + }, + " \\", + "\n", + " --region ", + { + "Ref": "AWS::Region" + }, + "", + "\n", + " ExecStart=/usr/bin/touch /var/lib/cfn-signal\n" + ] + ] + } + }, + "AssociatePublicIpAddress": "true", + "BlockDeviceMappings": [ + { + "VirtualName": "ephemeral0", + "DeviceName": "/dev/sdb" + } + ] + } + }, + "SlaveToSlaveIngress": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "SourceSecurityGroupId": { + "Ref": "SlaveSecurityGroup" + }, + "GroupId": { + "Ref": "SlaveSecurityGroup" + }, + "IpProtocol": "-1", + "ToPort": "65535", + "FromPort": "0" + } + }, + "PublicSubnetNetworkAclAssociation": { + "Type": "AWS::EC2::SubnetNetworkAclAssociation", + "Properties": { + "SubnetId": { + "Ref": "PublicSubnet" + }, + "NetworkAclId": { + "Ref": "PublicNetworkAcl" + } + } + }, + "MasterServerGroup": { + "Type": "AWS::AutoScaling::AutoScalingGroup", + "CreationPolicy": { + "ResourceSignal": { + "Timeout": { + "Fn::FindInMap": [ + "Parameters", + "StackCreationTimeout", + "default" + ] + }, + "Count": { + "Fn::FindInMap": [ + "Parameters", + "MasterInstanceCount", + "default" + ] + } + } + }, + "Properties": { + "LoadBalancerNames": [ + { + "Ref": "ElasticLoadBalancer" + }, + { + "Ref": "InternalMasterLoadBalancer" + } + ], + "AvailabilityZones": [ + { + "Fn::GetAtt": [ + "PublicSubnet", + "AvailabilityZone" + ] + } + ], + "MaxSize": { + "Fn::FindInMap": [ + "Parameters", + "MasterInstanceCount", + "default" + ] + }, + "VPCZoneIdentifier": [ + { + "Ref": "PublicSubnet" + } + ], + "LaunchConfigurationName": { + "Ref": "MasterLaunchConfig" + }, + "DesiredCapacity": { + "Fn::FindInMap": [ + "Parameters", + "MasterInstanceCount", + "default" + ] + }, + "MinSize": { + "Fn::FindInMap": [ + "Parameters", + "MasterInstanceCount", + "default" + ] + }, + "Tags": [ + { + "PropagateAtLaunch": "true", + "Key": "role", + "Value": "mesos-master" + } + ] + } + }, + "SlaveToMasterIngress": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "SourceSecurityGroupId": { + "Ref": "SlaveSecurityGroup" + }, + "GroupId": { + "Ref": "MasterSecurityGroup" + }, + "IpProtocol": "-1", + "ToPort": "65535", + "FromPort": "0" + } + }, + "Vpc": { + "Type": "AWS::EC2::VPC", + "Properties": { + "CidrBlock": { + "Fn::FindInMap": [ + "Parameters", + "VPCSubnetRange", + "default" + ] + }, + "EnableDnsSupport": "true", + "EnableDnsHostnames": "true", + "Tags": [ + { + "Key": "Application", + "Value": { + "Ref": "AWS::StackName" + } + }, + { + "Key": "Network", + "Value": "Public" + } + ] + } + }, + "OutboundNetworkAclEntry": { + "Type": "AWS::EC2::NetworkAclEntry", + "Properties": { + "Protocol": "-1", + "PortRange": { + "From": "0", + "To": "65535" + }, + "Egress": "true", + "CidrBlock": "0.0.0.0/0", + "RuleNumber": "100", + "RuleAction": "allow", + "NetworkAclId": { + "Ref": "PublicNetworkAcl" + } + } + }, + "PublicSlaveIngressThree": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "GroupId": { + "Ref": "PublicSlaveSecurityGroup" + }, + "IpProtocol": "tcp", + "ToPort": "65535", + "CidrIp": "0.0.0.0/0", + "FromPort": "5052" + } + }, + "PublicSlaveIngressSix": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "GroupId": { + "Ref": "PublicSlaveSecurityGroup" + }, + "IpProtocol": "udp", + "ToPort": "65535", + "CidrIp": "0.0.0.0/0", + "FromPort": "5052" + } + }, + "ExhibitorS3Bucket": { + "Type": "AWS::S3::Bucket", + "DeletionPolicy": "Retain" + }, + "InboundNetworkAclEntry": { + "Type": "AWS::EC2::NetworkAclEntry", + "Properties": { + "Protocol": "-1", + "PortRange": { + "From": "0", + "To": "65535" + }, + "Egress": "false", + "CidrBlock": "0.0.0.0/0", + "RuleNumber": "100", + "RuleAction": "allow", + "NetworkAclId": { + "Ref": "PublicNetworkAcl" + } + } + }, + "MasterInstanceProfile": { + "Type": "AWS::IAM::InstanceProfile", + "Properties": { + "Roles": [ + { + "Ref": "MasterRole" + } + ], + "Path": "/" + } + }, + "PublicNetworkAcl": { + "Type": "AWS::EC2::NetworkAcl", + "Properties": { + "VpcId": { + "Ref": "Vpc" + }, + "Tags": [ + { + "Key": "Application", + "Value": { + "Ref": "AWS::StackName" + } + }, + { + "Key": "Network", + "Value": "Public" + } + ] + } + }, + "PublicSlaveIngressOne": { + "Type": "AWS::EC2::SecurityGroupIngress", + "Properties": { + "GroupId": { + "Ref": "PublicSlaveSecurityGroup" + }, + "IpProtocol": "tcp", + "ToPort": "21", + "CidrIp": "0.0.0.0/0", + "FromPort": "0" + } + } + }, + "Conditions": { + "RegionIsUsEast1": { + "Fn::Equals": [ + { + "Ref": "AWS::Region" + }, + "us-east-1" + ] + } + }, + "AWSTemplateFormatVersion": "2010-09-09", + "Outputs": { + "DnsAddress": { + "Description": "Mesos Master", + "Value": { + "Fn::GetAtt": [ + "ElasticLoadBalancer", + "DNSName" + ] + } + }, + "PublicSlaveDnsAddress": { + "Description": "Public slaves", + "Value": { + "Fn::GetAtt": [ + "PublicSlaveLoadBalancer", + "DNSName" + ] + } + } + } +} diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..343ee08 --- /dev/null +++ b/outputs.tf @@ -0,0 +1,7 @@ +output "master" { + value = "${aws_elb.dcos.dns_name}" +} + +output "public_slave" { + value = "${aws_elb.public_slaves.dns_name}" +} diff --git a/private_route_table.tf b/private_route_table.tf new file mode 100644 index 0000000..20c5f16 --- /dev/null +++ b/private_route_table.tf @@ -0,0 +1,13 @@ +resource "aws_route_table" "private" { + vpc_id = "${aws_vpc.dcos.id}" + + route { + cidr_block = "0.0.0.0/0" + instance_id = "${aws_instance.nat.id}" + } + + tags { + Application = "${var.stack_name}" + Network = "Public" + } +} diff --git a/private_subnet.tf b/private_subnet.tf new file mode 100644 index 0000000..8ab4c56 --- /dev/null +++ b/private_subnet.tf @@ -0,0 +1,9 @@ +resource "aws_subnet" "private" { + vpc_id = "${aws_vpc.dcos.id}" + cidr_block = "${var.private_subnet_range}" + + tags { + Application = "${var.stack_name}" + Network = "Private" + } +} diff --git a/private_subnet_network_acl_association.tf b/private_subnet_network_acl_association.tf new file mode 100644 index 0000000..96ed103 --- /dev/null +++ b/private_subnet_network_acl_association.tf @@ -0,0 +1,27 @@ +resource "aws_network_acl" "private" { + vpc_id = "${aws_vpc.dcos.id}" + subnet_ids = ["${aws_subnet.private.id}"] + + egress { + protocol = "-1" + rule_no = 100 + action = "allow" + cidr_block = "0.0.0.0/0" + from_port = 0 + to_port = 0 + } + + ingress { + protocol = "-1" + rule_no = 100 + action = "allow" + cidr_block = "0.0.0.0/0" + from_port = 0 + to_port = 0 + } + + tags { + Application = "${var.stack_name}" + Network = "Public" + } +} diff --git a/private_subnet_route_table_association.tf b/private_subnet_route_table_association.tf new file mode 100644 index 0000000..204131d --- /dev/null +++ b/private_subnet_route_table_association.tf @@ -0,0 +1,4 @@ +resource "aws_route_table_association" "private" { + subnet_id = "${aws_subnet.private.id}" + route_table_id = "${aws_route_table.private.id}" +} diff --git a/provider.tf b/provider.tf new file mode 100644 index 0000000..e03ac10 --- /dev/null +++ b/provider.tf @@ -0,0 +1,5 @@ +provider "aws" { + access_key = "${var.aws_access_key}" + secret_key = "${var.aws_secret_key}" + region = "${var.aws_region}" +} diff --git a/public_route_table.tf b/public_route_table.tf new file mode 100644 index 0000000..cc5000f --- /dev/null +++ b/public_route_table.tf @@ -0,0 +1,13 @@ +resource "aws_route_table" "public" { + vpc_id = "${aws_vpc.dcos.id}" + + route { + cidr_block = "0.0.0.0/0" + gateway_id = "${aws_internet_gateway.dcos.id}" + } + + tags { + Application = "${var.stack_name}" + Network = "Public" + } +} diff --git a/public_slave_launch_configuration.tf b/public_slave_launch_configuration.tf new file mode 100644 index 0000000..2ef126b --- /dev/null +++ b/public_slave_launch_configuration.tf @@ -0,0 +1,36 @@ +resource "aws_launch_configuration" "public_slave" { + security_groups = ["${aws_security_group.public_slave.id}"] + image_id = "${var.instance_ami}" + instance_type = "${var.public_slave_instance_type}" + key_name = "${aws_key_pair.dcos.key_name}" + user_data = "${template_file.public_slave_user_data.rendered}" + associate_public_ip_address = true + + root_block_device { + volume_type = "gp2" + volume_size = "64" + delete_on_termination = true + } + + lifecycle { + create_before_destroy = false + } +} + +resource "template_file" "public_slave_user_data" { + filename = "public_slave_user_data.yml" + + vars { + stack_name = "${var.stack_name}" + aws_region = "${var.aws_region}" + aws_access_key_id = "${aws_iam_access_key.host_keys.id}" + aws_secret_access_key = "${aws_iam_access_key.host_keys.secret}" + fallback_dns = "${var.fallback_dns}" + internal_master_lb_dns_name = "${aws_elb.internal_master.dns_name}" + dcos_lb_dns_name = "${aws_elb.dcos.dns_name}" + exhibitor_s3_bucket = "${aws_s3_bucket.exhibitor.id}" + bootstrap_repo_root = "${var.bootstrap_repo_root}" + mesos_quorum = "${var.master_quorum_count}" + master_instance_count = "${var.master_instance_count}" + } +} diff --git a/public_slave_load_balancer.tf b/public_slave_load_balancer.tf new file mode 100644 index 0000000..63dba76 --- /dev/null +++ b/public_slave_load_balancer.tf @@ -0,0 +1,29 @@ +resource "aws_elb" "public_slaves" { + name = "public-slave-load-balancer" + + subnets = ["${aws_subnet.public.id}"] + + security_groups = ["${aws_security_group.public_slave.id}"] + + health_check { + healthy_threshold = 2 + unhealthy_threshold = 2 + timeout = 5 + target = "HTTP:80/" + interval = 30 + } + + listener { + instance_port = 80 + instance_protocol = "http" + lb_port = 80 + lb_protocol = "http" + } + + listener { + instance_port = 443 + instance_protocol = "tcp" + lb_port = 443 + lb_protocol = "tcp" + } +} diff --git a/public_slave_security_group.tf b/public_slave_security_group.tf new file mode 100644 index 0000000..6c2e06e --- /dev/null +++ b/public_slave_security_group.tf @@ -0,0 +1,86 @@ +resource "aws_security_group" "public_slave" { + name = "public_slave" + description = "Mesos Slaves Public" + + vpc_id = "${aws_vpc.dcos.id}" +} + +resource "aws_security_group_rule" "public_slave_egress_all" { + security_group_id = "${aws_security_group.public_slave.id}" + + type = "egress" + from_port = 0 + to_port = 65535 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] +} + +resource "aws_security_group_rule" "public_slave_ingress_public_slave" { + security_group_id = "${aws_security_group.public_slave.id}" + + type = "ingress" + from_port = 0 + to_port = 65535 + protocol = "-1" + self = true +} + +resource "aws_security_group_rule" "public_slave_ingress_master" { + security_group_id = "${aws_security_group.public_slave.id}" + + type = "ingress" + from_port = 0 + to_port = 65535 + protocol = "-1" + source_security_group_id = "${aws_security_group.master.id}" +} + +resource "aws_security_group_rule" "public_slave_ingress_slave" { + security_group_id = "${aws_security_group.public_slave.id}" + + type = "ingress" + from_port = 0 + to_port = 65535 + protocol = "-1" + source_security_group_id = "${aws_security_group.slave.id}" +} + +resource "aws_security_group_rule" "public_slave_ingress_0_21_tcp" { + security_group_id = "${aws_security_group.public_slave.id}" + + type = "ingress" + from_port = 0 + to_port = 21 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] +} + +resource "aws_security_group_rule" "public_slave_ingress_0_21_udp" { + security_group_id = "${aws_security_group.public_slave.id}" + + type = "ingress" + from_port = 0 + to_port = 21 + protocol = "udp" + cidr_blocks = ["0.0.0.0/0"] +} + +resource "aws_security_group_rule" "public_slave_ingress_23_5050_udp" { + security_group_id = "${aws_security_group.public_slave.id}" + + type = "ingress" + from_port = 23 + to_port = 5050 + protocol = "udp" + cidr_blocks = ["0.0.0.0/0"] +} + +resource "aws_security_group_rule" "public_slave_ingress_5052_65535_udp" { + security_group_id = "${aws_security_group.public_slave.id}" + + type = "ingress" + from_port = 5052 + to_port = 65535 + protocol = "udp" + cidr_blocks = ["0.0.0.0/0"] +} diff --git a/public_slave_server_group.tf b/public_slave_server_group.tf new file mode 100644 index 0000000..f7c01e8 --- /dev/null +++ b/public_slave_server_group.tf @@ -0,0 +1,22 @@ +resource "aws_autoscaling_group" "public_slave_server_group" { + name = "Public Slaves" + + min_size = "${var.public_slave_instance_count}" + max_size = "${var.public_slave_instance_count}" + desired_capacity = "${var.public_slave_instance_count}" + + load_balancers = ["${aws_elb.public_slaves.id}"] + availability_zones = ["${aws_subnet.public.availability_zone}"] + vpc_zone_identifier = ["${aws_subnet.public.id}"] + launch_configuration = "${aws_launch_configuration.public_slave.id}" + + tag { + key = "role" + value = "mesos-slave" + propagate_at_launch = true + } + + lifecycle { + create_before_destroy = false + } +} diff --git a/public_slave_user_data.yml b/public_slave_user_data.yml new file mode 100644 index 0000000..9540310 --- /dev/null +++ b/public_slave_user_data.yml @@ -0,0 +1,146 @@ +#cloud-config + +write_files: + - path: /etc/ssh/sshd_config + permissions: 0600 + owner: root:root + content: | + UsePrivilegeSeparation sandbox + Subsystem sftp internal-sftp + + PermitRootLogin no + AllowUsers core + PasswordAuthentication no + ChallengeResponseAuthentication no + - path: /etc/mesosphere/setup-flags/repository-url + permissions: 0644 + owner: root + content: | + ${bootstrap_repo_root} + - path: /etc/mesosphere/roles/slave_public + - path: /etc/mesosphere/setup-packages/dcos-config--setup/pkginfo.json + content: '{}' + - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-dns.json + content: | + { + "zk": "zk://127.0.0.1:2181/mesos", + "refreshSeconds": 30, + "ttl": 60, + "domain": "mesos", + "port": 53, + "resolvers": ["${fallback_dns}"], + "timeout": 5, + "listener": "0.0.0.0", + "email": "root.mesos-dns.mesos" + } + - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-master + content: | + MESOS_LOG_DIR=/var/log/mesos + MESOS_WORK_DIR=/var/lib/mesos/master + MESOS_ZK=zk://127.0.0.1:2181/mesos + MESOS_QUORUM=${mesos_quorum} + MESOS_CLUSTER=${stack_name} + MESOS_ROLES=slave_public + - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-slave + content: | + MESOS_MASTER=zk://leader.mesos:2181/mesos + MESOS_CONTAINERIZERS=docker,mesos + MESOS_LOG_DIR=/var/log/mesos + MESOS_EXECUTOR_REGISTRATION_TIMEOUT=5mins + MESOS_ISOLATION=cgroups/cpu,cgroups/mem + MESOS_WORK_DIR=/var/lib/mesos/slave + MESOS_RESOURCES=ports:[1025-2180,2182-3887,3889-5049,5052-8079,8082-8180,8182-65535] + MESOS_SLAVE_SUBSYSTEMS=cpu,memory + - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-slave-public + content: | + MESOS_MASTER=zk://leader.mesos:2181/mesos + MESOS_CONTAINERIZERS=docker,mesos + MESOS_LOG_DIR=/var/log/mesos + MESOS_EXECUTOR_REGISTRATION_TIMEOUT=5mins + MESOS_ISOLATION=cgroups/cpu,cgroups/mem + MESOS_WORK_DIR=/var/lib/mesos/slave + MESOS_RESOURCES=ports:[1-21,23-5050,5052-65535] + MESOS_SLAVE_SUBSYSTEMS=cpu,memory + MESOS_DEFAULT_ROLE=slave_public + MESOS_ATTRIBUTES=public_ip:true + - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/cloudenv + content: | + AWS_REGION=${aws_region} + AWS_STACK_ID= + AWS_STACK_NAME=${stack_name} + AWS_ACCESS_KEY_ID=${aws_access_key_id} + AWS_SECRET_ACCESS_KEY=${aws_secret_access_key} + ZOOKEEPER_CLUSTER_SIZE=${master_instance_count} + MASTER_ELB=${internal_master_lb_dns_name} + EXTERNAL_ELB=${dcos_lb_dns_name} + + # Must set FALLBACK_DNS to an AWS region-specific DNS server which returns + # the internal IP when doing lookups on AWS public hostnames. + FALLBACK_DNS=${fallback_dns} + - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/exhibitor + content: | + AWS_S3_BUCKET=${exhibitor_s3_bucket} + AWS_S3_PREFIX=${stack_name} + EXHIBITOR_WEB_UI_PORT=8181 + +coreos: + update: + reboot-strategy: off + units: + - name: etcd.service + mask: true + command: stop + - name: update-engine.service + mask: true + command: stop + - name: locksmithd.service + mask: true + command: stop + - name: systemd-resolved.service + command: stop + - name: config-writer.service + command: start + content: | + [Unit] + Description=Write out dynamic config values + [Service] + Type=oneshot + ExecStart=/usr/bin/bash -c "echo EXHIBITOR_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/cloudenv" + ExecStart=/usr/bin/bash -c "echo MARATHON_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/cloudenv" + ExecStart=/usr/bin/bash -c "echo MESOS_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-master" + ExecStart=/usr/bin/bash -c "echo MESOS_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-slave" + - name: link-env.service + command: start + content: | + [Unit] + Before=dcos.target + [Service] + Type=oneshot + ExecStartPre=/usr/bin/mkdir -p /etc/profile.d + ExecStart=/usr/bin/ln -sf /opt/mesosphere/environment.export /etc/profile.d/dcos.sh + - name: dcos-download.service + content: | + [Unit] + Description=Download the DCOS + After=network-online.target + Wants=network-online.target + ConditionPathExists=!/opt/mesosphere/ + [Service] + Type=oneshot + ExecStartPre=/usr/bin/bash -c 'until wget --progress=dot -e dotbytes=10M --continue ${bootstrap_repo_root}/bootstrap.tar.xz -O /tmp/bootstrap.tar.xz; do echo "failed to download"; sleep 5; done' + ExecStartPre=/usr/bin/mkdir -p /opt/mesosphere + ExecStart=/usr/bin/tar -axf /tmp/bootstrap.tar.xz -C /opt/mesosphere + - name: dcos-setup.service + command: start + enable: true + content: | + [Unit] + Description=Prep the Pkgpanda working directories for this host. + Requires=dcos-download.service + After=dcos-download.service + [Service] + Type=oneshot + EnvironmentFile=/opt/mesosphere/environment + ExecStart=/opt/mesosphere/bin/pkgpanda setup --no-block-systemd + [Install] + WantedBy=multi-user.target diff --git a/public_subnet.tf b/public_subnet.tf new file mode 100644 index 0000000..a9893f7 --- /dev/null +++ b/public_subnet.tf @@ -0,0 +1,9 @@ +resource "aws_subnet" "public" { + vpc_id = "${aws_vpc.dcos.id}" + cidr_block = "${var.public_subnet_range}" + + tags { + Application = "${var.stack_name}" + Network = "Public" + } +} diff --git a/public_subnet_network_acl_association.tf b/public_subnet_network_acl_association.tf new file mode 100644 index 0000000..672949e --- /dev/null +++ b/public_subnet_network_acl_association.tf @@ -0,0 +1,27 @@ +resource "aws_network_acl" "public" { + vpc_id = "${aws_vpc.dcos.id}" + subnet_ids = ["${aws_subnet.public.id}"] + + egress { + protocol = "-1" + rule_no = 100 + action = "allow" + cidr_block = "0.0.0.0/0" + from_port = 0 + to_port = 0 + } + + ingress { + protocol = "-1" + rule_no = 100 + action = "allow" + cidr_block = "0.0.0.0/0" + from_port = 0 + to_port = 0 + } + + tags { + Application = "${var.stack_name}" + Network = "Public" + } +} diff --git a/public_subnet_route_table_association.tf b/public_subnet_route_table_association.tf new file mode 100644 index 0000000..cac16a7 --- /dev/null +++ b/public_subnet_route_table_association.tf @@ -0,0 +1,4 @@ +resource "aws_route_table_association" "public" { + subnet_id = "${aws_subnet.public.id}" + route_table_id = "${aws_route_table.public.id}" +} diff --git a/s3_bucket.tf b/s3_bucket.tf new file mode 100644 index 0000000..c5a780d --- /dev/null +++ b/s3_bucket.tf @@ -0,0 +1,8 @@ +resource "aws_s3_bucket" "exhibitor" { + bucket = "dcos-exhibitor" + force_destroy = true + + lifecycle { + prevent_destroy = false + } +} diff --git a/slave_egress.tf b/slave_egress.tf new file mode 100644 index 0000000..ffd4769 --- /dev/null +++ b/slave_egress.tf @@ -0,0 +1,9 @@ +/*resource "aws_security_group_rule" "slave_egress" { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + + security_group_id = "${aws_security_group.slave.id}" +}*/ diff --git a/slave_launch_configuration.tf b/slave_launch_configuration.tf new file mode 100644 index 0000000..0b99b09 --- /dev/null +++ b/slave_launch_configuration.tf @@ -0,0 +1,36 @@ +resource "aws_launch_configuration" "slave" { + security_groups = ["${aws_security_group.slave.id}"] + image_id = "${var.instance_ami}" + instance_type = "${var.slave_instance_type}" + key_name = "${aws_key_pair.dcos.key_name}" + user_data = "${template_file.slave_user_data.rendered}" + associate_public_ip_address = false + + root_block_device { + volume_type = "gp2" + volume_size = "64" + delete_on_termination = true + } + + lifecycle { + create_before_destroy = false + } +} + +resource "template_file" "slave_user_data" { + filename = "slave_user_data.yml" + + vars { + stack_name = "${var.stack_name}" + aws_region = "${var.aws_region}" + aws_access_key_id = "${aws_iam_access_key.host_keys.id}" + aws_secret_access_key = "${aws_iam_access_key.host_keys.secret}" + fallback_dns = "${var.fallback_dns}" + internal_master_lb_dns_name = "${aws_elb.internal_master.dns_name}" + dcos_lb_dns_name = "${aws_elb.dcos.dns_name}" + exhibitor_s3_bucket = "${aws_s3_bucket.exhibitor.id}" + bootstrap_repo_root = "${var.bootstrap_repo_root}" + mesos_quorum = "${var.master_quorum_count}" + master_instance_count = "${var.master_instance_count}" + } +} diff --git a/slave_security_group.tf b/slave_security_group.tf new file mode 100644 index 0000000..ad37944 --- /dev/null +++ b/slave_security_group.tf @@ -0,0 +1,46 @@ +resource "aws_security_group" "slave" { + name = "slave" + description = "Mesos Slaves" + + vpc_id = "${aws_vpc.dcos.id}" +} + +resource "aws_security_group_rule" "slave_egress_all" { + security_group_id = "${aws_security_group.slave.id}" + + type = "egress" + from_port = 0 + to_port = 65535 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] +} + +resource "aws_security_group_rule" "slave_ingress_slave" { + security_group_id = "${aws_security_group.slave.id}" + + type = "ingress" + from_port = 0 + to_port = 65535 + protocol = "-1" + self = true +} + +resource "aws_security_group_rule" "slave_ingress_master" { + security_group_id = "${aws_security_group.slave.id}" + + type = "ingress" + from_port = 0 + to_port = 65535 + protocol = "-1" + source_security_group_id = "${aws_security_group.master.id}" +} + +resource "aws_security_group_rule" "slave_ingress_public_slave" { + security_group_id = "${aws_security_group.slave.id}" + + type = "ingress" + from_port = 0 + to_port = 65535 + protocol = "-1" + source_security_group_id = "${aws_security_group.public_slave.id}" +} diff --git a/slave_server_group.tf b/slave_server_group.tf new file mode 100644 index 0000000..c63dc9c --- /dev/null +++ b/slave_server_group.tf @@ -0,0 +1,21 @@ +resource "aws_autoscaling_group" "slave_server_group" { + name = "Slaves" + + min_size = "${var.slave_instance_count}" + max_size = "${var.slave_instance_count}" + desired_capacity = "${var.slave_instance_count}" + + availability_zones = ["${aws_subnet.private.availability_zone}"] + vpc_zone_identifier = ["${aws_subnet.private.id}"] + launch_configuration = "${aws_launch_configuration.slave.id}" + + tag { + key = "role" + value = "mesos-slave" + propagate_at_launch = true + } + + lifecycle { + create_before_destroy = false + } +} diff --git a/slave_user_data.yml b/slave_user_data.yml new file mode 100644 index 0000000..0653e1a --- /dev/null +++ b/slave_user_data.yml @@ -0,0 +1,146 @@ +#cloud-config + +write_files: + - path: /etc/ssh/sshd_config + permissions: 0600 + owner: root:root + content: | + UsePrivilegeSeparation sandbox + Subsystem sftp internal-sftp + + PermitRootLogin no + AllowUsers core + PasswordAuthentication no + ChallengeResponseAuthentication no + - path: /etc/mesosphere/setup-flags/repository-url + permissions: 0644 + owner: root + content: | + ${bootstrap_repo_root} + - path: /etc/mesosphere/roles/slave + - path: /etc/mesosphere/setup-packages/dcos-config--setup/pkginfo.json + content: '{}' + - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-dns.json + content: | + { + "zk": "zk://127.0.0.1:2181/mesos", + "refreshSeconds": 30, + "ttl": 60, + "domain": "mesos", + "port": 53, + "resolvers": ["${fallback_dns}"], + "timeout": 5, + "listener": "0.0.0.0", + "email": "root.mesos-dns.mesos" + } + - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-master + content: | + MESOS_LOG_DIR=/var/log/mesos + MESOS_WORK_DIR=/var/lib/mesos/master + MESOS_ZK=zk://127.0.0.1:2181/mesos + MESOS_QUORUM=${mesos_quorum} + MESOS_CLUSTER=${stack_name} + MESOS_ROLES=slave_public + - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-slave + content: | + MESOS_MASTER=zk://leader.mesos:2181/mesos + MESOS_CONTAINERIZERS=docker,mesos + MESOS_LOG_DIR=/var/log/mesos + MESOS_EXECUTOR_REGISTRATION_TIMEOUT=5mins + MESOS_ISOLATION=cgroups/cpu,cgroups/mem + MESOS_WORK_DIR=/var/lib/mesos/slave + MESOS_RESOURCES=ports:[1025-2180,2182-3887,3889-5049,5052-8079,8082-8180,8182-65535] + MESOS_SLAVE_SUBSYSTEMS=cpu,memory + - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-slave-public + content: | + MESOS_MASTER=zk://leader.mesos:2181/mesos + MESOS_CONTAINERIZERS=docker,mesos + MESOS_LOG_DIR=/var/log/mesos + MESOS_EXECUTOR_REGISTRATION_TIMEOUT=5mins + MESOS_ISOLATION=cgroups/cpu,cgroups/mem + MESOS_WORK_DIR=/var/lib/mesos/slave + MESOS_RESOURCES=ports:[1-21,23-5050,5052-65535] + MESOS_SLAVE_SUBSYSTEMS=cpu,memory + MESOS_DEFAULT_ROLE=slave_public + MESOS_ATTRIBUTES=public_ip:true + - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/cloudenv + content: | + AWS_REGION=${aws_region} + AWS_STACK_ID= + AWS_STACK_NAME=${stack_name} + AWS_ACCESS_KEY_ID=${aws_access_key_id} + AWS_SECRET_ACCESS_KEY=${aws_secret_access_key} + ZOOKEEPER_CLUSTER_SIZE=${master_instance_count} + MASTER_ELB=${internal_master_lb_dns_name} + EXTERNAL_ELB=${dcos_lb_dns_name} + + # Must set FALLBACK_DNS to an AWS region-specific DNS server which returns + # the internal IP when doing lookups on AWS public hostnames. + FALLBACK_DNS=${fallback_dns} + - path: /etc/mesosphere/setup-packages/dcos-config--setup/etc/exhibitor + content: | + AWS_S3_BUCKET=${exhibitor_s3_bucket} + AWS_S3_PREFIX=${stack_name} + EXHIBITOR_WEB_UI_PORT=8181 + +coreos: + update: + reboot-strategy: off + units: + - name: etcd.service + mask: true + command: stop + - name: update-engine.service + mask: true + command: stop + - name: locksmithd.service + mask: true + command: stop + - name: systemd-resolved.service + command: stop + - name: config-writer.service + command: start + content: | + [Unit] + Description=Write out dynamic config values + [Service] + Type=oneshot + ExecStart=/usr/bin/bash -c "echo EXHIBITOR_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/cloudenv" + ExecStart=/usr/bin/bash -c "echo MARATHON_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/cloudenv" + ExecStart=/usr/bin/bash -c "echo MESOS_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-master" + ExecStart=/usr/bin/bash -c "echo MESOS_HOSTNAME=$(curl -s http://169.254.169.254/latest/meta-data/hostname) >> /etc/mesosphere/setup-packages/dcos-config--setup/etc/mesos-slave" + - name: link-env.service + command: start + content: | + [Unit] + Before=dcos.target + [Service] + Type=oneshot + ExecStartPre=/usr/bin/mkdir -p /etc/profile.d + ExecStart=/usr/bin/ln -sf /opt/mesosphere/environment.export /etc/profile.d/dcos.sh + - name: dcos-download.service + content: | + [Unit] + Description=Download the DCOS + After=network-online.target + Wants=network-online.target + ConditionPathExists=!/opt/mesosphere/ + [Service] + Type=oneshot + ExecStartPre=/usr/bin/bash -c 'until wget --progress=dot -e dotbytes=10M --continue ${bootstrap_repo_root}/bootstrap.tar.xz -O /tmp/bootstrap.tar.xz; do echo "failed to download"; sleep 5; done' + ExecStartPre=/usr/bin/mkdir -p /opt/mesosphere + ExecStart=/usr/bin/tar -axf /tmp/bootstrap.tar.xz -C /opt/mesosphere + - name: dcos-setup.service + command: start + enable: true + content: | + [Unit] + Description=Prep the Pkgpanda working directories for this host. + Requires=dcos-download.service + After=dcos-download.service + [Service] + Type=oneshot + EnvironmentFile=/opt/mesosphere/environment + ExecStart=/opt/mesosphere/bin/pkgpanda setup --no-block-systemd + [Install] + WantedBy=multi-user.target diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..c34b600 --- /dev/null +++ b/variables.tf @@ -0,0 +1,108 @@ +variable "aws_access_key" { + description = "AWS Access Key" +} + +variable "aws_secret_key" { + description = "AWS Secret Key" +} + +variable "aws_region" { + description = "AWS Region to launch configuration in" +} + +variable "ssh_public_key" { + description = "SSH public key to give SSH access" +} + +############################### +### CONFIGURABLE PARAMETERS ### +############################### + +variable "stack_name" { + description = "DCOS stack name" + default = "dcos" +} + +variable "slave_instance_count" { + description = "Number of slave nodes to launch" + default = 2 +} + +variable "public_slave_instance_count" { + description = "Number of public slave nodes to launch" + default = 1 +} + +variable "admin_location" { + description = "The IP range to whitelist for admin access. Must be a valid CIDR." + default = "0.0.0.0/0" +} + +################## +### PARAMETERS ### +################## + +variable "aws_availability_zone" { + description = "AWS Secret Key" + default = "eu-central-1b" +} + +variable "nat_ami" { + description = "AMI for Amazon NAT machine" + default = "ami-204c7a3d" +} + +variable "instance_ami" { + description = "AMI for CoreOS machine" + default = "ami-bececaa3" +} + +variable "master_instance_type" { + description = "Default instance type for masters" + default = "m4.large" +} + +variable "slave_instance_type" { + description = "Default instance type for slaves" + default = "m4.large" +} + +variable "public_slave_instance_type" { + description = "Default instance type for public slaves" + default = "m4.large" +} + +variable "vpc_subnet_range" { + descpiption = "The IP range of the VPC subnet" + default = "10.0.0.0/16" +} + +variable "bootstrap_repo_root" { + descpiption = "Root address of the bootstrap script" + default = "https://downloads.mesosphere.io/dcos/stable" +} + +variable "master_instance_count" { + description = "Amount of requested Masters" + default = 1 +} + +variable "master_quorum_count" { + description = "Quorum count" + default = 1 +} + +variable "private_subnet_range" { + description = "Private Subnet IP range" + default = "10.0.0.0/22" +} + +variable "public_subnet_range" { + description = "Public Subnet IP range" + default = "10.0.4.0/22" +} + +variable "fallback_dns" { + description = "Fallback DNS IP" + default = "10.0.0.2" +} diff --git a/vpc.tf b/vpc.tf new file mode 100644 index 0000000..03b6154 --- /dev/null +++ b/vpc.tf @@ -0,0 +1,11 @@ +resource "aws_vpc" "dcos" { + cidr_block = "${var.vpc_subnet_range}" + enable_dns_support = true + enable_dns_hostnames = true + + tags { + Name = "${var.stack_name}" + Application = "${var.stack_name}" + Network = "Public" + } +}