From ad85aba1c1d38f7b5b2afe088d1b20a2e9909609 Mon Sep 17 00:00:00 2001 From: Tim Park Date: Tue, 30 Dec 2014 12:04:59 -0800 Subject: [PATCH 1/6] feat(contrib/azure): add Azure provision scripts and docs --- contrib/azure/README.md | 3 + contrib/azure/azure-coreos-cluster | 246 +++++++++++++++++++++++++ contrib/azure/azure-user-data-template | 27 +++ contrib/azure/cert.conf | 14 ++ contrib/azure/create-azure-user-data | 82 +++++++++ contrib/azure/generate-mgmt-cert.sh | 6 + docs/installing_deis/azure.rst | 116 ++++++++++++ docs/installing_deis/index.rst | 1 + 8 files changed, 495 insertions(+) create mode 100644 contrib/azure/README.md create mode 100755 contrib/azure/azure-coreos-cluster create mode 100644 contrib/azure/azure-user-data-template create mode 100644 contrib/azure/cert.conf create mode 100755 contrib/azure/create-azure-user-data create mode 100755 contrib/azure/generate-mgmt-cert.sh create mode 100644 docs/installing_deis/azure.rst diff --git a/contrib/azure/README.md b/contrib/azure/README.md new file mode 100644 index 0000000000..75d58f1eae --- /dev/null +++ b/contrib/azure/README.md @@ -0,0 +1,3 @@ +# Deis on Microsoft Azure + +Please refer to the instructions at http://docs.deis.io/en/latest/installing_deis/azure/. diff --git a/contrib/azure/azure-coreos-cluster b/contrib/azure/azure-coreos-cluster new file mode 100755 index 0000000000..028a5cf5dc --- /dev/null +++ b/contrib/azure/azure-coreos-cluster @@ -0,0 +1,246 @@ +#!/usr/bin/env python + +from azure import * +from azure.servicemanagement import * +import argparse +import urllib2 +import time +import base64 +import os +import subprocess + +parser = argparse.ArgumentParser(description='Create a CoreOS cluster on Microsoft Azure.') +parser.add_argument('--version', action='version', version='azure-coreos-cluster 0.1') +parser.add_argument('cloud_service_name', + help='cloud service name') +parser.add_argument('--ssh-cert', + help='certificate file with public key for ssh, in .cer format') +parser.add_argument('--ssh-thumb', + help='thumbprint of ssh cert') +parser.add_argument('--subscription', required=True, + help='required Azure subscription id') +parser.add_argument('--azure-cert', required=True, + help='required path to Azure cert pem file') +parser.add_argument('--blob-container-url', required=True, + help='required url to blob container where vm disk images will be created, including /, ex: https://patcoreos.blob.core.windows.net/vhds/') +parser.add_argument('--vm-size', default='Small', + help='optional, VM size [Small]') +parser.add_argument('--vm-name-prefix', default='coreos', + help='optional, VM name prefix [coreos]') +parser.add_argument('--availability-set', default='coreos-as', + help='optional, name of availability set for cluster [coreos-as]') +parser.add_argument('--location', default='West US', + help='optional, [West US]') +parser.add_argument('--ssh', default=22001, type=int, + help='optional, starts with 22001 and +1 for each machine in cluster') +parser.add_argument('--coreos-image', default='2b171e93f07c4903bcad35bda10acf22__CoreOS-Beta-494.1.0', + help='optional, [2b171e93f07c4903bcad35bda10acf22__CoreOS-Beta-494.1.0]') +parser.add_argument('--num-nodes', default=3, type=int, + help='optional, number of nodes to create (or add), defaults to 3') +parser.add_argument('--virtual-network-name', + help='optional, name of an existing virtual network to which we will add the VMs') +parser.add_argument('--subnet-names', + help='optional, subnet name to which the VMs will belong') +parser.add_argument('--custom-data', + help='optional, path to your own cloud-init file') +parser.add_argument('--discovery-service-url', + help='optional, url for an existing cluster discovery service. Else we will generate one.') +parser.add_argument('--pip', action='store_true', + help='optional, assigns public instance ip addresses to each VM') +parser.add_argument('--deis', action='store_true', + help='optional, automatically opens http and controller endpoints') +parser.add_argument('--data-disk', action='store_true', + help='optional, attaches a data disk to each VM') + +cloud_init_template = """#cloud-config + +coreos: + etcd: + # generate a new token for each unique cluster from https://discovery.etcd.io/new + discovery: {0} + # deployments across multiple cloud services will need to use $public_ipv4 + addr: $private_ipv4:4001 + peer-addr: $private_ipv4:7001 + units: + - name: etcd.service + command: start + - name: fleet.service + command: start +""" + +args = parser.parse_args() + +# Create SSH cert if it's not given +if not args.ssh_cert and not args.ssh_thumb: + print 'SSH arguments not given, generating certificate' + with open(os.devnull, 'w') as shutup: + subprocess.call('openssl req -x509 -nodes -days 365 -newkey rsa:2048 -config cert.conf -keyout ssh-cert.key -out ssh-cert.pem', shell=True, stdout=shutup, stderr=shutup) + subprocess.call('chmod 600 ssh-cert.key', shell=True, stdout=shutup, stderr=shutup) + subprocess.call('openssl x509 -outform der -in ssh-cert.pem -out ssh-cert.cer', shell=True, stdout=shutup, stderr=shutup) + thumbprint = subprocess.check_output('openssl x509 -in ssh-cert.pem -sha1 -noout -fingerprint | sed s/://g', shell=True) + args.ssh_thumb = thumbprint.split('=')[1].replace('\n', '') + args.ssh_cert = './ssh-cert.cer' + print 'Generated SSH certificate with thumbprint ' + args.ssh_thumb + +# Setup custom data +if args.custom_data: + with open(args.custom_data, 'r') as f: + cloud_init = f.read() + f.closed +else: + if args.discovery_service_url: + cloud_init = cloud_init_template.format(args.discovery_service_url) + else: + response = urllib2.urlopen('https://discovery.etcd.io/new') + discovery_url = response.read() + cloud_init = cloud_init_template.format(discovery_url) + +SERVICE_CERT_FORMAT = 'pfx' + +with open(args.ssh_cert) as f: + service_cert_file_data = base64.b64encode(f.read()) +f.closed + +def wait_for_async(request_id, operation_name, timeout): + count = 0 + result = sms.get_operation_status(request_id) + while result.status == 'InProgress': + count = count + 1 + if count > timeout: + print('Timed out waiting for async operation to complete.') + return + time.sleep(5) + print('.') + result = sms.get_operation_status(request_id) + print(vars(result)) + if result.error: + print(result.error.code) + print(vars(result.error)) + print(result.status) + print(operation_name + ' took:' + str(count*5) + 's') + +def linux_config(hostname, args): + pk = PublicKey(args.ssh_thumb, + u'/home/core/.ssh/authorized_keys') + system = LinuxConfigurationSet(hostname, 'core', None, True, + custom_data=cloud_init) + system.ssh.public_keys.public_keys.append(pk) + system.disable_ssh_password_authentication = True + return system + +def endpoint_config(name, port): + endpoint = ConfigurationSetInputEndpoint(name, 'tcp', port, port, name) + load_balancer_probe = LoadBalancerProbe() + load_balancer_probe.path = 'health-check' + load_balancer_probe.port = port + load_balancer_probe.protocol = 'http' + endpoint.load_balancer_probe = load_balancer_probe + return endpoint + +def network_config(subnet_name=None, port='59913', public_ip_name=None): + network = ConfigurationSet() + network.configuration_set_type = 'NetworkConfiguration' + network.input_endpoints.input_endpoints.append( + ConfigurationSetInputEndpoint('ssh', 'tcp', port, '22')) + if subnet_name: + network.subnet_names.append(subnet_name) + if public_ip_name: + network.public_ips.public_ips.append(PublicIP(name=public_ip_name)) + if args.deis: + network.input_endpoints.input_endpoints.append(endpoint_config('http', '80')) + network.input_endpoints.input_endpoints.append(endpoint_config('deis', '2222')) + return network + +def data_hd(target_container_url, target_blob_name, target_lun, target_disk_size_in_gb): + media_link = target_container_url + target_blob_name + data_hd = DataVirtualHardDisk() + data_hd.disk_label = target_blob_name + data_hd.logical_disk_size_in_gb = target_disk_size_in_gb + data_hd.lun = target_lun + data_hd.media_link = media_link + return data_hd + +sms = ServiceManagementService(args.subscription, args.azure_cert) + +#Create the cloud service +sms.create_hosted_service( + args.cloud_service_name, label=args.cloud_service_name, location=args.location) +print('created service ' + args.cloud_service_name) +time.sleep(2) + +#upload ssh cert to cloud-service +result = sms.add_service_certificate(args.cloud_service_name, + service_cert_file_data, SERVICE_CERT_FORMAT, '') +wait_for_async(result.request_id, 'upload cert', 15) + +def get_vm_name(args, i): + return args.cloud_service_name + '-' + args.vm_name_prefix + '-' + str(i) + +vms =[] + +#Create the VMs +for i in range(args.num_nodes): + ssh_port = args.ssh +i + vm_name = get_vm_name(args, i) + if args.pip: + pip_name = vm_name + else: + pip_name = None + media_link = args.blob_container_url + vm_name + os_hd = OSVirtualHardDisk(media_link=media_link, + source_image_name=args.coreos_image) + system = linux_config(vm_name, args) + network = network_config(subnet_name=args.subnet_names, port=ssh_port, public_ip_name=pip_name) + #specifiy the data disk, important to start at lun = 0 + if args.data_disk: + data_disk = data_hd(args.blob_container_url, vm_name + '-data.vhd', 0, 25) + data_disks = DataVirtualHardDisks() + data_disks.data_virtual_hard_disks.append(data_disk) + else: + data_disks = None + + if i == 0: + result = sms.create_virtual_machine_deployment( + args.cloud_service_name, deployment_name=args.cloud_service_name, + deployment_slot='production', label=vm_name, + role_name=vm_name, system_config=system, os_virtual_hard_disk=os_hd, + role_size=args.vm_size, network_config=network, data_virtual_hard_disks=data_disks) + else: + result = sms.add_role( + args.cloud_service_name, deployment_name=args.cloud_service_name, + role_name=vm_name, + system_config=system, os_virtual_hard_disk=os_hd, + role_size=args.vm_size, network_config=network, data_virtual_hard_disks=data_disks) + wait_for_async(result.request_id, 'create VM' + vm_name, 30) + vms.append({'name':vm_name, + 'host':args.cloud_service_name + '.cloudapp.net', + 'port':ssh_port, + 'user':'core', + 'identity':args.ssh_cert.replace('.cer','.key')}) + +#get the ip addresses +def get_ips(service_name, deployment_name): + result = sms.get_deployment_by_name(service_name, deployment_name) + for instance in result.role_instance_list: + ips.append(instance.public_ips[0].address) + return ips + +#print dns config +if args.pip: + ips = [] + ips = get_ips(args.cloud_service_name, args.cloud_service_name) + print 'dns file ----' + for ip in ips: + print '@ 10800 IN A ' + ip + print '* 10800 IN CNAME @' + print 'end dns file ----' + +#print ~/.ssh/config +print '~/.ssh/config ----' +for vm in vms: + print 'Host ' + vm['name'] + print ' HostName ' + vm['host'] + print ' Port ' + str(vm['port']) + print ' User ' + vm['user'] + print ' IdentityFile ' + vm['identity'] +print 'end ~/.ssh/config ----' diff --git a/contrib/azure/azure-user-data-template b/contrib/azure/azure-user-data-template new file mode 100644 index 0000000000..8eb1eac9b9 --- /dev/null +++ b/contrib/azure/azure-user-data-template @@ -0,0 +1,27 @@ +#cloud-config +coreos: + units: + - name: format-ephemeral.service + command: start + content: | + [Unit] + Description=Formats the ephemeral drive + ConditionPathExists=!/etc/azure-formatted + [Service] + Type=oneshot + RemainAfterExit=yes + ExecStart=/usr/sbin/wipefs -f /dev/sdc + ExecStart=/usr/sbin/mkfs.btrfs -f /dev/sdc + ExecStart=/bin/touch /etc/azure-formatted + - name: var-lib-docker.mount + command: start + content: | + [Unit] + Description=Mount ephemeral to /var/lib/docker + Requires=format-ephemeral.service + After=format-ephemeral.service + Before=docker.service + [Mount] + What=/dev/sdc + Where=/var/lib/docker + Type=btrfs diff --git a/contrib/azure/cert.conf b/contrib/azure/cert.conf new file mode 100644 index 0000000000..27c58fc633 --- /dev/null +++ b/contrib/azure/cert.conf @@ -0,0 +1,14 @@ +[ req ] +prompt = no +default_bits = 2048 +encrypt_key = no +distinguished_name = req_distinguished_name + +string_mask = utf8only + +[ req_distinguished_name ] +O=My Company +L=San Francisco +ST=CA +C=US +CN=www.test.com diff --git a/contrib/azure/create-azure-user-data b/contrib/azure/create-azure-user-data new file mode 100755 index 0000000000..e126379849 --- /dev/null +++ b/contrib/azure/create-azure-user-data @@ -0,0 +1,82 @@ +#!/usr/bin/env python +""" +Create CoreOS user-data by merging contrib/coreos/user-data and azure-user-data + +Usage: create-azure-user-data.py + +Arguments: + This is the CoreOS etcd discovery URL. You should generate + a new URL at https://discovery.etcd.io/new and pass it as the argument. +""" + +import sys +import os +import re +import yaml +import collections + + +def combine_dicts(orig_dict, new_dict): + for key, val in new_dict.iteritems(): + if isinstance(val, collections.Mapping): + tmp = combine_dicts(orig_dict.get(key, {}), val) + orig_dict[key] = tmp + elif isinstance(val, list): + orig_dict[key] = (val + orig_dict[key]) + else: + orig_dict[key] = new_dict[key] + return orig_dict + + +def get_file(name, mode="r", abspath=False): + current_dir = os.path.dirname(__file__) + + if abspath: + return file(os.path.abspath(os.path.join(current_dir, name)), mode) + else: + return file(os.path.join(current_dir, name), mode) + + +def main(): + try: + url = sys.argv[1] + except (NameError, IndexError): + print __doc__ + return 1 + + url_pattern = 'http[|s]\:[\/]{2}discovery\.etcd\.io\/[0-9a-f]{32}' + + m = re.match(url_pattern, url) + + if not m: + print "Discovery URL invalid." + return 1 + + azure_user_data = get_file("azure-user-data", "w", True) + azure_template = get_file("azure-user-data-template") + coreos_template = get_file("../coreos/user-data.example") + + configuration_coreos_template = yaml.safe_load(coreos_template) + configuration_azure_template = yaml.safe_load(azure_template) + + configuration = combine_dicts(configuration_coreos_template, + configuration_azure_template) + + configuration["coreos"]["etcd"]["discovery"] = url + + for config in configuration['write_files']: + if "DOCKER_OPTS" in config['content']: + config['content'] = '[Service]\nEnvironment="DOCKER_OPTS=--insecure-registry 0.0.0.0/0"\n' + + with azure_user_data as outfile: + try: + outfile.write("#cloud-config\n\n" + yaml.dump(configuration, default_flow_style=False)) + except (IOError, ValueError): + print "There was an issue writing to file " + azure_user_data.name + return 1 + else: + print "Wrote file \"%s\" with url \"%s\"" %\ + (azure_user_data.name, url) + +if __name__ == "__main__": + sys.exit(main()) diff --git a/contrib/azure/generate-mgmt-cert.sh b/contrib/azure/generate-mgmt-cert.sh new file mode 100755 index 0000000000..05e365e966 --- /dev/null +++ b/contrib/azure/generate-mgmt-cert.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# generate management cert + +openssl req -x509 -nodes -days 365 -newkey rsa:2048 -config cert.conf -keyout azure-cert.pem -out azure-cert.pem -config cert.conf +openssl x509 -outform der -in azure-cert.pem -out azure-cert.cer diff --git a/docs/installing_deis/azure.rst b/docs/installing_deis/azure.rst new file mode 100644 index 0000000000..4e1878a37e --- /dev/null +++ b/docs/installing_deis/azure.rst @@ -0,0 +1,116 @@ +:title: Installing Deis on Microsoft Azure +:description: How to provision a multi-node Deis cluster on Microsoft Azure + +.. _deis_on_azure: + +Microsoft Azure +=============== + +This section will show you how to create a 3-node Deis cluster on Microsoft Azure. + +Before you start, :ref:`get the Deis source ` and change directory into `contrib/azure`_ while following this documentation. + + +Install Python and Azure SDK for Python +--------------------------------------- + +The cluster creation tool uses Python and the Python Azure library to create a CoreOS cluster. If you haven't already, install these on your development machine: + +.. code-block:: console + + $ brew install python + ... + + $ sudo pip install azure + ... + + +And check to make sure they are configured correctly: + +.. code-block:: console + + $ python -c "import azure; print(azure.__version__)" + 0.9.0 <-- everything working ok! + +Generate Certificates +--------------------- + +The azure-coreos-cluster creation tool uses the Azure management REST API to create the CoreOS cluster which uses a management certificate to authenticate. + +If you don't have a management certificate already configured, the script generate-mgmt-cert.sh can create this certificate for you. Otherwise, you can skip to the next section. + +If you need to create a certificate, edit cert.conf in contrib/azure with your company's details and then run: + +.. code-block:: console + + $ ./generate-mgmt-cert.sh + +Upload Management Cert +---------------------- + +If you haven't uploaded your management certificate to Azure (azure-cert.cer if you used the script in the previous section), do that now using the `management certificates tab`_ of the Azure portal's Settings. + +Also copy the Azure subscription id from this table and save it for the cluster creation script below. + +Create Cluster Cloud Config +--------------------------- + +Before we can create a cluster, we need to create a cloud config for it. The script create-azure-user-data does this for you. This script takes the stock cluster instance config in ../coreos/user-data.example and customizes it for Azure and inserts a unique cluster discovery url: + +.. code-block:: console + + $ ./create-azure-user-data $(curl -s https://discovery.etcd.io/new) + +This will create a azure-user-data cloud config file. We'll use this with the script in the next section during cluster creation. + +Create CoreOS Cluster +--------------------- + +With the management certificate and cloud config in place, we are ready to create our cluster. + +* Create a container called 'vhds' within a storage account in the same region as your cluster using the Azure portal. Note the URL of the container for the cluster creation script below. +* Choose a cloud service name for your Deis cluster for the script below. The script will automatically create this cloud service for you. + +With that, let's run the azure-coreos-cluster script which will create the CoreOS cluster. Fill in the bracketed values with the values for your deployment you created above. + +.. code-block:: console + + $ ./azure-coreos-cluster [cloud service name] + --subscription [subscription id] + --azure-cert azure-cert.pem + --num-nodes 3 + --location "West US" + --vm-size Large + --pip + --deis + --blob-container-url https://[blob container].blob.core.windows.net/vhds/ + --data-disk + --custom-data azure-user-data + +This script will by default provision a 3 node cluster but you can increase this with the --num-nodes parameter. Likewise, you can increase the vm size using the --vm-size. It is not recommended that you use smaller than Large (A3) sized instances. + +Note that for scheduling to work properly, clusters must consist of at least 3 nodes and always have an odd number of members. For more information, see `optimal etcd cluster size`_. + + +Configure DNS +------------- + +See :ref:`configure-dns` for more information on properly setting up your DNS records with Deis. + + +Install Deis Platform +--------------------- + +Now that you've finished provisioning a cluster, please refer to :ref:`install_deis_platform` to +start installing the platform. + +IMPORTANT NOTE: Once you have installed deisctl, you will need to use a customized deis-builder component for Azure since Azure uses routable IP addresses for each instance. Configure this using the following command before you run 'deisctl install platform': + +.. code-block:: console + + $ deisctl config builder set image=deis/builder:v1.1.1-azure + +.. _`management certificates tab`: https://manage.windowsazure.com/#Workspaces/AdminTasks/ListManagementCertificates +.. _`contrib/azure`: https://github.com/deis/deis/tree/master/contrib/azure +.. _`etcd`: https://github.com/coreos/etcd +.. _`optimal etcd cluster size`: https://github.com/coreos/etcd/blob/master/Documentation/optimal-cluster-size.md diff --git a/docs/installing_deis/index.rst b/docs/installing_deis/index.rst index ac25cc57ba..31c31f88bd 100644 --- a/docs/installing_deis/index.rst +++ b/docs/installing_deis/index.rst @@ -23,6 +23,7 @@ with CoreOS. quick-start system-requirements aws + azure digitalocean gce rackspace From c35f9e883e50446ed71256d32f6c1fe10147ab1d Mon Sep 17 00:00:00 2001 From: Chris Armstrong Date: Tue, 13 Jan 2015 16:26:27 -0800 Subject: [PATCH 2/6] feat(contrib/azure): clean up Azure docs and scripts --- .gitignore | 1 + contrib/azure/azure-coreos-cluster | 91 +++++++++++++++++++++--------- docs/installing_deis/azure.rst | 62 ++++++++++---------- 3 files changed, 94 insertions(+), 60 deletions(-) diff --git a/.gitignore b/.gitignore index 874ce7ae90..42cf88a725 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ builder/image/bin/yaml2json-procfile cache/image/bin client/dist/ client/makeself/ +contrib/azure/azure-user-data contrib/bumpver/bumpver deisctl/deisctl deisctl/dist/ diff --git a/contrib/azure/azure-coreos-cluster b/contrib/azure/azure-coreos-cluster index 028a5cf5dc..1835bb1934 100755 --- a/contrib/azure/azure-coreos-cluster +++ b/contrib/azure/azure-coreos-cluster @@ -85,7 +85,10 @@ if not args.ssh_cert and not args.ssh_thumb: # Setup custom data if args.custom_data: with open(args.custom_data, 'r') as f: - cloud_init = f.read() + if not os.path.exists(args.custom_data): + print "Couldn't find the user-data file. Did you remember to run `create-azure-user-data`?" + sys.exit(1) + cloud_init = f.read() f.closed else: if args.discovery_service_url: @@ -101,7 +104,7 @@ with open(args.ssh_cert) as f: service_cert_file_data = base64.b64encode(f.read()) f.closed -def wait_for_async(request_id, operation_name, timeout): +def wait_for_async(request_id, timeout): count = 0 result = sms.get_operation_status(request_id) while result.status == 'InProgress': @@ -110,14 +113,13 @@ def wait_for_async(request_id, operation_name, timeout): print('Timed out waiting for async operation to complete.') return time.sleep(5) - print('.') + print('.'), + sys.stdout.flush() result = sms.get_operation_status(request_id) - print(vars(result)) if result.error: print(result.error.code) print(vars(result.error)) - print(result.status) - print(operation_name + ' took:' + str(count*5) + 's') + print result.status + ' in ' + str(count*5) + 's' def linux_config(hostname, args): pk = PublicKey(args.ssh_thumb, @@ -163,15 +165,24 @@ def data_hd(target_container_url, target_blob_name, target_lun, target_disk_size sms = ServiceManagementService(args.subscription, args.azure_cert) #Create the cloud service -sms.create_hosted_service( - args.cloud_service_name, label=args.cloud_service_name, location=args.location) -print('created service ' + args.cloud_service_name) -time.sleep(2) +try: + print 'Creating the hosted service...', + sys.stdout.flush() + sms.create_hosted_service( + args.cloud_service_name, label=args.cloud_service_name, location=args.location) + print('Successfully created hosted service ' + args.cloud_service_name) + sys.stdout.flush() + time.sleep(2) +except WindowsAzureConflictError: + print "Hosted service {} already exists. Delete it or try again with a different name.".format(args.cloud_service_name) + sys.exit(1) #upload ssh cert to cloud-service +print 'Uploading SSH certificate...', +sys.stdout.flush() result = sms.add_service_certificate(args.cloud_service_name, service_cert_file_data, SERVICE_CERT_FORMAT, '') -wait_for_async(result.request_id, 'upload cert', 15) +wait_for_async(result.request_id, 15) def get_vm_name(args, i): return args.cloud_service_name + '-' + args.vm_name_prefix + '-' + str(i) @@ -199,19 +210,31 @@ for i in range(args.num_nodes): else: data_disks = None - if i == 0: - result = sms.create_virtual_machine_deployment( - args.cloud_service_name, deployment_name=args.cloud_service_name, - deployment_slot='production', label=vm_name, - role_name=vm_name, system_config=system, os_virtual_hard_disk=os_hd, - role_size=args.vm_size, network_config=network, data_virtual_hard_disks=data_disks) - else: - result = sms.add_role( - args.cloud_service_name, deployment_name=args.cloud_service_name, - role_name=vm_name, - system_config=system, os_virtual_hard_disk=os_hd, - role_size=args.vm_size, network_config=network, data_virtual_hard_disks=data_disks) - wait_for_async(result.request_id, 'create VM' + vm_name, 30) + try: + if i == 0: + result = sms.create_virtual_machine_deployment( + args.cloud_service_name, deployment_name=args.cloud_service_name, + deployment_slot='production', label=vm_name, + role_name=vm_name, system_config=system, os_virtual_hard_disk=os_hd, + role_size=args.vm_size, network_config=network, data_virtual_hard_disks=data_disks) + else: + result = sms.add_role( + args.cloud_service_name, deployment_name=args.cloud_service_name, + role_name=vm_name, + system_config=system, os_virtual_hard_disk=os_hd, + role_size=args.vm_size, network_config=network, data_virtual_hard_disks=data_disks) + except WindowsAzureError as e: + if "Forbidden" in str(e): + print "Unable to use this CoreOS image. This usually means a newer image has been published." + print "See https://coreos.com/docs/running-coreos/cloud-providers/azure/ for the latest stable image," + print "and supply it to this script with --coreos-image. If it works, please open a pull request to update this script." + sys.exit(1) + else: + pass + + print 'Creating VM ' + vm_name + '...', + sys.stdout.flush() + wait_for_async(result.request_id, 30) vms.append({'name':vm_name, 'host':args.cloud_service_name + '.cloudapp.net', 'port':ssh_port, @@ -229,18 +252,30 @@ def get_ips(service_name, deployment_name): if args.pip: ips = [] ips = get_ips(args.cloud_service_name, args.cloud_service_name) - print 'dns file ----' + print '' + print '-------' + print "You'll need to configure DNS records for a domain you wish to use with your Deis cluster." + print 'For convenience, the public IP addresses are printed below, along with sane DNS timeouts.' + print '' for ip in ips: print '@ 10800 IN A ' + ip print '* 10800 IN CNAME @' - print 'end dns file ----' + print '-------' + print 'For more information, see: http://docs.deis.io/en/latest/managing_deis/configure-dns/' + print '' #print ~/.ssh/config -print '~/.ssh/config ----' +print '' +print '-------' +print "Instances on Azure don't use typical SSH ports. It is recommended to configure ~/.ssh/config" +print 'so the instances can easily be referenced when logging in via SSH. For convenience, the config' +print 'directives for your instances are below:' +print '' for vm in vms: print 'Host ' + vm['name'] print ' HostName ' + vm['host'] print ' Port ' + str(vm['port']) print ' User ' + vm['user'] print ' IdentityFile ' + vm['identity'] -print 'end ~/.ssh/config ----' +print '-------' +print '' diff --git a/docs/installing_deis/azure.rst b/docs/installing_deis/azure.rst index 4e1878a37e..e1bb080b90 100644 --- a/docs/installing_deis/azure.rst +++ b/docs/installing_deis/azure.rst @@ -8,36 +8,29 @@ Microsoft Azure This section will show you how to create a 3-node Deis cluster on Microsoft Azure. -Before you start, :ref:`get the Deis source ` and change directory into `contrib/azure`_ while following this documentation. +Before you start, :ref:`get the Deis source ` and change directory into `contrib/azure`_ +while following this documentation. Install Python and Azure SDK for Python --------------------------------------- -The cluster creation tool uses Python and the Python Azure library to create a CoreOS cluster. If you haven't already, install these on your development machine: +The cluster creation tool uses Python and the Python Azure library to create a CoreOS cluster. +If you haven't already, install these on your development machine: .. code-block:: console $ brew install python - ... - - $ sudo pip install azure - ... - - -And check to make sure they are configured correctly: - -.. code-block:: console - - $ python -c "import azure; print(azure.__version__)" - 0.9.0 <-- everything working ok! + $ sudo pip install azure pyyaml Generate Certificates --------------------- -The azure-coreos-cluster creation tool uses the Azure management REST API to create the CoreOS cluster which uses a management certificate to authenticate. +The azure-coreos-cluster creation tool uses the Azure management REST API to create the CoreOS +cluster which uses a management certificate to authenticate. -If you don't have a management certificate already configured, the script generate-mgmt-cert.sh can create this certificate for you. Otherwise, you can skip to the next section. +If you don't have a management certificate already configured, the script generate-mgmt-cert.sh can +create this certificate for you. Otherwise, you can skip to the next section. If you need to create a certificate, edit cert.conf in contrib/azure with your company's details and then run: @@ -48,28 +41,35 @@ If you need to create a certificate, edit cert.conf in contrib/azure with your c Upload Management Cert ---------------------- -If you haven't uploaded your management certificate to Azure (azure-cert.cer if you used the script in the previous section), do that now using the `management certificates tab`_ of the Azure portal's Settings. +If you haven't uploaded your management certificate to Azure (azure-cert.cer if you used the script +in the previous section), do that now using the `management certificates tab`_ of the +Azure portal's Settings. Also copy the Azure subscription id from this table and save it for the cluster creation script below. Create Cluster Cloud Config --------------------------- -Before we can create a cluster, we need to create a cloud config for it. The script create-azure-user-data does this for you. This script takes the stock cluster instance config in ../coreos/user-data.example and customizes it for Azure and inserts a unique cluster discovery url: +Before we can create a cluster, we need to create a cloud config for it. The script +``create-azure-user-data`` does this for you. This script takes the stock cluster instance config +in ``../coreos/user-data.example``, customizes it for Azure, and inserts a unique cluster discovery +endpoint: .. code-block:: console $ ./create-azure-user-data $(curl -s https://discovery.etcd.io/new) -This will create a azure-user-data cloud config file. We'll use this with the script in the next section during cluster creation. +This will create a azure-user-data cloud config file. We'll use this with the script in the next +section during cluster creation. Create CoreOS Cluster --------------------- With the management certificate and cloud config in place, we are ready to create our cluster. -* Create a container called 'vhds' within a storage account in the same region as your cluster using the Azure portal. Note the URL of the container for the cluster creation script below. +* Create a container called ``vhds`` within a storage account in the same region as your cluster using the Azure portal. Note the URL of the container for the cluster creation script below. * Choose a cloud service name for your Deis cluster for the script below. The script will automatically create this cloud service for you. +* Choose an Azure `region`_ to use. Supply it in quotes with the ``--location`` parameter. The default is "West US". With that, let's run the azure-coreos-cluster script which will create the CoreOS cluster. Fill in the bracketed values with the values for your deployment you created above. @@ -77,19 +77,22 @@ With that, let's run the azure-coreos-cluster script which will create the CoreO $ ./azure-coreos-cluster [cloud service name] --subscription [subscription id] - --azure-cert azure-cert.pem + --azure-cert azure-cert.pem --num-nodes 3 - --location "West US" - --vm-size Large + --location "[location]" + --vm-size Large --pip --deis --blob-container-url https://[blob container].blob.core.windows.net/vhds/ --data-disk --custom-data azure-user-data -This script will by default provision a 3 node cluster but you can increase this with the --num-nodes parameter. Likewise, you can increase the vm size using the --vm-size. It is not recommended that you use smaller than Large (A3) sized instances. +This script will by default provision a 3 node cluster but you can increase this with the +``--num-nodes`` parameter. Likewise, you can increase the VM size using ``--vm-size``. +It is not recommended that you use smaller than Large (A3) sized instances. -Note that for scheduling to work properly, clusters must consist of at least 3 nodes and always have an odd number of members. For more information, see `optimal etcd cluster size`_. +Note that for scheduling to work properly, clusters must consist of at least 3 nodes and always +have an odd number of members. For more information, see `etcd disaster recovery`_. Configure DNS @@ -104,13 +107,8 @@ Install Deis Platform Now that you've finished provisioning a cluster, please refer to :ref:`install_deis_platform` to start installing the platform. -IMPORTANT NOTE: Once you have installed deisctl, you will need to use a customized deis-builder component for Azure since Azure uses routable IP addresses for each instance. Configure this using the following command before you run 'deisctl install platform': - -.. code-block:: console - - $ deisctl config builder set image=deis/builder:v1.1.1-azure - .. _`management certificates tab`: https://manage.windowsazure.com/#Workspaces/AdminTasks/ListManagementCertificates .. _`contrib/azure`: https://github.com/deis/deis/tree/master/contrib/azure .. _`etcd`: https://github.com/coreos/etcd -.. _`optimal etcd cluster size`: https://github.com/coreos/etcd/blob/master/Documentation/optimal-cluster-size.md +.. _`etcd disaster recovery`: https://github.com/coreos/etcd/blob/master/Documentation/admin_guide.md#disaster-recovery +.. _`region`: http://azure.microsoft.com/en-us/regions/ From 18304318bbaaa97a76bc7f4cae229829f62ad5f0 Mon Sep 17 00:00:00 2001 From: Chris Armstrong Date: Tue, 13 Jan 2015 17:45:55 -0800 Subject: [PATCH 3/6] chore(contrib/azure): bump Docker volume to 100GB to match AWS --- contrib/azure/azure-coreos-cluster | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/azure/azure-coreos-cluster b/contrib/azure/azure-coreos-cluster index 1835bb1934..5f3e35ae49 100755 --- a/contrib/azure/azure-coreos-cluster +++ b/contrib/azure/azure-coreos-cluster @@ -204,7 +204,7 @@ for i in range(args.num_nodes): network = network_config(subnet_name=args.subnet_names, port=ssh_port, public_ip_name=pip_name) #specifiy the data disk, important to start at lun = 0 if args.data_disk: - data_disk = data_hd(args.blob_container_url, vm_name + '-data.vhd', 0, 25) + data_disk = data_hd(args.blob_container_url, vm_name + '-data.vhd', 0, 100) data_disks = DataVirtualHardDisks() data_disks.data_virtual_hard_disks.append(data_disk) else: From 105fdc10600f872f1a26a86319812a82f165b0ea Mon Sep 17 00:00:00 2001 From: Chris Armstrong Date: Tue, 13 Jan 2015 17:48:06 -0800 Subject: [PATCH 4/6] ref(contrib/azure): remove --insecure-registry 0.0.0.0/0 This is not needed as of #2891. --- contrib/azure/create-azure-user-data | 4 ---- 1 file changed, 4 deletions(-) diff --git a/contrib/azure/create-azure-user-data b/contrib/azure/create-azure-user-data index e126379849..256232f398 100755 --- a/contrib/azure/create-azure-user-data +++ b/contrib/azure/create-azure-user-data @@ -64,10 +64,6 @@ def main(): configuration["coreos"]["etcd"]["discovery"] = url - for config in configuration['write_files']: - if "DOCKER_OPTS" in config['content']: - config['content'] = '[Service]\nEnvironment="DOCKER_OPTS=--insecure-registry 0.0.0.0/0"\n' - with azure_user_data as outfile: try: outfile.write("#cloud-config\n\n" + yaml.dump(configuration, default_flow_style=False)) From 9793fd506e519e8a86ebcbcf106ae7cff79a87b3 Mon Sep 17 00:00:00 2001 From: Chris Armstrong Date: Mon, 9 Feb 2015 14:36:49 -0700 Subject: [PATCH 5/6] fix(contrib/azure): fix health-check for builder --- contrib/azure/azure-coreos-cluster | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/contrib/azure/azure-coreos-cluster b/contrib/azure/azure-coreos-cluster index 5f3e35ae49..ac1d59c063 100755 --- a/contrib/azure/azure-coreos-cluster +++ b/contrib/azure/azure-coreos-cluster @@ -130,14 +130,18 @@ def linux_config(hostname, args): system.disable_ssh_password_authentication = True return system -def endpoint_config(name, port): +def endpoint_config(name, port, probe=False): endpoint = ConfigurationSetInputEndpoint(name, 'tcp', port, port, name) + if probe: + endpoint.load_balancer_probe = probe + return endpoint + +def load_balancer_probe(path, port, protocol): load_balancer_probe = LoadBalancerProbe() - load_balancer_probe.path = 'health-check' + load_balancer_probe.path = path load_balancer_probe.port = port - load_balancer_probe.protocol = 'http' - endpoint.load_balancer_probe = load_balancer_probe - return endpoint + load_balancer_probe.protocol = protocol + return load_balancer_probe def network_config(subnet_name=None, port='59913', public_ip_name=None): network = ConfigurationSet() @@ -149,8 +153,10 @@ def network_config(subnet_name=None, port='59913', public_ip_name=None): if public_ip_name: network.public_ips.public_ips.append(PublicIP(name=public_ip_name)) if args.deis: - network.input_endpoints.input_endpoints.append(endpoint_config('http', '80')) - network.input_endpoints.input_endpoints.append(endpoint_config('deis', '2222')) + # create web endpoint with probe checking /health-check + network.input_endpoints.input_endpoints.append(endpoint_config('web', '80', load_balancer_probe('/health-check', '80', 'http'))) + # create builder endpoint with no health check + network.input_endpoints.input_endpoints.append(endpoint_config('builder', '2222', load_balancer_probe(None, '2222', 'tcp'))) return network def data_hd(target_container_url, target_blob_name, target_lun, target_disk_size_in_gb): From 4c02b5b27779bde062e82806e8016006690d622e Mon Sep 17 00:00:00 2001 From: Chris Armstrong Date: Fri, 6 Feb 2015 15:57:42 -0800 Subject: [PATCH 6/6] chore(contrib/azure): bump to CoreOS 522.6.0 --- contrib/azure/azure-coreos-cluster | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/azure/azure-coreos-cluster b/contrib/azure/azure-coreos-cluster index ac1d59c063..8f1bf9c75f 100755 --- a/contrib/azure/azure-coreos-cluster +++ b/contrib/azure/azure-coreos-cluster @@ -33,8 +33,8 @@ parser.add_argument('--location', default='West US', help='optional, [West US]') parser.add_argument('--ssh', default=22001, type=int, help='optional, starts with 22001 and +1 for each machine in cluster') -parser.add_argument('--coreos-image', default='2b171e93f07c4903bcad35bda10acf22__CoreOS-Beta-494.1.0', - help='optional, [2b171e93f07c4903bcad35bda10acf22__CoreOS-Beta-494.1.0]') +parser.add_argument('--coreos-image', default='2b171e93f07c4903bcad35bda10acf22__CoreOS-Stable-522.6.0', + help='optional, [2b171e93f07c4903bcad35bda10acf22__CoreOS-Stable-522.6.0]') parser.add_argument('--num-nodes', default=3, type=int, help='optional, number of nodes to create (or add), defaults to 3') parser.add_argument('--virtual-network-name',