Skip to content

Commit 908ac22

Browse files
author
chenyunfei
committed
init
0 parents  commit 908ac22

File tree

215 files changed

+6586
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

215 files changed

+6586
-0
lines changed

.gitignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
.idea/
2+
.vagrant/
3+
.DS_Store
4+
*.swp
5+
*.pyc
6+
env/
7+
lain_box/centos7
8+
lain_box/lain.box
9+
.ropeproject/
10+
examples/hello/Dockerfile
11+
playbooks/roles/binary/files/*
12+
*/**/.gitignore
13+
**/.coverage
14+
/site/
15+
**/unittest.xml
16+
**/htmlcov
17+
*.gz
18+
*.bz2
19+
*.tar
20+
**/build
21+
**/dist
22+
**/*.egg-info
23+
venv/

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2016 LAIN Cloud
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# LAIN
2+
3+
Lain 是一个基于 docker 的 PaaS 系统。
4+
5+
其面向技术栈多样寻求高效运维方案的高速发展中的组织,devops 人力缺乏的 startup ,个人开发者。
6+
7+
统一高效的开发工作流,降低应用运维复杂度;在 IaaS / 私有 IDC 裸机的基础上直接提供应用开发,集成,部署,运维的一揽子解决方案。
8+
9+
设计目标包括但不限于:
10+
11+
- 降低系统管理复杂度
12+
- 简化服务的部署管理
13+
- 优化基础服务的调配
14+
- 提高资源的使用效率
15+
- 统一开发测试生产三环境
16+
- 持续交付工作流的良好支持
17+
18+
## Getting started
19+
20+
[请阅读文档](https://laincloud.gitbooks.io/white-paper/content/)
21+
22+
## Contributors
23+
24+
- @[hongqn](https://github.com/hongqn)
25+
- @[mijia](https://github.com/mijia)
26+
- @[flex](https://github.com/frostynova)
27+
- @[Tachikoma](https://github.com/sunyi00)
28+
- @[cloudfly](https://github.com/cloudfly)
29+
- @[BaiJian](https://github.com/ericpai)
30+
- @[Pan Li](https://github.com/panli889)
31+
- @[Meng Wenbin](https://github.com/supermeng)
32+
- @[chaoyiwang](https://github.com/wchaoyi)
33+
- @[Zhuoyun Wei](https://github.com/wzyboy)
34+
- @[xtao](https://github.com/xtao)
35+
36+
## LICENSE
37+
38+
Lain is released under the [MIT license](LICENSE).

Vagrantfile

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# vim: ft=ruby:
2+
3+
Vagrant.configure(2) do |config|
4+
5+
(1..3).each do |i|
6+
nodename = "node#{i}"
7+
8+
config.vm.define nodename do |node|
9+
10+
node.vm.box = "laincloud/centos-lain"
11+
node.vm.hostname = nodename
12+
13+
node.vm.provider "virtualbox" do |v|
14+
v.memory = i == 1 ? 1536 : 512
15+
end
16+
17+
node.vm.network "private_network", ip: "192.168.77.2#{i}"
18+
19+
end
20+
21+
end
22+
23+
end

bootstrap

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
#!/usr/bin/env python
2+
# coding: utf-8
3+
4+
import os
5+
import sys
6+
import optparse
7+
from subprocess import check_call, call, check_output, CalledProcessError, Popen, PIPE, STDOUT
8+
from distutils.spawn import find_executable as which
9+
from uuid import uuid4
10+
import socket
11+
import json
12+
13+
try:
14+
from shlex import quote as shellquote
15+
except ImportError:
16+
from pipes import quote as shellquote
17+
18+
LAIN_VERSION = "2.0.0"
19+
20+
here = os.path.abspath(os.path.dirname(__file__))
21+
22+
23+
class QuitInstallation(Exception):
24+
pass
25+
26+
27+
def main():
28+
parser = optparse.OptionParser()
29+
parser.add_option('-d', '--download-only', action='store_true',
30+
help="Download docker images and export to a tarball")
31+
parser.add_option('--delete-etcd', action='store_true',
32+
help="Delete existing etcd data")
33+
parser.add_option('-k', '--keep-etcd', action='store_true',
34+
help="Do not delete existing etcd data")
35+
parser.add_option('-r', '--registry-bootstrap',
36+
help="use a local docker registry for bootstrap"
37+
"(e.g. http://registry.example.com:5001)")
38+
parser.add_option('-m', '--registry-mirror',
39+
help="use a docker registry mirror "
40+
"(e.g. http://registry.example.com:5000)")
41+
saved_images = os.path.join(here, 'images-%s.tar.xz' % LAIN_VERSION)
42+
if not os.path.exists(saved_images):
43+
saved_images = None
44+
parser.add_option('--saved-images', default=saved_images,
45+
help="Path to the saved images archive, which is downloaded using "
46+
"--download-only option. "
47+
"[default: images-LAIN_VERSION.tar.xz if exists]")
48+
parser.add_option('-e', '--extra-vars', action='append',
49+
default=[],
50+
help="extra variables to be sent to ansible")
51+
default_node_name = socket.gethostname()
52+
parser.add_option('-n', '--node-name',
53+
default=default_node_name,
54+
help="name used in etcd cluster, should be cluster-wide "
55+
"unique. [default: %s]" % default_node_name)
56+
parser.add_option('--node-ip',
57+
help="host ip used to communicate with cluster [default: auto detect]")
58+
parser.add_option('--node-network',
59+
help="host network subnet [default: auto default]")
60+
parser.add_option('--vip',
61+
help="Floating IP for external visiting")
62+
parser.add_option('--domain', default='lain.local',
63+
help="Default domain for apps in the cluster [default: lain.local]")
64+
parser.add_option('--net-interface',
65+
help="network interface to communicate with cluster [default: auto detect]")
66+
parser.add_option('-v', '--verbose', action='store_true',
67+
help="output verbose logs")
68+
parser.add_option('--tag',
69+
help="run only selected tasks (debug only)")
70+
parser.add_option('--extra-roles', action='append', default=[],
71+
help="Extra ansible roles to apply besides node role")
72+
parser.add_option('--docker-device', default="",
73+
help="The block device used for docker's devicemapper storage."
74+
"docker will run on loop-lvm if this is not given, but loop-lvm is not proposed")
75+
parser.add_option('--ipip', action='store_true',
76+
help="calico using ip tunnel")
77+
options, args = parser.parse_args()
78+
79+
if options.download_only:
80+
download(options)
81+
return 0
82+
83+
if os.getuid() != 0:
84+
error("Run bootstrap script with root privilege please.")
85+
return 1
86+
87+
try:
88+
bootstrap(options)
89+
except CalledProcessError:
90+
error("Failed to install lain")
91+
return 1
92+
except QuitInstallation as e:
93+
error("Quit installation: %s", e.message)
94+
return 1
95+
96+
97+
def download(options):
98+
f = open(os.path.join(here, 'playbooks', 'roles', 'bootstrap-images', 'vars', 'main.yaml'))
99+
images = '\n'.join(line for line in f.readlines() if not line.startswith('#'))
100+
images = json.loads(images)['bootstrap_images']
101+
for image in images.values():
102+
retval = call(['docker', 'inspect', image], stdout=open('/dev/null', 'w'))
103+
if retval == 0:
104+
# image exists
105+
continue
106+
info("Pull %s", image)
107+
if options.registry_bootstrap:
108+
alt_image = '%s/%s' % (options.registry_bootstrap, image)
109+
check_call(['docker', 'pull', alt_image])
110+
check_call(['docker', 'tag', '-f', alt_image, image])
111+
else:
112+
check_call(['docker', 'pull', image])
113+
114+
tarball = 'images-%s.tar.xz' % LAIN_VERSION
115+
info("Save and compress %s (this may take a long time)", tarball)
116+
p1 = Popen(['docker', 'save'] + list(images.values()), stdout=PIPE)
117+
# use parallel version of compresser if available
118+
xz = 'pxz' if which('pxz') else 'xz'
119+
p2 = Popen([xz], stdin=p1.stdout, stdout=open(tarball, 'wb'))
120+
if p1.wait() != 0:
121+
raise Exception("save images failed")
122+
if p2.wait() != 0:
123+
raise Exception("compress images failed")
124+
125+
126+
def bootstrap(options):
127+
install_ansible(options)
128+
exec_prepare()
129+
apply_bootstrap_playbook(options)
130+
apply_site_playbook(options)
131+
132+
info("You can run the following command regularly to ensure the cluster's state: \n"
133+
" ansible-playbook -i playbooks/cluster playbooks/site.yaml")
134+
135+
136+
def exec_prepare():
137+
os.chdir(here)
138+
check_call(['bash', 'prepare.sh'])
139+
140+
141+
def install_ansible(options):
142+
if not which('ansible-playbook'):
143+
info("Installing ansible...")
144+
145+
# pycrypto, if installed, causes python-crypto package fail to install
146+
if _is_pip_package_installed('pycrypto'):
147+
warn("pycrypto, if installed via pip, is conflict "
148+
"with CentOS's python-crypto package. Remove it.")
149+
call('pip uninstall -y pycrypto', shell=True)
150+
151+
check_call('yum install -y epel-release', shell=True)
152+
# ansible 2.0 hang when starting
153+
check_call('yum install -y "ansible < 2.0"', shell=True)
154+
155+
# Install customized plugins, eg. show timestamp for tasks
156+
_install_ansible_plugins(options)
157+
158+
159+
def _install_ansible_plugins(options):
160+
run_playbook('bootstrap-hosts', 'bootstrap.yaml', options,
161+
role='ansible_plugins')
162+
163+
164+
def _is_pip_package_installed(package):
165+
return which('pip') and \
166+
call(
167+
['pip', 'show', package],
168+
stdout=open('/dev/null', 'w'),
169+
stderr=STDOUT) == 0
170+
171+
172+
def apply_bootstrap_playbook(options):
173+
if etcd_data_exists():
174+
if options.keep_etcd:
175+
delete_existing_etcd = False
176+
elif options.delete_etcd:
177+
delete_existing_etcd = True
178+
else:
179+
delete_existing_etcd = yes_or_no(
180+
"Existing etcd data detected. Is it okay to delete them?",
181+
default='no', color=_yellow)
182+
else:
183+
delete_existing_etcd = True
184+
185+
try:
186+
cluster_token = check_output([
187+
'etcdctl', 'get', '/lain/config/etcd_cluster_token'])
188+
except (OSError, CalledProcessError):
189+
cluster_token = uuid4()
190+
191+
run_playbook(
192+
'bootstrap-hosts', 'bootstrap.yaml', options,
193+
delete_existing_etcd='yes' if delete_existing_etcd else 'no',
194+
cluster_token=cluster_token,
195+
registry_mirror=options.registry_mirror,
196+
registry_bootstrap=options.registry_bootstrap,
197+
saved_images=options.saved_images,
198+
node_name=options.node_name,
199+
node_ip=options.node_ip,
200+
node_network=options.node_network,
201+
vip=options.vip,
202+
domain=options.domain,
203+
net_interface=options.net_interface,
204+
allow_restart_docker='yes',
205+
bootstrapping='yes',
206+
target='bootstrap-node',
207+
docker_device=options.docker_device,
208+
calico_ipip='yes' if options.ipip else 'no',
209+
)
210+
211+
212+
def apply_site_playbook(options):
213+
run_playbook('cluster', 'site.yaml', options)
214+
215+
216+
def etcd_data_exists():
217+
return os.path.exists('/var/etcd')
218+
219+
220+
def yes_or_no(prompt, default='yes', color=None):
221+
if default not in ('yes', 'no'):
222+
raise Exception("default must be either yes or no")
223+
question = '(Y/n)' if default == 'yes' else '(y/N)'
224+
text = '%s %s ' % (prompt, question)
225+
if color:
226+
text = color(text)
227+
while True:
228+
answer = raw_input(text)
229+
if not answer:
230+
return default == 'yes'
231+
if answer.lower() in ('y', 'yes'):
232+
return True
233+
elif answer.lower() in ('n', 'no'):
234+
return False
235+
print("Please input yes or no")
236+
237+
238+
def run_playbook(inventory, playbook, options, **extra_vars):
239+
cmd = ['ansible-playbook', '-i', os.path.join(here, 'playbooks', inventory)]
240+
if options.verbose:
241+
cmd += ['-vvvv']
242+
243+
if options.tag:
244+
cmd += ['-t', options.tag]
245+
246+
for k, v in extra_vars.items():
247+
if v:
248+
cmd += ['-e', '%s=%s' % (k, v)]
249+
for ev in options.extra_vars:
250+
cmd += ['-e', ev]
251+
252+
cmd += [os.path.join(here, 'playbooks', playbook)]
253+
info(' '.join(shellquote(x) for x in cmd))
254+
check_call(cmd)
255+
256+
257+
def info(pattern, *args):
258+
print(_green(">>> " + pattern % args))
259+
260+
261+
def error(pattern, *args):
262+
print(_red(">>> " + pattern % args, True))
263+
264+
265+
def warn(pattern, *args):
266+
print(_yellow(">>> " + pattern % args, True))
267+
268+
269+
def _colorize(code):
270+
def _(text, bold=False):
271+
c = code
272+
if bold:
273+
c = '1;%s' % c
274+
return '\033[%sm%s\033[0m' % (c, text)
275+
return _
276+
277+
_red = _colorize('31')
278+
_green = _colorize('32')
279+
_yellow = _colorize('33')
280+
281+
282+
if __name__ == '__main__':
283+
sys.exit(main())

0 commit comments

Comments
 (0)