mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-17 08:46:40 +08:00
1343 lines
38 KiB
HTML
1343 lines
38 KiB
HTML
<!DOCTYPE html>
|
||
|
||
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
|
||
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
|
||
<head>
|
||
|
||
<head>
|
||
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||
|
||
<link rel="icon" href="/static/favicon.png">
|
||
|
||
<title>12 高级技巧之集群部署:利用 Linux 指令同时在多台机器部署程序.md.html</title>
|
||
|
||
<!-- Spectre.css framework -->
|
||
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
|
||
<!-- theme css & js -->
|
||
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
|
||
</head>
|
||
|
||
|
||
|
||
<body>
|
||
|
||
|
||
|
||
<div class="book-container">
|
||
|
||
<div class="book-sidebar">
|
||
|
||
<div class="book-brand">
|
||
|
||
<a href="/">
|
||
|
||
<img src="/static/favicon.png">
|
||
|
||
<span>技术文章摘抄</span>
|
||
|
||
</a>
|
||
|
||
</div>
|
||
|
||
<div class="book-menu uncollapsible">
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li><a href="../">上一级</a></li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
<ul class="uncollapsible">
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/00 开篇词 为什么大厂面试必考操作系统?.md.html">00 开篇词 为什么大厂面试必考操作系统?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/00 课前必读 构建知识体系,可以这样做!.md.html">00 课前必读 构建知识体系,可以这样做!.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/01 计算机是什么:“如何把程序写好”这个问题是可计算的吗?.md.html">01 计算机是什么:“如何把程序写好”这个问题是可计算的吗?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/02 程序的执行:相比 32 位,64 位的优势是什么?(上).md.html">02 程序的执行:相比 32 位,64 位的优势是什么?(上).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/03 程序的执行:相比 32 位,64 位的优势是什么?(下).md.html">03 程序的执行:相比 32 位,64 位的优势是什么?(下).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/04 构造复杂的程序:将一个递归函数转成非递归函数的通用方法.md.html">04 构造复杂的程序:将一个递归函数转成非递归函数的通用方法.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/05 存储器分级:L1 Cache 比内存和 SSD 快多少倍?.md.html">05 存储器分级:L1 Cache 比内存和 SSD 快多少倍?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/05 (1) 加餐 练习题详解(一).md.html">05 (1) 加餐 练习题详解(一).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/06 目录结构和文件管理指令:rm -rf 指令的作用是?.md.html">06 目录结构和文件管理指令:rm -rf 指令的作用是?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/07 进程、重定向和管道指令:xargs 指令的作用是?.md.html">07 进程、重定向和管道指令:xargs 指令的作用是?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/08 用户和权限管理指令: 请简述 Linux 权限划分的原则?.md.html">08 用户和权限管理指令: 请简述 Linux 权限划分的原则?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/09 Linux 中的网络指令:如何查看一个域名有哪些 NS 记录?.md.html">09 Linux 中的网络指令:如何查看一个域名有哪些 NS 记录?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/10 软件的安装: 编译安装和包管理器安装有什么优势和劣势?.md.html">10 软件的安装: 编译安装和包管理器安装有什么优势和劣势?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/11 高级技巧之日志分析:利用 Linux 指令分析 Web 日志.md.html">11 高级技巧之日志分析:利用 Linux 指令分析 Web 日志.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
<a class="current-tab" href="/专栏/重学操作系统-完/12 高级技巧之集群部署:利用 Linux 指令同时在多台机器部署程序.md.html">12 高级技巧之集群部署:利用 Linux 指令同时在多台机器部署程序.md.html</a>
|
||
|
||
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/12 (1)加餐 练习题详解(二).md.html">12 (1)加餐 练习题详解(二).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/13 操作系统内核:Linux 内核和 Windows 内核有什么区别?.md.html">13 操作系统内核:Linux 内核和 Windows 内核有什么区别?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/14 用户态和内核态:用户态线程和内核态线程有什么区别?.md.html">14 用户态和内核态:用户态线程和内核态线程有什么区别?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/15 中断和中断向量:Javajs 等语言为什么可以捕获到键盘输入?.md.html">15 中断和中断向量:Javajs 等语言为什么可以捕获到键盘输入?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/16 WinMacUnixLinux 的区别和联系:为什么 Debian 漏洞排名第一还这么多人用?.md.html">16 WinMacUnixLinux 的区别和联系:为什么 Debian 漏洞排名第一还这么多人用?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/16 (1)加餐 练习题详解(三).md.html">16 (1)加餐 练习题详解(三).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/17 进程和线程:进程的开销比线程大在了哪里?.md.html">17 进程和线程:进程的开销比线程大在了哪里?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/18 锁、信号量和分布式锁:如何控制同一时间只有 2 个线程运行?.md.html">18 锁、信号量和分布式锁:如何控制同一时间只有 2 个线程运行?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/19 乐观锁、区块链:除了上锁还有哪些并发控制方法?.md.html">19 乐观锁、区块链:除了上锁还有哪些并发控制方法?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/20 线程的调度:线程调度都有哪些方法?.md.html">20 线程的调度:线程调度都有哪些方法?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/21 哲学家就餐问题:什么情况下会触发饥饿和死锁?.md.html">21 哲学家就餐问题:什么情况下会触发饥饿和死锁?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/22 进程间通信: 进程间通信都有哪些方法?.md.html">22 进程间通信: 进程间通信都有哪些方法?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/23 分析服务的特性:我的服务应该开多少个进程、多少个线程?.md.html">23 分析服务的特性:我的服务应该开多少个进程、多少个线程?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/23 (1)加餐 练习题详解(四).md.html">23 (1)加餐 练习题详解(四).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/24 虚拟内存 :一个程序最多能使用多少内存?.md.html">24 虚拟内存 :一个程序最多能使用多少内存?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/25 内存管理单元: 什么情况下使用大内存分页?.md.html">25 内存管理单元: 什么情况下使用大内存分页?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/26 缓存置换算法: LRU 用什么数据结构实现更合理?.md.html">26 缓存置换算法: LRU 用什么数据结构实现更合理?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/27 内存回收上篇:如何解决内存的循环引用问题?.md.html">27 内存回收上篇:如何解决内存的循环引用问题?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/28 内存回收下篇:三色标记-清除算法是怎么回事?.md.html">28 内存回收下篇:三色标记-清除算法是怎么回事?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/28 (1)加餐 练习题详解(五).md.html">28 (1)加餐 练习题详解(五).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/29 Linux 下的各个目录有什么作用?.md.html">29 Linux 下的各个目录有什么作用?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/30 文件系统的底层实现:FAT、NTFS 和 Ext3 有什么区别?.md.html">30 文件系统的底层实现:FAT、NTFS 和 Ext3 有什么区别?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/31 数据库文件系统实例:MySQL 中 B 树和 B+ 树有什么区别?.md.html">31 数据库文件系统实例:MySQL 中 B 树和 B+ 树有什么区别?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/32 HDFS 介绍:分布式文件系统是怎么回事?.md.html">32 HDFS 介绍:分布式文件系统是怎么回事?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/32 (1)加餐 练习题详解(六).md.html">32 (1)加餐 练习题详解(六).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/33 互联网协议群(TCPIP):多路复用是怎么回事?.md.html">33 互联网协议群(TCPIP):多路复用是怎么回事?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/34 UDP 协议:UDP 和 TCP 相比快在哪里?.md.html">34 UDP 协议:UDP 和 TCP 相比快在哪里?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/35 Linux 的 IO 模式:selectpollepoll 有什么区别?.md.html">35 Linux 的 IO 模式:selectpollepoll 有什么区别?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/36 公私钥体系和网络安全:什么是中间人攻击?.md.html">36 公私钥体系和网络安全:什么是中间人攻击?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/36 (1)加餐 练习题详解(七).md.html">36 (1)加餐 练习题详解(七).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/37 虚拟化技术介绍:VMware 和 Docker 的区别?.md.html">37 虚拟化技术介绍:VMware 和 Docker 的区别?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/38 容器编排技术:如何利用 K8s 和 Docker Swarm 管理微服务?.md.html">38 容器编排技术:如何利用 K8s 和 Docker Swarm 管理微服务?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/39 Linux 架构优秀在哪里.md.html">39 Linux 架构优秀在哪里.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/40 商业操作系统:电商操作系统是不是一个噱头?.md.html">40 商业操作系统:电商操作系统是不是一个噱头?.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/40 (1)加餐 练习题详解(八).md.html">40 (1)加餐 练习题详解(八).md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
<li>
|
||
|
||
|
||
|
||
|
||
|
||
<a href="/专栏/重学操作系统-完/41 结束语 论程序员的发展——信仰、选择和博弈.md.html">41 结束语 论程序员的发展——信仰、选择和博弈.md.html</a>
|
||
|
||
|
||
|
||
</li>
|
||
|
||
</ul>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
|
||
|
||
<div class="sidebar-toggle-inner"></div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<script>
|
||
|
||
function add_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.add('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function remove_inner() {
|
||
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
|
||
inner.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
function sidebar_toggle() {
|
||
|
||
let sidebar_toggle = document.querySelector('.sidebar-toggle')
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let content = document.querySelector('.off-canvas-content')
|
||
|
||
if (sidebar_toggle.classList.contains('extend')) { // show
|
||
|
||
sidebar_toggle.classList.remove('extend')
|
||
|
||
sidebar.classList.remove('hide')
|
||
|
||
content.classList.remove('extend')
|
||
|
||
} else { // hide
|
||
|
||
sidebar_toggle.classList.add('extend')
|
||
|
||
sidebar.classList.add('hide')
|
||
|
||
content.classList.add('extend')
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
function open_sidebar() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.add('show')
|
||
|
||
overlay.classList.add('show')
|
||
|
||
}
|
||
|
||
function hide_canvas() {
|
||
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
|
||
sidebar.classList.remove('show')
|
||
|
||
overlay.classList.remove('show')
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
<div class="off-canvas-content">
|
||
|
||
<div class="columns">
|
||
|
||
<div class="column col-12 col-lg-12">
|
||
|
||
<div class="book-navbar">
|
||
|
||
<!-- For Responsive Layout -->
|
||
|
||
<header class="navbar">
|
||
|
||
<section class="navbar-section">
|
||
|
||
<a onclick="open_sidebar()">
|
||
|
||
<i class="icon icon-menu"></i>
|
||
|
||
</a>
|
||
|
||
</section>
|
||
|
||
</header>
|
||
|
||
</div>
|
||
|
||
<div class="book-content" style="max-width: 960px; margin: 0 auto;
|
||
|
||
overflow-x: auto;
|
||
|
||
overflow-y: hidden;">
|
||
|
||
<div class="book-post">
|
||
|
||
<p id="tip" align="center"></p>
|
||
|
||
<div><h1>12 高级技巧之集群部署:利用 Linux 指令同时在多台机器部署程序</h1>
|
||
|
||
<p>Linux 指令是由很多顶级程序员共同设计的,使用 Linux 指令解决问题的过程,就好像在体验一款优秀的产品。每次通过查资料使用 Linux 指令解决问题后,都会让我感到收获满满。在这个过程中,我不仅学会了一条指令,还从中体会到了软件设计的魅力:彼此独立,又互成一体。这就像每个 Linux 指令一样,专注、高效。回想起来,在我第一次看到管道、第一次使用 awk、第一次使用 sort,都曾有过这种感受。</p>
|
||
|
||
<p>通过前面的学习,相信你已经掌握了一些基础指令的使用方法,今天我们继续挑战一个更复杂的问题——<strong>用 Linux 指令管理一个集群</strong>。这属于 Linux 指令的高级技巧,所谓高级技巧并不是我们要学习更多的指令,而是要把之前所学的指令进行排列组合。当你从最初只能写几条指令、执行然后看结果,成长到具备书写一个拥有几十行、甚至上百行的 bash 脚本的能力时,就意味着你具备了解决复杂问题的能力。而最终的目标,是提升你对指令的熟练程度,锻炼工程能力。</p>
|
||
|
||
<p>本课时,我将带你朝着这个目标努力,通过把简单的指令组合起来,分层组织成最终的多个脚本文件,解决一个复杂的工程问题:在成百上千的集群中安装一个 Java 环境。接下来,请你带着这个目标,开启今天的学习。</p>
|
||
|
||
<h3>第一步:搭建学习用的集群</h3>
|
||
|
||
<p>第一步我们先搭建一个学习用的集群。这里简化一下模型。我在自己的电脑上装一个<code>ubuntu</code>桌面版的虚拟机,然后再装两个<code>ubuntu</code>服务器版的虚拟机。</p>
|
||
|
||
<p>相对于桌面版,服务器版对资源的消耗会少很多。我将教学材料中桌面版的<code>ubuntu</code>命名为<code>u1</code>,两个用来被管理的服务器版<code>ubuntu</code>叫作<code>v1</code>和<code>v2</code>。</p>
|
||
|
||
<p>用桌面版的原因是:我喜欢<code>ubuntu</code>漂亮的开源字体,这样会让我在给你准备素材的时候拥有一个好心情。如果你对此感兴趣,可以搜索<code>ubuntu mono</code>,尝试把这个字体安装到自己的文本编辑器中。不过我还是觉得在<code>ubuntu</code>中敲代码更有感觉。</p>
|
||
|
||
<p>注意,我在这里只用了 3 台服务器,但是接下来我们要写的脚本是可以在很多台服务器之间复用的。</p>
|
||
|
||
<h3>第二步:循环遍历 IP 列表</h3>
|
||
|
||
<p>你可以想象一个局域网中有很多服务器需要管理,它们彼此之间网络互通,我们通过一台主服务器对它们进行操作,即通过<code>u1</code>操作<code>v1</code>和<code>v2</code>。</p>
|
||
|
||
<p>在主服务器上我们维护一个<code>ip</code>地址的列表,保存成一个文件,如下图所示:</p>
|
||
|
||
<p><img src="assets/CgqCHl-GsciASqucAACaCl1bXF4240.png" alt="Drawing 0.png" /></p>
|
||
|
||
<p>目前<code>iplist</code>中只有两项,但是如果我们有足够的机器,可以在里面放成百上千项。接下来,请你思考<code>shell</code>如何遍历这些<code>ip</code>?</p>
|
||
|
||
<p>你可以先尝试实现一个最简单的程序,从文件<code>iplist</code>中读出这些<code>ip</code>并尝试用<code>for</code>循环遍历这些<code>ip</code>,具体程序如下:</p>
|
||
|
||
<pre><code>#!/usr/bin/bash
|
||
|
||
|
||
|
||
readarray -t ips < iplist
|
||
|
||
|
||
|
||
for ip in ${ips[@]}
|
||
|
||
|
||
|
||
do
|
||
|
||
|
||
|
||
echo $ip
|
||
|
||
|
||
|
||
done
|
||
|
||
</code></pre>
|
||
|
||
<p>首行的<code>#!</code>叫作 Shebang。Linux 的程序加载器会分析 Shebang 的内容,决定执行脚本的程序。这里我们希望用<code>bash</code>来执行这段程序,因为我们用到的 readarray 指令是<code>bash 4.0</code>后才增加的能力。</p>
|
||
|
||
<p><code>readarray</code>指令将 iplist 文件中的每一行读取到变量<code>ips</code>中。<code>ips</code>是一个数组,可以用<code>echo ${ips[@]}</code>打印其中全部的内容:<code>@</code>代表取数组中的全部内容;<code>$</code>符号是一个求值符号。不带<code>$</code>的话,<code>ips[@]</code>会被认为是一个字符串,而不是表达式。</p>
|
||
|
||
<p><code>for</code>循环遍历数组中的每个<code>ip</code>地址,<code>echo</code>把地址打印到屏幕上。</p>
|
||
|
||
<p>如果用<code>shell</code>执行上面的程序会报错,因为<code>readarray</code>是<code>bash 4.0</code>后支持的能力,因此我们用<code>chomd</code>为<code>foreach.sh</code>增加执行权限,然后直接利用<code>shebang</code>的能力用<code>bash</code>执行,如下图所示:</p>
|
||
|
||
<p><img src="assets/Ciqc1F-GsdSAZPtIAAF5yL5VkdQ049.png" alt="Drawing 1.png" /></p>
|
||
|
||
<h3>第三步:创建集群管理账户</h3>
|
||
|
||
<p>为了方便集群管理,通常使用统一的用户名管理集群。这个账号在所有的集群中都需要保持命名一致。比如这个集群账号的名字就叫作<code>lagou</code>。</p>
|
||
|
||
<p>接下来我们探索一下如何创建这个账户<code>lagou</code>,如下图所示:</p>
|
||
|
||
<p><img src="assets/CgqCHl-GsdqAc2khAALNpLTWENc494.png" alt="Drawing 2.png" /></p>
|
||
|
||
<p>上面我们创建了<code>lagou</code>账号,然后把<code>lagou</code>加入<code>sudo</code>分组。这样<code>lagou</code>就有了<code>sudo</code>成为<code>root</code>的能力,如下图所示:</p>
|
||
|
||
<p><img src="assets/Ciqc1F-GseCAYss5AAB9-SYXFJU693.png" alt="Drawing 3.png" /></p>
|
||
|
||
<p>接下来,我们设置<code>lagou</code>用户的初始化<code>shell</code>是<code>bash</code>,如下图所示:</p>
|
||
|
||
<p><img src="assets/CgqCHl-GsiyAGKitAACU_gkGZRI467.png" alt="Drawing 4.png" /></p>
|
||
|
||
<p>这个时候如果使用命令<code>su lagou</code>,可以切换到<code>lagou</code>账号,但是你会发现命令行没有了颜色。因此我们可以将原来用户下面的<code>.bashrc</code>文件拷贝到<code>/home/lagou</code>目录下,如下图所示:</p>
|
||
|
||
<p><img src="assets/Ciqc1F-GsjeAL_RwAAEyx32py80146.png" alt="Drawing 5.png" /></p>
|
||
|
||
<p>这样,我们就把一些自己平时用的设置拷贝了过去,包括终端颜色的设置。<code>.bashrc</code>是启动<code>bash</code>的时候会默认执行的一个脚本文件。</p>
|
||
|
||
<p>接下来,我们编辑一下<code>/etc/sudoers</code>文件,增加一行<code>lagou ALL=(ALL) NOPASSWD:ALL</code>表示<code>lagou</code>账号 sudo 时可以免去密码输入环节,如下图所示:</p>
|
||
|
||
<p><img src="assets/CgqCHl-Gsj6AQBXeAAEW0V065r0519.png" alt="Drawing 6.png" /></p>
|
||
|
||
<p>我们可以把上面的完整过程整理成指令文件,<code>create_lagou.sh</code>:</p>
|
||
|
||
<pre><code>sudo useradd -m -d /home/lagou lagou
|
||
|
||
|
||
|
||
sudo passwd lagou
|
||
|
||
|
||
|
||
sudo usermod -G sudo lagou
|
||
|
||
|
||
|
||
sudo usermod --shell /bin/bash lagou
|
||
|
||
|
||
|
||
sudo cp ~/.bashrc /home/lagou/
|
||
|
||
|
||
|
||
sudo chown lagou.lagou /home/lagou/.bashrc
|
||
|
||
|
||
|
||
sduo sh -c 'echo "lagou ALL=(ALL) NOPASSWD:ALL">>/etc/sudoers'
|
||
|
||
</code></pre>
|
||
|
||
<p>你可以删除用户<code>lagou</code>,并清理<code>/etc/sudoers</code>文件最后一行。用指令<code>userdel lagou</code>删除账户,然后执行<code>create_lagou.sh</code>重新创建回<code>lagou</code>账户。如果发现结果一致,就代表<code>create_lagou.sh</code>功能没有问题。</p>
|
||
|
||
<p>最后我们想在<code>v1``v2</code>上都执行<code>create_logou.sh</code>这个脚本。但是你不要忘记,我们的目标是让程序在成百上千台机器上传播,因此还需要一个脚本将<code>create_lagou.sh</code>拷贝到需要执行的机器上去。</p>
|
||
|
||
<p>这里,可以对<code>foreach.sh</code>稍做修改,然后分发<code>create_lagou.sh</code>文件。</p>
|
||
|
||
<p><em>foreach.sh</em></p>
|
||
|
||
<pre><code>#!/usr/bin/bash
|
||
|
||
|
||
|
||
readarray -t ips < iplist
|
||
|
||
|
||
|
||
for ip in ${ips[@]}
|
||
|
||
|
||
|
||
do
|
||
|
||
|
||
|
||
scp ~/remote/create_lagou.sh <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="f785969a85989b9bb7">[email protected]</a>$ip:~/create_lagou.sh
|
||
|
||
|
||
|
||
done
|
||
|
||
</code></pre>
|
||
|
||
<p>这里,我们在循环中用<code>scp</code>进行文件拷贝,然后分别去每台机器上执行<code>create_lagou.sh</code>。</p>
|
||
|
||
<p>如果你的机器非常多,上述过程会变得非常烦琐。你可以先带着这个问题学习下面的<code>Step 4</code>,然后再返回来重新思考这个问题,当然你也可以远程执行脚本。另外,还有一个叫作<code>sshpass</code>的工具,可以帮你把密码传递给要远程执行的指令,如果你对这块内容感兴趣,可以自己研究下这个工具。</p>
|
||
|
||
<h3>第四步: 打通集群权限</h3>
|
||
|
||
<p>接下来我们需要打通从主服务器到<code>v1</code>和<code>v2</code>的权限。当然也可以每次都用<code>ssh</code>输入用户名密码的方式登录,但这并不是长久之计。 如果我们有成百上千台服务器,输入用户名密码就成为一件繁重的工作。</p>
|
||
|
||
<p>这时候,你可以考虑利用主服务器的公钥在各个服务器间登录,避免输入密码。接下来我们聊聊具体的操作步骤:</p>
|
||
|
||
<p>首先,需要在<code>u1</code>上用<code>ssh-keygen</code>生成一个公私钥对,然后把公钥写入需要管理的每一台机器的<code>authorized_keys</code>文件中。如下图所示:我们使用<code>ssh-keygen</code>在主服务器<code>u1</code>中生成公私钥对。</p>
|
||
|
||
<p><img src="assets/CgqCHl-GslSAAUT5AATF-5rjGWU079.png" alt="Drawing 7.png" /></p>
|
||
|
||
<p>然后使用<code>mkdir -p</code>创建<code>~/.ssh</code>目录,<code>-p</code>的优势是当目录不存在时,才需要创建,且不会报错。<code>~</code>代表当前家目录。 如果文件和目录名前面带有一个<code>.</code>,就代表该文件或目录是一个需要隐藏的文件。平时用<code>ls</code>的时候,并不会查看到该文件,通常这种文件拥有特别的含义,比如<code>~/.ssh</code>目录下是对<code>ssh</code>的配置。</p>
|
||
|
||
<p>我们用<code>cd</code>切换到<code>.ssh</code>目录,然后执行<code>ssh-keygen</code>。这样会在<code>~/.ssh</code>目录中生成两个文件,<code>id_rsa.pub</code>公钥文件和<code>is_rsa</code>私钥文件。 如下图所示:</p>
|
||
|
||
<p><img src="assets/CgqCHl-GsluAWyS-AAayQyKs6NY181.png" alt="Drawing 8.png" /></p>
|
||
|
||
<p>可以看到<code>id_rsa.pub</code>文件中是加密的字符串,我们可以把这些字符串拷贝到其他机器对应用户的<code>~/.ssh/authorized_keys</code>文件中,当<code>ssh</code>登录其他机器的时候,就不用重新输入密码了。 这个传播公钥的能力,可以用一个<code>shell</code>脚本执行,这里我用<code>transfer_key.sh</code>实现。</p>
|
||
|
||
<p>我们修改一下<code>foreach.sh</code>,并写一个<code>transfer_key.sh</code>配合<code>foreach.sh</code>的工作。<code>transfer_key.sh</code>内容如下:</p>
|
||
|
||
<p><em>foreach.sh</em></p>
|
||
|
||
<pre><code>#!/usr/bin/bash
|
||
|
||
|
||
|
||
readarray -t ips < iplist
|
||
|
||
|
||
|
||
for ip in ${ips[@]}
|
||
|
||
|
||
|
||
do
|
||
|
||
|
||
|
||
sh ./transfer_key.sh $ip
|
||
|
||
|
||
|
||
done
|
||
|
||
</code></pre>
|
||
|
||
<p><em>tranfer_key.sh</em></p>
|
||
|
||
<pre><code>ip=$1
|
||
|
||
|
||
|
||
pubkey=$(cat ~/.ssh/id_rsa.pub)
|
||
|
||
|
||
|
||
echo "execute on .. $ip"
|
||
|
||
|
||
|
||
ssh <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="86eae7e1e9f3c6">[email protected]</a>$ip "
|
||
|
||
|
||
|
||
mkdir -p ~/.ssh
|
||
|
||
|
||
|
||
echo $pubkey >> ~/.ssh/authorized_keys
|
||
|
||
|
||
|
||
chmod 700 ~/.ssh
|
||
|
||
|
||
|
||
chmod 600 ~/.ssh/authorized_keys
|
||
|
||
|
||
|
||
"
|
||
|
||
</code></pre>
|
||
|
||
<p>在<code>foreach.sh</code>中我们执行 transfer_key.sh,并且将 IP 地址通过参数传递过去。在 transfer_key.sh 中,用<code>$1</code>读出 IP 地址参数, 再将公钥写入变量<code>pubkey</code>,然后登录到对应的服务器,执行多行指令。用<code>mkdir</code>指令检查<code>.ssh</code>目录,如不存在就创建这个目录。最后我们将公钥追加写入目标机器的<code>~/.ssh/authorized_keys</code>中。</p>
|
||
|
||
<p><code>chmod 700</code>和<code>chmod 600</code>是因为某些特定的<code>linux</code>版本需要<code>.ssh</code>的目录为可读写执行,<code>authorized_keys</code>文件的权限为只可读写。而为了保证安全性,组用户、所有用户都不可以访问这个文件。</p>
|
||
|
||
<p>此前,我们执行<code>foreach.sh</code>需要输入两次密码。完成上述操作后,我们再登录这两台服务器就不需要输入密码了。</p>
|
||
|
||
<p><img src="assets/CgqCHl-GsnuAC-lYAAb76OR4cFs817.png" alt="Drawing 9.png" /></p>
|
||
|
||
<p>接下来,我们尝试一下免密登录,如下图所示:</p>
|
||
|
||
<p><img src="assets/Ciqc1F-GsoGANiKlAAIjYZ8fscs878.png" alt="Drawing 10.png" /></p>
|
||
|
||
<p>可以发现,我们登录任何一台机器,都不再需要输入用户名和密码了。</p>
|
||
|
||
<h3>第五步:单机安装 Java 环境</h3>
|
||
|
||
<p>在远程部署 Java 环境之前,我们先单机完成以下 Java 环境的安装,用来收集需要执行的脚本。</p>
|
||
|
||
<p>在<code>ubuntu</code>上安装<code>java</code>环境可以直接用<code>apt</code>。</p>
|
||
|
||
<p>我们通过下面几个步骤脚本配置 Java 环境:</p>
|
||
|
||
<pre><code>sudo apt install openjdk-11-jdk
|
||
|
||
</code></pre>
|
||
|
||
<p>经过一番等待我们已经安装好了<code>java</code>,然后执行下面的脚本确认<code>java</code>安装。</p>
|
||
|
||
<pre><code>which java
|
||
|
||
|
||
|
||
java --version
|
||
|
||
</code></pre>
|
||
|
||
<p><img src="assets/Ciqc1F-GspCAJ0r9AAJx-kzES1k505.png" alt="Drawing 11.png" /></p>
|
||
|
||
<p>根据最小权限原则,执行 Java 程序我们考虑再创建一个用户<code>ujava</code>。</p>
|
||
|
||
<pre><code>sudo useradd -m -d /opt/ujava ujava
|
||
|
||
|
||
|
||
sudo usermod --shell /bin/bash lagou
|
||
|
||
</code></pre>
|
||
|
||
<p>这个用户可以不设置密码,因为我们不会真的登录到这个用户下去做任何事情。接下来我们为用户配置 Java 环境变量,如下图所示:</p>
|
||
|
||
<p><img src="assets/Ciqc1F-GsqWAa2e2AAJosZCNXpU388.png" alt="Drawing 12.png" /></p>
|
||
|
||
<p>通过两次 ls 追查,可以发现<code>java</code>可执行文件软连接到<code>/etc/alternatives/java</code>然后再次软连接到<code>/usr/lib/jvm/java-11-openjdk-amd64</code>下。</p>
|
||
|
||
<p>这样我们就可以通过下面的语句设置 JAVA_HOME 环境变量了。</p>
|
||
|
||
<pre><code>export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64/
|
||
|
||
</code></pre>
|
||
|
||
<p>Linux 的环境变量就好比全局可见的数据,这里我们使用 export 设置<code>JAVA_HOME</code>环境变量的指向。如果你想看所有的环境变量的指向,可以使用<code>env</code>指令。</p>
|
||
|
||
<p><img src="assets/CgqCHl-GsrGAMIfNAAW55Kdz1xc547.png" alt="Drawing 13.png" /></p>
|
||
|
||
<p>其中有一个环境变量比较重要,就是<code>PATH</code>。</p>
|
||
|
||
<p><img src="assets/Ciqc1F-GsriACI2JAAEtgeamQNI945.png" alt="Drawing 14.png" /></p>
|
||
|
||
<p>如上图,我们可以使用<code>shell</code>查看<code>PATH</code>的值,<code>PATH</code>中用<code>:</code>分割,每一个目录都是<code>linux</code>查找执行文件的目录。当用户在命令行输入一个命令,Linux 就会在<code>PATH</code>中寻找对应的执行文件。</p>
|
||
|
||
<p>当然我们不希望<code>JAVA_HOME</code>配置后重启一次电脑就消失,因此可以把这个环境变量加入<code>ujava</code>用户的<code>profile</code>中。这样只要发生用户登录,就有这个环境变量。</p>
|
||
|
||
<pre><code>sudo sh -c 'echo "export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64/" >> /opt/ujava/.bash_profile'
|
||
|
||
</code></pre>
|
||
|
||
<p>将<code>JAVA_HOME</code>加入<code>bash_profile</code>,这样后续远程执行<code>java</code>指令时就可以使用<code>JAVA_HOME</code>环境变量了。</p>
|
||
|
||
<p>最后,我们将上面所有的指令整理起来,形成一个<code>install_java.sh</code>。</p>
|
||
|
||
<pre><code>sudo apt -y install openjdk-11-jdk
|
||
|
||
|
||
|
||
sudo useradd -m -d /opt/ujava ujava
|
||
|
||
|
||
|
||
sudo usermod --shell /bin/bash ujava
|
||
|
||
|
||
|
||
sudo sh -c 'echo "export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64/" >> /opt/ujava/.bash_profile'
|
||
|
||
</code></pre>
|
||
|
||
<p><code>apt</code>后面增了一个<code>-y</code>是为了让执行过程不弹出确认提示。</p>
|
||
|
||
<h3>第六步:远程安装 Java 环境</h3>
|
||
|
||
<p>终于到了远程安装 Java 环境这一步,我们又需要用到<code>foreach.sh</code>。为了避免每次修改,你可以考虑允许<code>foreach.sh</code>带一个文件参数,指定需要远程执行的脚本。</p>
|
||
|
||
<p><em><strong>foreach.sh</strong></em></p>
|
||
|
||
<pre><code>#!/usr/bin/bash
|
||
|
||
|
||
|
||
readarray -t ips < iplist
|
||
|
||
|
||
|
||
script=$1
|
||
|
||
|
||
|
||
for ip in ${ips[@]}
|
||
|
||
|
||
|
||
do
|
||
|
||
|
||
|
||
ssh $ip 'bash -s' < $script
|
||
|
||
|
||
|
||
done
|
||
|
||
</code></pre>
|
||
|
||
<p>改写后的<code>foreach</code>会读取第一个执行参数作为远程执行的脚本文件。 而<code>bash -s</code>会提示使用标准输入流作为命令的输入;<code>< $script</code>负责将脚本文件内容重定向到远程<code>bash</code>的标准输入流。</p>
|
||
|
||
<p>然后我们执行<code>foreach.sh install_java.sh</code>,机器等待 1 分钟左右,在执行结束后,可以用下面这个脚本检测两个机器中的安装情况。</p>
|
||
|
||
<p><em><strong>check.sh</strong></em></p>
|
||
|
||
<pre><code>sudo -u ujava -i /bin/bash -c 'echo $JAVA_HOME'
|
||
|
||
|
||
|
||
sudo -u ujava -i java --version
|
||
|
||
</code></pre>
|
||
|
||
<p><code>check.sh</code>中我们切换到<code>ujava</code>用户去检查<code>JAVA_HOME</code>环境变量和 Java 版本。执行的结果如下图所示:</p>
|
||
|
||
<p><img src="assets/CgqCHl-GstWAFW9yAAQXx_nh6dw719.png" alt="Drawing 15.png" /></p>
|
||
|
||
<h3>总结</h3>
|
||
|
||
<p>这节课我们所讲的场景是自动化运维的一些皮毛。通过这样的场景练习,我们复习了很多之前学过的 Linux 指令。在尝试用脚本文件构建一个又一个小工具的过程中,可以发现复用很重要。</p>
|
||
|
||
<p>在工作中,优秀的工程师,总是善于积累和复用,而<code>shell</code>脚本就是积累和复用的利器。如果你第一次安装<code>java</code>环境,可以把今天的安装脚本保存在自己的笔记本中,下次再安装就能自动化完成了。除了积累和总结,另一个非常重要的就是你要尝试自己去查资料,包括使用<code>man</code>工具熟悉各种指令的使用方法,用搜索引擎查阅资料等。</p>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div>
|
||
|
||
<div style="float: left">
|
||
|
||
<a href="/专栏/重学操作系统-完/11 高级技巧之日志分析:利用 Linux 指令分析 Web 日志.md.html">上一页</a>
|
||
|
||
</div>
|
||
|
||
<div style="float: right">
|
||
|
||
<a href="/专栏/重学操作系统-完/12 (1)加餐 练习题详解(二).md.html">下一页</a>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||
|
||
</div>
|
||
|
||
<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script><script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997d623a6e3cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
|
||
|
||
</body>
|
||
|
||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||
|
||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
|
||
|
||
<script>
|
||
|
||
window.dataLayer = window.dataLayer || [];
|
||
|
||
|
||
|
||
function gtag() {
|
||
|
||
dataLayer.push(arguments);
|
||
|
||
}
|
||
|
||
|
||
|
||
gtag('js', new Date());
|
||
|
||
gtag('config', 'G-NPSEEVD756');
|
||
|
||
var path = window.location.pathname
|
||
|
||
var cookie = getCookie("lastPath");
|
||
|
||
console.log(path)
|
||
|
||
if (path.replace("/", "") === "") {
|
||
|
||
if (cookie.replace("/", "") !== "") {
|
||
|
||
console.log(cookie)
|
||
|
||
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
|
||
|
||
}
|
||
|
||
} else {
|
||
|
||
setCookie("lastPath", path)
|
||
|
||
}
|
||
|
||
|
||
|
||
function setCookie(cname, cvalue) {
|
||
|
||
var d = new Date();
|
||
|
||
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
|
||
|
||
var expires = "expires=" + d.toGMTString();
|
||
|
||
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
|
||
|
||
}
|
||
|
||
|
||
|
||
function getCookie(cname) {
|
||
|
||
var name = cname + "=";
|
||
|
||
var ca = document.cookie.split(';');
|
||
|
||
for (var i = 0; i < ca.length; i++) {
|
||
|
||
var c = ca[i].trim();
|
||
|
||
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
|
||
|
||
}
|
||
|
||
return "";
|
||
|
||
}
|
||
|
||
|
||
|
||
</script>
|
||
|
||
|
||
|
||
</html>
|
||
|