Skip to content

Commit 22003ba

Browse files
committed
Merge pull request #2893 from carmstrong/pr-2824
feat(contrib): add Microsoft Azure provision scripts and documentation
2 parents f030781 + 4c02b5b commit 22003ba

9 files changed

Lines changed: 531 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ builder/image/bin/yaml2json-procfile
3434
cache/image/bin
3535
client/dist/
3636
client/makeself/
37+
contrib/azure/azure-user-data
3738
contrib/bumpver/bumpver
3839
deisctl/deisctl
3940
deisctl/dist/

contrib/azure/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Deis on Microsoft Azure
2+
3+
Please refer to the instructions at http://docs.deis.io/en/latest/installing_deis/azure/.

contrib/azure/azure-coreos-cluster

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
#!/usr/bin/env python
2+
3+
from azure import *
4+
from azure.servicemanagement import *
5+
import argparse
6+
import urllib2
7+
import time
8+
import base64
9+
import os
10+
import subprocess
11+
12+
parser = argparse.ArgumentParser(description='Create a CoreOS cluster on Microsoft Azure.')
13+
parser.add_argument('--version', action='version', version='azure-coreos-cluster 0.1')
14+
parser.add_argument('cloud_service_name',
15+
help='cloud service name')
16+
parser.add_argument('--ssh-cert',
17+
help='certificate file with public key for ssh, in .cer format')
18+
parser.add_argument('--ssh-thumb',
19+
help='thumbprint of ssh cert')
20+
parser.add_argument('--subscription', required=True,
21+
help='required Azure subscription id')
22+
parser.add_argument('--azure-cert', required=True,
23+
help='required path to Azure cert pem file')
24+
parser.add_argument('--blob-container-url', required=True,
25+
help='required url to blob container where vm disk images will be created, including /, ex: https://patcoreos.blob.core.windows.net/vhds/')
26+
parser.add_argument('--vm-size', default='Small',
27+
help='optional, VM size [Small]')
28+
parser.add_argument('--vm-name-prefix', default='coreos',
29+
help='optional, VM name prefix [coreos]')
30+
parser.add_argument('--availability-set', default='coreos-as',
31+
help='optional, name of availability set for cluster [coreos-as]')
32+
parser.add_argument('--location', default='West US',
33+
help='optional, [West US]')
34+
parser.add_argument('--ssh', default=22001, type=int,
35+
help='optional, starts with 22001 and +1 for each machine in cluster')
36+
parser.add_argument('--coreos-image', default='2b171e93f07c4903bcad35bda10acf22__CoreOS-Stable-522.6.0',
37+
help='optional, [2b171e93f07c4903bcad35bda10acf22__CoreOS-Stable-522.6.0]')
38+
parser.add_argument('--num-nodes', default=3, type=int,
39+
help='optional, number of nodes to create (or add), defaults to 3')
40+
parser.add_argument('--virtual-network-name',
41+
help='optional, name of an existing virtual network to which we will add the VMs')
42+
parser.add_argument('--subnet-names',
43+
help='optional, subnet name to which the VMs will belong')
44+
parser.add_argument('--custom-data',
45+
help='optional, path to your own cloud-init file')
46+
parser.add_argument('--discovery-service-url',
47+
help='optional, url for an existing cluster discovery service. Else we will generate one.')
48+
parser.add_argument('--pip', action='store_true',
49+
help='optional, assigns public instance ip addresses to each VM')
50+
parser.add_argument('--deis', action='store_true',
51+
help='optional, automatically opens http and controller endpoints')
52+
parser.add_argument('--data-disk', action='store_true',
53+
help='optional, attaches a data disk to each VM')
54+
55+
cloud_init_template = """#cloud-config
56+
57+
coreos:
58+
etcd:
59+
# generate a new token for each unique cluster from https://discovery.etcd.io/new
60+
discovery: {0}
61+
# deployments across multiple cloud services will need to use $public_ipv4
62+
addr: $private_ipv4:4001
63+
peer-addr: $private_ipv4:7001
64+
units:
65+
- name: etcd.service
66+
command: start
67+
- name: fleet.service
68+
command: start
69+
"""
70+
71+
args = parser.parse_args()
72+
73+
# Create SSH cert if it's not given
74+
if not args.ssh_cert and not args.ssh_thumb:
75+
print 'SSH arguments not given, generating certificate'
76+
with open(os.devnull, 'w') as shutup:
77+
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)
78+
subprocess.call('chmod 600 ssh-cert.key', shell=True, stdout=shutup, stderr=shutup)
79+
subprocess.call('openssl x509 -outform der -in ssh-cert.pem -out ssh-cert.cer', shell=True, stdout=shutup, stderr=shutup)
80+
thumbprint = subprocess.check_output('openssl x509 -in ssh-cert.pem -sha1 -noout -fingerprint | sed s/://g', shell=True)
81+
args.ssh_thumb = thumbprint.split('=')[1].replace('\n', '')
82+
args.ssh_cert = './ssh-cert.cer'
83+
print 'Generated SSH certificate with thumbprint ' + args.ssh_thumb
84+
85+
# Setup custom data
86+
if args.custom_data:
87+
with open(args.custom_data, 'r') as f:
88+
if not os.path.exists(args.custom_data):
89+
print "Couldn't find the user-data file. Did you remember to run `create-azure-user-data`?"
90+
sys.exit(1)
91+
cloud_init = f.read()
92+
f.closed
93+
else:
94+
if args.discovery_service_url:
95+
cloud_init = cloud_init_template.format(args.discovery_service_url)
96+
else:
97+
response = urllib2.urlopen('https://discovery.etcd.io/new')
98+
discovery_url = response.read()
99+
cloud_init = cloud_init_template.format(discovery_url)
100+
101+
SERVICE_CERT_FORMAT = 'pfx'
102+
103+
with open(args.ssh_cert) as f:
104+
service_cert_file_data = base64.b64encode(f.read())
105+
f.closed
106+
107+
def wait_for_async(request_id, timeout):
108+
count = 0
109+
result = sms.get_operation_status(request_id)
110+
while result.status == 'InProgress':
111+
count = count + 1
112+
if count > timeout:
113+
print('Timed out waiting for async operation to complete.')
114+
return
115+
time.sleep(5)
116+
print('.'),
117+
sys.stdout.flush()
118+
result = sms.get_operation_status(request_id)
119+
if result.error:
120+
print(result.error.code)
121+
print(vars(result.error))
122+
print result.status + ' in ' + str(count*5) + 's'
123+
124+
def linux_config(hostname, args):
125+
pk = PublicKey(args.ssh_thumb,
126+
u'/home/core/.ssh/authorized_keys')
127+
system = LinuxConfigurationSet(hostname, 'core', None, True,
128+
custom_data=cloud_init)
129+
system.ssh.public_keys.public_keys.append(pk)
130+
system.disable_ssh_password_authentication = True
131+
return system
132+
133+
def endpoint_config(name, port, probe=False):
134+
endpoint = ConfigurationSetInputEndpoint(name, 'tcp', port, port, name)
135+
if probe:
136+
endpoint.load_balancer_probe = probe
137+
return endpoint
138+
139+
def load_balancer_probe(path, port, protocol):
140+
load_balancer_probe = LoadBalancerProbe()
141+
load_balancer_probe.path = path
142+
load_balancer_probe.port = port
143+
load_balancer_probe.protocol = protocol
144+
return load_balancer_probe
145+
146+
def network_config(subnet_name=None, port='59913', public_ip_name=None):
147+
network = ConfigurationSet()
148+
network.configuration_set_type = 'NetworkConfiguration'
149+
network.input_endpoints.input_endpoints.append(
150+
ConfigurationSetInputEndpoint('ssh', 'tcp', port, '22'))
151+
if subnet_name:
152+
network.subnet_names.append(subnet_name)
153+
if public_ip_name:
154+
network.public_ips.public_ips.append(PublicIP(name=public_ip_name))
155+
if args.deis:
156+
# create web endpoint with probe checking /health-check
157+
network.input_endpoints.input_endpoints.append(endpoint_config('web', '80', load_balancer_probe('/health-check', '80', 'http')))
158+
# create builder endpoint with no health check
159+
network.input_endpoints.input_endpoints.append(endpoint_config('builder', '2222', load_balancer_probe(None, '2222', 'tcp')))
160+
return network
161+
162+
def data_hd(target_container_url, target_blob_name, target_lun, target_disk_size_in_gb):
163+
media_link = target_container_url + target_blob_name
164+
data_hd = DataVirtualHardDisk()
165+
data_hd.disk_label = target_blob_name
166+
data_hd.logical_disk_size_in_gb = target_disk_size_in_gb
167+
data_hd.lun = target_lun
168+
data_hd.media_link = media_link
169+
return data_hd
170+
171+
sms = ServiceManagementService(args.subscription, args.azure_cert)
172+
173+
#Create the cloud service
174+
try:
175+
print 'Creating the hosted service...',
176+
sys.stdout.flush()
177+
sms.create_hosted_service(
178+
args.cloud_service_name, label=args.cloud_service_name, location=args.location)
179+
print('Successfully created hosted service ' + args.cloud_service_name)
180+
sys.stdout.flush()
181+
time.sleep(2)
182+
except WindowsAzureConflictError:
183+
print "Hosted service {} already exists. Delete it or try again with a different name.".format(args.cloud_service_name)
184+
sys.exit(1)
185+
186+
#upload ssh cert to cloud-service
187+
print 'Uploading SSH certificate...',
188+
sys.stdout.flush()
189+
result = sms.add_service_certificate(args.cloud_service_name,
190+
service_cert_file_data, SERVICE_CERT_FORMAT, '')
191+
wait_for_async(result.request_id, 15)
192+
193+
def get_vm_name(args, i):
194+
return args.cloud_service_name + '-' + args.vm_name_prefix + '-' + str(i)
195+
196+
vms =[]
197+
198+
#Create the VMs
199+
for i in range(args.num_nodes):
200+
ssh_port = args.ssh +i
201+
vm_name = get_vm_name(args, i)
202+
if args.pip:
203+
pip_name = vm_name
204+
else:
205+
pip_name = None
206+
media_link = args.blob_container_url + vm_name
207+
os_hd = OSVirtualHardDisk(media_link=media_link,
208+
source_image_name=args.coreos_image)
209+
system = linux_config(vm_name, args)
210+
network = network_config(subnet_name=args.subnet_names, port=ssh_port, public_ip_name=pip_name)
211+
#specifiy the data disk, important to start at lun = 0
212+
if args.data_disk:
213+
data_disk = data_hd(args.blob_container_url, vm_name + '-data.vhd', 0, 100)
214+
data_disks = DataVirtualHardDisks()
215+
data_disks.data_virtual_hard_disks.append(data_disk)
216+
else:
217+
data_disks = None
218+
219+
try:
220+
if i == 0:
221+
result = sms.create_virtual_machine_deployment(
222+
args.cloud_service_name, deployment_name=args.cloud_service_name,
223+
deployment_slot='production', label=vm_name,
224+
role_name=vm_name, system_config=system, os_virtual_hard_disk=os_hd,
225+
role_size=args.vm_size, network_config=network, data_virtual_hard_disks=data_disks)
226+
else:
227+
result = sms.add_role(
228+
args.cloud_service_name, deployment_name=args.cloud_service_name,
229+
role_name=vm_name,
230+
system_config=system, os_virtual_hard_disk=os_hd,
231+
role_size=args.vm_size, network_config=network, data_virtual_hard_disks=data_disks)
232+
except WindowsAzureError as e:
233+
if "Forbidden" in str(e):
234+
print "Unable to use this CoreOS image. This usually means a newer image has been published."
235+
print "See https://coreos.com/docs/running-coreos/cloud-providers/azure/ for the latest stable image,"
236+
print "and supply it to this script with --coreos-image. If it works, please open a pull request to update this script."
237+
sys.exit(1)
238+
else:
239+
pass
240+
241+
print 'Creating VM ' + vm_name + '...',
242+
sys.stdout.flush()
243+
wait_for_async(result.request_id, 30)
244+
vms.append({'name':vm_name,
245+
'host':args.cloud_service_name + '.cloudapp.net',
246+
'port':ssh_port,
247+
'user':'core',
248+
'identity':args.ssh_cert.replace('.cer','.key')})
249+
250+
#get the ip addresses
251+
def get_ips(service_name, deployment_name):
252+
result = sms.get_deployment_by_name(service_name, deployment_name)
253+
for instance in result.role_instance_list:
254+
ips.append(instance.public_ips[0].address)
255+
return ips
256+
257+
#print dns config
258+
if args.pip:
259+
ips = []
260+
ips = get_ips(args.cloud_service_name, args.cloud_service_name)
261+
print ''
262+
print '-------'
263+
print "You'll need to configure DNS records for a domain you wish to use with your Deis cluster."
264+
print 'For convenience, the public IP addresses are printed below, along with sane DNS timeouts.'
265+
print ''
266+
for ip in ips:
267+
print '@ 10800 IN A ' + ip
268+
print '* 10800 IN CNAME @'
269+
print '-------'
270+
print 'For more information, see: http://docs.deis.io/en/latest/managing_deis/configure-dns/'
271+
print ''
272+
273+
#print ~/.ssh/config
274+
print ''
275+
print '-------'
276+
print "Instances on Azure don't use typical SSH ports. It is recommended to configure ~/.ssh/config"
277+
print 'so the instances can easily be referenced when logging in via SSH. For convenience, the config'
278+
print 'directives for your instances are below:'
279+
print ''
280+
for vm in vms:
281+
print 'Host ' + vm['name']
282+
print ' HostName ' + vm['host']
283+
print ' Port ' + str(vm['port'])
284+
print ' User ' + vm['user']
285+
print ' IdentityFile ' + vm['identity']
286+
print '-------'
287+
print ''
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#cloud-config
2+
coreos:
3+
units:
4+
- name: format-ephemeral.service
5+
command: start
6+
content: |
7+
[Unit]
8+
Description=Formats the ephemeral drive
9+
ConditionPathExists=!/etc/azure-formatted
10+
[Service]
11+
Type=oneshot
12+
RemainAfterExit=yes
13+
ExecStart=/usr/sbin/wipefs -f /dev/sdc
14+
ExecStart=/usr/sbin/mkfs.btrfs -f /dev/sdc
15+
ExecStart=/bin/touch /etc/azure-formatted
16+
- name: var-lib-docker.mount
17+
command: start
18+
content: |
19+
[Unit]
20+
Description=Mount ephemeral to /var/lib/docker
21+
Requires=format-ephemeral.service
22+
After=format-ephemeral.service
23+
Before=docker.service
24+
[Mount]
25+
What=/dev/sdc
26+
Where=/var/lib/docker
27+
Type=btrfs

contrib/azure/cert.conf

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[ req ]
2+
prompt = no
3+
default_bits = 2048
4+
encrypt_key = no
5+
distinguished_name = req_distinguished_name
6+
7+
string_mask = utf8only
8+
9+
[ req_distinguished_name ]
10+
O=My Company
11+
L=San Francisco
12+
ST=CA
13+
C=US
14+
CN=www.test.com

0 commit comments

Comments
 (0)