宿主机: Ubuntu 22.04,已装好kvm
硬盘: ubuntu安装在512g ssd,两个4T机械硬盘(忍不住夕夕低价诱惑,344入了海康希捷ST4000VX000)组zpool mirror,可用空间4T,挂载到/tank目录下,打算建个3T的磁盘镜像给群晖。
群晖选的是DS918+镜像,版本DSM 6.2.3-25426
经过多次试错,已经给大家整理好下载即可用的镜像,放云盘上自己下载。
https://cloud.189.cn/web/share?code=UnaIBjBJZneq(访问码:ka2t)
synoboot.img是启动镜像,需要转换后以U盘形式挂载引导,DSM_DS918+_2546.pat是安装文件,引导后需要手动在页面上上传这个文件安装,不能联网下载,因为启动镜像只支持这个版本的安装文件。
另外放了个PHP7.0的套件安装包,因为Photo Station必须依赖这个,但是套件中心已经没有下载了,如果不装Photo Station,忽略它(可以用Moments代替)。
安装目录 /tank/vmdisk/ds918
,先用mkdir创建,进入该目录
上传引导文件synoboot.img
到/tank/vmdisk/ds918
,执行转换:
1 | qemu-img convert -f raw -O qcow2 synoboot.img syno-ds918-qcow2.img |
生成syno-ds918-qcow2.img文件
创建3T的磁盘镜像文件:
1 | qemu-img create -f qcow2 syno-data.qcow2 3072G |
创建虚拟机:
1 | virt-install --name ds918 --memory 4096 --vcpus=2 \ |
第一行: 分配4G内存 2核cpu
第二行: 指定操作系统类型,没有群晖,用linux的就行,开启vnc 5914端口,用于安装操作系统(这里其实没啥用,因为安装在web上进行)
第三行: 指定桥接网络,网卡e1000e, 非常重要,不然会无法连接
第四行: 指定启动盘,就是刚刚我们转换出来的文件,bus必须是usb
,U盘模式
第五行: 指定挂载的3T磁盘文件,bus必须是sata
第六行: 指定启动模式
执行后,用vnc连接到宿主机:5914端口,可以看到系统已经启动
先要找到创建的虚拟机ip,上面我们走的是桥接模式,可以在路由器上找到它,操作的电脑如果在同一局域网内也可以打开 http://find.synology.com 查找,或者下载Synology Assistant查找设备。我这里找到的是192.168.20.23
。 浏览器打开 http://192.168.20.23
,看到欢迎界面:
点设置,选择手动安装,浏览选择DSM_DS918+_2546.pat
,再点立即安装:
弹出警告,打勾选确定:
开始安装:
静静等待安装完成吧。
]]>我现在的方案是一个树莓派24x365天开机(5w左右),树莓派通过tailscale穿透,在外需要使用NAS时,连上树莓派,通过树莓派发送magic packet开机,crontab设置凌晨自动关机。
除了在BIOS里开启Wake on lan, ubuntu也需要设置过,才能支持Wake on Lan。
1 | sudo apt-get install ethtool |
1 | ifconfig |
我这里是enp3s0
,可能是eth0
,eth1
,enp2s0
之类的
同时记录网卡的mac地址,发送唤醒包需要
sudo vim /etc/systemd/system/wol.service
1 | [Unit] |
保存配置,启用:
1 | sudo systemctl enable wol |
树莓派里安装wakeonlan
1 | sudo apt install wakeonlan |
发送唤醒包:
1 | wakeonlan xx:xx:xx:x:xx:xx |
同时数据安全起见,打算用zfs on linux,4T硬盘分1个T的分区和1T硬盘组RAID1(zpool mirror模式), 重要数据放这里,非重要数据放另外的3T分区。以后1T不够用了,再买新硬盘扩容。
先在虚拟机上测试。
宿主机上执行
1 | qemu-img create -f qcow2 ubuntu-1.qcow2 1G |
虚拟机里执行sudo fdisk -l
查看刚挂载的磁盘
可以看到4g硬盘挂到了/dev/vdc下,下面给/dev/vdc分成1G+3G
使用fdisk给硬盘分区,下面的图里分两区分了两次执行
分完区后,再用sudo fdisk -l
查看:
我们要把 /dev/vdb和/dev/vdc1组成RAID1, 在zfs中,叫mirror。
/dev/vdc2 格式化,挂载到 /data目录下直接使用
1 | sudo zpool create tank mirror /dev/vdb /dev/vdc1 |
执行完后sudo zpool status
查看zpool状态,df -H
查看分区
下面我们试试,把文件放到/tank/目录下,卸载一个硬盘,会不会丢数据。
先用dd命令创建一个512M的测试文件
1 | sudo dd if=/dev/zero of=/tank/test.data bs=1M count=512 |
md5sum
计算文件md5值,并记录,如果文件没有被破坏,md5值一定不会变
aa559b4e3523a6c931f08f4df52d58f2 test.data
宿主机上执行卸载磁盘命令
1 | virsh detach-disk ubuntu ~/vm/ubuntu/ubuntu-1.qcow2 --persistent |
虚拟机上sudo fdisk -l
/dev/vdb 马上看不到了,但是sudo zpool status
不能马上显示异常,测试过程中大概过了几分钟,才显示vdb UNAVAIL
此时重新计算md5, 没有变化,再试试把刚卸载的ubuntu-1.qcow2挂回来,卸载另一个磁盘,也就是ubuntu-2.qcow2
1 | virsh attach-disk --domain ubuntu --source ~/vm/ubuntu/ubuntu-1.qcow2 --target vdb --subdriver qcow2 --persistent |
正常情况下,应该也不会有问题,但我此次出了意外,可能是因为上面的操作太快,挂载后马上卸载,zpool没反应过来,这时我执行了md5sum
然后一直卡着不动,ctrl+c也取消不了。
新开了个ssh窗口,执行sudo zpool status
提示I/O异常,建议执行zpool clear
试试。
执行完以后,再md5sum
文件没有变化:
说明我们的raid1冗余方案没问题,但提示设备有错误,试着把卸载的磁盘挂回去。
1 | virsh attach-disk --domain ubuntu --source ~/vm/ubuntu/ubuntu-2.qcow2 --target vdc --subdriver qcow2 --persistent |
发现/dev/vdc没回来,变成了/dev/vdd,virsh edit ubuntu
查看了下配置文件,确定我没配错,可能是kvm虚拟机的bug, 那就重启下虚拟机吧。
重启后,/dev/vdc回来了, zpool status
状态也是online,但还是提示有错误,执行下 zpool clear
, 清除错误
上面的破坏测试模拟的是拔插硬盘,下面试试模拟硬盘坏了换个硬盘,以及换大硬盘升级空间.
先看看当前容量:
1 | sudo zpool list |
当前总大小是960M,因为创建了一个512M的文件,可用448M。
宿主机上创建两个8G的硬盘文件并挂载, 目的是把1G的升级成8G
1 | qemu-img create -f qcow2 ubuntu-3.qcow2 8G |
虚拟机上用sudo zpool replace tank vdb vdd
命令把vdb替换成vdd
替换完以后,zpool status
会显示替换状态,上图因为才512M文件,又是在ssd中,1s就完成了。
再换另一个 sudo zpool replace tank vdc1 vdd
默认情况下,自动扩展功能是关闭的,下图可以看到EXPANDSZ
有7G
可以通过sudo zpool set autoexpand=on tank
开启自动扩展。
两个盘都执行扩展:
1 | sudo zpool online -e tank vdd |
完成扩容,再看看test.data文件还在不在
文件没有任何变化
]]>1 | virt-install --name ubuntu --memory 4096 --vcpus=2 \ |
--os-variant
可用值选项查看 virt-install --osinfo list
, 但ubuntu22.04
不在列表里貌似也没问题
安装linux用lvm创建分区,方便后期扩容
可使用vnc viewer连接安装操作系统, 端口5911,密码000000 会持久化,一直可用,配置保存到/etc/libvirt/qemu/ubuntu.xml ,命令virsh dumpxml ubuntu
可查看配置
如果是从已装好的硬盘文件导入:
1 | virt-install --name ubuntu2 --memory 4096 --vcpus=2 \ |
开启虚拟机
1 | virsh start ubuntu |
关闭虚拟机
1 | virsh shutdown ubuntu |
查看状态
1 | virsh list --all |
设置开机启动
1 | virsh autostart ubuntu |
取消开机启动
1 | virsh autostart --disable ubuntu |
查看虚拟机IP
在桥接模式下,我暂时没找到在宿主机上查看虚拟机ip的办法,目前用Vnc viewer连接vnc端口进入虚拟机查看ip
基于lvm文件系统
查看磁盘信息
1 | qemu-img info /home/xxx/vm/ubuntu/ubuntu.qcow2 |
扩容10G
1 | qemu-img resize /home/xxx/vm/ubuntu/ubuntu.qcow2 +10G |
重启虚拟机
以上在母机中操作
查看硬盘是否变大sudo fdisk -l
/dev/vda
已经从30g变成40g
给新加的10g分区sudo fdisk /dev/vda
输入p
命令,所有选项直接回车,默认分配所有可用空间
创建物理卷sudo pvcreate /dev/vda4
查看物理卷, 加入物理卷/dev/vda4
到卷组ubuntu-vg
sudo pvs
查看逻辑卷sudo lvdisplay
记录逻辑卷 LV Path: /dev/ubuntu-vg/ubuntu-lv
查看物理卷sudo pvs
有14G可用
执行逻辑卷扩容
1 | sudo lvextend -L +14G /dev/ubuntu-vg/ubuntu-lv |
或者
sudo lvextend -l +100%FREE /dev/ubuntu-vg/ubuntu-lv
分配全部空间
ext4文件系统用resize2fs,xfs系统用xfs_growfs
执行文件系统扩容
sudo resize2fs /dev/ubuntu-vg/ubuntu-lv
可以看到,可用空间变大了
给ubuntu
虚拟机创建快照
virsh snapshot-create ubuntu
查看ubuntu
虚拟机快照
virsh snapshot-list ubuntu
快照配置文件在 /var/lib/libvirt/qemu/snapshot/ubuntu
恢复快照
virsh snapshot-revert ubuntu 1669277133
秒级恢复,不需要先关机
创建磁盘
qemu-img create -f qcow2 ubuntu-1.qcow2 10G
查看磁盘信息qemu-img info ubuntu-3.qcow2
磁盘被挂载时会被锁住,无法查看信息
添加磁盘
virsh edit ubuntu
打开虚拟机配置文件,将刚刚创建的文件加到虚拟机中
1 | <disk type='file' device='disk'> |
图中的address标签,会在保存后自动生成, 也可以用命令添加磁盘:
virsh attach-disk --domain ubuntu --source ~/vm/ubuntu/ubuntu-1.qcow2 --target vdb --subdriver qcow2 --persistent
--persistent
选项会将配置持久化保存到xml中,重启后仍然有效。
添加后再按上面的扩容方法,分区扩容
查看挂载的磁盘virsh domblklist ubuntu
卸载磁盘virsh detach-disk ubuntu /home/xxx/vm/ubuntu/ubuntu-3.qcow2
查看快照
1 | virsh snapshot-list --domain vm-name |
删除快照
1 | virsh snapshot-delete --domain vm-name --snapshotname 3sep2016u1 |
删除虚拟机
1 | virsh undefine vm-name |
最后一步,删除磁盘文件
]]>节点 | IP |
---|---|
node1 | 192.168.0.1 |
node2 | 192.168.0.2 |
node 3 | 192.168.0.3 |
数据库我们使用mysql 数据库,内置数据源也是可以的,会自动同步,但生产环境,还是建议使用云厂商提供的数据库,安全有保障。
SQL来自官方github:
https://github.com/alibaba/nacos/blob/master/distribution/conf/mysql-schema.sql
1 | /* |
docker-compose.yml:
1 | version: "3" |
三个节点,根据各自ip修改NACOS_SERVERS和NACOS_SERVER_IP字段,NACOS_SERVERS为另两台机器的ip:8848, NACOS_SERVER_IP是本机IP
四个端口
端口 | 功能 |
---|---|
8848 | 主端口 |
9848 | grpc端口,主端口+1000 |
9849 | grpc for server端口,主端口+1001 |
7848 | raft端口 |
docker-compose up -d
命令启动
浏览器访问任意一台机器8848端口:
如 http://192.168.0.1:8848/nacos 账号密码都是nacos
查看节点:
根据官方的架构图,还需要配置负载均衡:
配置SLB或者用nginx,把8848,9848两个端口放出来,客户端(sping boot)使用SLB或nginx的ip:8848 端口连接
]]>当前虚拟机的垃圾收集都采用分代收集理论,根据对象存活时间不同将内存分为几块。Java推分为新生代和老年代,我们根据各个年龄代不同的特性选择合适的垃圾收集算法。在新生代,每次收集都会有大量的对象(99%)死亡,可以选择复制算法,只需要付出少量对象的复制成本就可以完成垃圾收集。老年代存活率比较高,而且没有额外的空间对它进行分配担保,所以我们必须选择标记-清除或者标记-整理算法。标记清除和标记整理算会会比复制算法慢10倍以上。
复制算法将内存分为大小相同的两块,每次只使用其中一块。当其中一块使用完时,就将存活的对象复制到另一块上,然后把这块空间清理干净。每次回收都是对内存空间的一半进行回收。
标记-清除算法分为标记、清除两个阶段,先标记存活的对象,再统一回收未被标记的对象(通常情况),也可以标记需要回收的对象,标记完后统一回收标记过的对象。
标记-整理算法在标记这个阶段,与标记-清除算法一致,但后续并不是直接回收垃圾对象,而是将存活对象对象挪到一端,剩下的空间清理掉。
我们都知道类的加载过程有加载
->验证
->准备
->解析
->初始化
几个步骤,才能正式使用,当我们用new指令创建对象时,虚拟机会判断该类是否已经完成加载,如果没有,先执行上述的加载步骤,如果已经完成加载,就开始创建对象。一旦一个类加载完成,待创建对象所需的内存大小就已经确定了,为对象分配空间,等同于把一块确定大小的内存从堆里划分出来。
如果垃圾收集器采用标记整理算法,Java堆中的内存是规整的,所有用过的内存都在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离。
如果Java堆中的内存并不是规整的(标记清除算法),已使用的内存和空 闲的内存相互交错,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记 上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录.
多线程同时创建对象时,会存在并发问题,同时使用相同的内存地址,那么jvm是怎么解决的呢?
XX:+/-UseTLAB
参数来设定虚拟机是否使用TLAB(JVM会默认开启XX:+UseTLAB
),XX:TLABSize
指定TLAB大小。内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头), 如果使用TLAB,这一工作过程也可以提前至TLAB分配时进行。这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问 到这些字段的数据类型所对应的零值。
初始化零值之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头Object Header之中。
在HotSpot虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、 实例数据(Instance Data) 和对齐填充(Padding)。 HotSpot虚拟机的对象头包括两部分信息,第一部分用于存储对象自身的运行时数据, 如哈 希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时 间戳等。对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
可以看到分代年龄只占4bit,所以年龄最大是15(2^4-1)
这个是指向元空间里类信息的指针
如果对象的大小不是8的位数,就会使用对齐填充填到8的位数,主要是提交内存管理效率。
执行<init>
方法,即对象按照程序员的意愿进行初始化。对应到语言层面上讲,就是为属性赋值(注意,这与上面的赋 零值不同,这是由程序员赋的值),和执行构造方法。
对象的头信息以用占用空间可以使用jol-core包查看:
1 | <dependency> |
1 | Object obj=new Object(); |
在jvm中,32位地址最大支持4G内存(2的32次方),可以通过对对象指针的压缩编码、解码方式进行优化,使得jvm 只用32位地址就可以支持更大的内存配置(小于等于32G)
堆内存小于4G时,不需要启用指针压缩,jvm会直接去除高32位地址,即使用低虚拟地址空间
堆内存大于32G时,压缩指针会失效,会强制使用64位(即8字节)来对java对象寻址,这就会出现1的问题,所以堆内 存不要大于32G为好。
我们通过JVM内存分配可以知道JAVA中的对象都是在堆上进行分配,当对象没有被引用的时候,需要依靠GC进行回收内存,如果对象数量较多的时候,会给GC带来较大压力,也间接影响了应用的性能。为了减少临时对象在堆内分配的数量,JVM通过逃逸分析
确定该对象不会被外部访问。如果不会逃逸可以将该对象在栈上分配内存,这样该对象所占用的内存空间就可以随栈帧出栈而销毁,就减轻了垃圾回收的压力。
对象逃逸分析
:就是分析对象动态作用域,当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参 数传递到其他地方中:
1 | public User test1(){ |
很显然test1方法中的user对象被返回了,这个对象的作用域范围不确定,test2方法中的user对象我们可以确定当方法结束这个对象就可以认为是无效对象了,对于这样的对象我们其实可以将其分配在栈内存里,让其在方法结束时跟随栈内存一起被回收掉。 JVM对于这种情况可以通过开启逃逸分析参数(-XX:+DoEscapeAnalysis)来优化对象内存分配位置,使其通过标量替换优 先分配在栈上(栈上分配),JDK7之后默认开启逃逸分析,如果要关闭使用参数(-XX:-DoEscapeAnalysis)
标量替换
:通过逃逸分析确定该对象不会被外部访问,并且对象可以被进一步分解时,JVM不会创建该对象,而是将该对象成员变量分解若干个被这个方法使用的成员变量所代替,这些代替的成员变量在栈帧或寄存器上分配空间,这样就 不会因为没有一大块连续空间导致对象内存不够分配。开启标量替换参数(-XX:+EliminateAllocations),JDK7之后默认 开启。
标量与聚合量
:标量即不可被进一步分解的量,而JAVA的基本数据类型就是标量(如:int,long等基本数据类型以及 reference类型等),标量的对立就是可以被进一步分解的量,而这种量称之为聚合量。而在JAVA中对象就是可以被进一 步分解的聚合量。
栈上分配,依赖于逃逸分析
和标量替换
大多数情况下,对象在新生代中 Eden 区分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
大量的对象被分配在eden区,eden区满了后会触发minor gc,可能会有99%以上的对象成为垃圾被回收掉,剩余存活的对象会被挪到为空的那块survivor区,下一次eden区满了后又会触发minor gc,把eden区和survivor区垃圾对象回 收,把剩余存活的对象一次性挪动到另外一块为空的survivor区,因为新生代的对象都是朝生夕死的,存活时间很短,所 以JVM默认的8:1:1的比例是很合适的,让eden区尽量的大,survivor区够用即可, JVM默认有这个参数-XX:+UseAdaptiveSizePolicy
(默认开启),会导致这个8:1:1比例自动变化,如果不想这个比例有变 化可以设置参数-XX:-UseAdaptiveSizePolicy
大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。JVM参数 -XX:PretenureSizeThreshold
可以设置大对象的大小,如果对象超过设置大小会直接进入老年代,不会进入年轻代,这个参数只在 Serial
和ParNew
两个收集器下有效 。比如设置JVM参数:-XX:PretenureSizeThreshold=1000000
(单位是字节) -XX:+UseSerialGC
,当创建大于100000的对象,就会直接进入老年代。
为什么要这样呢? 为了避免为大对象分配内存时的复制操作而降低效率。
既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在 老年代中。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器。 如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为1。对象在 Survivor 中每熬过一次 MinorGC,年龄就增加1岁,当它的年龄增加到一定程度 (默认为15岁,CMS收集器默认6岁,不同的垃圾收集器会略微有点不同),就会被晋升到老年代中。对象晋升到老年代 的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold
来设置。
当前放对象的Survivor区域里(其中一块区域,放对象的那块s区),一批对象的总大小大于这块Survivor区域内存大小的 50%(-XX:TargetSurvivorRatio
可以指定),那么此时大于等于这批对象年龄最大值的对象,就可以直接进入老年代了, 例如Survivor区域里现在有一批对象,年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区域的50%,此时就会 把年龄n(含)以上的对象都放入老年代。这个规则其实是希望那些可能是长期存活的对象,尽早进入老年代。对象动态年 龄判断机制一般是在minor gc之后触发的。
年轻代每次minor gc之前JVM都会计算下老年代剩余可用空间。如果这个可用空间小于年轻代里现有的所有对象大小之和(包括垃圾对象) 就会看一个-XX:-HandlePromotionFailure
(jdk1.8默认就设置了)的参数是否设置了,如果有这个参数,就会看看老年代的可用内存大小,是否大于之前每一次minor gc后进入老年代的对象的平均大小。 如果上一步结果是小于或者之前说的参数没有设置,那么就会触发一次Full gc,对老年代和年轻代一起回收一次垃圾, 如果回收完还是没有足够空间存放新的对象就会发生”OOM” 当然,如果minor gc之后剩余存活的需要挪动到老年代的对象大小还是大于老年代可用空间,那么也会触发full gc,full gc完之后如果还是没有空间放minor gc之后的存活对象,则也会发生“OOM”
判断一个对象是否需要被回收,有引用计数法
和可达性分析法
给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加1;当引用失效,计数器就减1;任何时候计数器为0 的对象就是不可能再被使用的。 这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决 对象之间相互循环引用的问题。
将“GC Roots” 对象作为起点,从这些节点开始向下搜索引用的对象,找到的对象都标记为非垃圾对象,其余未标记的 对象都是垃圾对象.
GC Roots根节点:线程栈的本地变量、静态变量、本地方法栈的变量等等
]]>初始内存跟最大内存最好相等,防止内存不够时扩充内 存或者Full GC,导致性能降低
以上配置可以推出老年代内存512-100=412M
如果程序启动慢,观察是否在启动过程中频繁full gc,如果是,可能是程序比较大,因为Metaspace存放类的信息,很快达到了21M,频繁Full gc扩容,可以试着改大。建议MaxMetaspaceSize和MaxMetaspaceSize改成一样
]]>JVM优化,就是尽可能让对象都在新生代里分配和回收,尽量别 让太多对象频繁进入老年代,避免频繁对老年代进行垃圾回收,同时给系统充足的内存大小,避免新生代频繁的进行垃圾回收。没有通用的调优参数,需要根据业务场景来。
操作系统: CentOS Linux release 7.6.1810
内网ip: 192.168.20.15
kubernetes: v1.15.1
docker: 19.03.1
1 | curl -fsSL get.docker.com -o get-docker.sh |
1 | cat <<EOF > /etc/docker/daemon.json |
nas.pocketdigi.com:8083 是私有仓库地址,不支持https,所以需要放到insecure-registries,没有私有仓库就不需要
1 | cat <<EOF > /etc/yum.repos.d/kubernetes.repo |
1 | cat <<EOF > /etc/sysctl.d/k8s.conf |
注释掉/etc/fstab文件里swap分区,重启
1 | kubeadm init --apiserver-advertise-address=192.168.20.15 --pod-network-cidr=10.244.0.0/16 --apiserver-cert-extra-sans=nas.pocketdigi.com |
因为服务器没有外网ip,是通过nginx转发的,必须通过-apiserver-cert-extra-sans
配置最终访问的外网ip或域名
如果是国内服务器,因为k8s仓库被墙,会报以下错误:
1 |
|
好办,我们先找台海外服务器,把相应的镜像拉下来,推到我们自己的私有仓库里,再pull,然后改tag。没有私有仓库也不要紧,我已经把1.15.1推到hub.docker.com了。
找一台能连接k8s.gcr.io的服务器:
1 | docker pull k8s.gcr.io/kube-apiserver:v1.15.1 |
回到安装kubernetes的机器:
1 | docker pull nas.pocketdigi.com:8083/k8s.gcr.io/kube-apiserver:v1.15.1 |
没有条件的小伙伴,请直接使用下面的命令下载我转到hub.docker.com的镜像:
1 | docker pull pocketdigi/kube-apiserver:v1.15.1 |
重新执行init:
1 | kubeadm init --apiserver-advertise-address=192.168.20.15 --pod-network-cidr=10.244.0.0/16 --apiserver-cert-extra-sans=nas.pocketdigi.com |
–pod-network-cidr是因为我们后面用的是flannel网络插件,默认就是这个网段
成功后得到以下信息:
1 | Your Kubernetes control-plane has initialized successfully! |
注意保存token,token会在24小时后过期,到时如果需要往集群里加节点,需要重新创建token,discovery-token-ca-cert-hash值是不会变的
1 | kubeadm token create |
复制kubectl所需的配置文件:
1 | mkdir -p $HOME/.kube |
安装网络插件flannel,先从镜像节点下载镜像
1 | docker pull pocketdigi/flannel:v0.11.0-amd64 |
允许在master节点安装pod(默认不允许)
1 | kubectl taint nodes --all node-role.kubernetes.io/master- |
后期如果增加了节点,要禁止在master节点部署pod,通过以下命令恢复:
1 | kubectl taint nodes kubernetes-master node-role.kubernetes.io/master=:NoSchedule |
kubernetes-master
是master节点名
如果使用外部安装的rancher管理集群,会无法注册,关闭防火墙解决:
1 | systemctl disable firewalld |
如果要对外暴露http服务,建议安装Ingress. 官方文档
官方文档里的yaml不会暴露80,443端口,需要下载后修改。
1 | wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml |
在nginx-ingress-controller Deployment的spec.template.spec节点下增加hostNetwork: true
,即使用主机网络。
如果有多台机器,不只一个master节点,需要把Deployment改成DaemonSet,以便在每个节点都部署nginx。最后修改后如下:
1 | apiVersion: v1 |
然后在master和worker节点pull镜像
1 | docker pull pocketdigi/nginx-ingress-controller:0.25.0 |
在master节点执行:
1 | kubectl apply -f mandatory.yaml |
添加ingress-nginx Service,这一步不同主机提供商操作不同,参考官方文档,一般虚拟机就用下面的就可以了。
1 | kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/cloud-generic.yaml |
导入SSL证书
1 | kubectl create secret tls nas-pocketdigi-com --key private.pem --cert fullchain.pem --namespace prod |
nas-pocketdigi-com
是证书名,我这个是泛域名证书,prod是指定导入的命名空间,需要先创建
nginx.yaml
1 | apiVersion: v1 |
1 | kubectl apply -f nginx.yaml |
如果nginx.nas.pocketdigi.com已经解析到这台主机,现在应该能正常访问到nginx。
]]>编辑器,我现在用的是Visual Studio Code,图片用PicGo插件,配上阿里云oss,非常方便。部署用的是hexo-deployer-rsync插件,一条命令,打算写个小脚本,配上gitlab runner,实现提交后自动部署。
]]>Spring boot项目在启动时会加载application.properties(或yaml)里的配置,如果修改了application.properties,需要重新打包、部署,当服务有多个实例,需要每个都重新部署。配置中心就是用来解决这个问题的,我们所配置内容放在配置中心统一管理,项目启动时先去配置中心拉取配置,再用这些配置执行启动操作。如果配置有变更,在配置中心修改后,只需要一一重启服务,即可完成配置更新。有些配置中心甚至不需要重启,支持配置推送,比如我们今天介绍的Spring cloud zookeeper config。
Zookeeper提供了一个类似目录树的存储结构,允许客户端存储任意数据,如:配置数据。
Spring Cloud Zookeeper Config是Spring Cloud Config Server和Client的替代方案(这个方案需要起一个server服务,依赖git存储配置),在bootstrap
阶段,加载配置到Spring环境中。
默认情况下,配置存储在/config
节点下。Spring Cloud Zookeeper Config会基于应用程序名称和profile创建多个PropertySource
模拟Spring Cloud Config顺序从Zookeeper读取并解析配置。
例如,应用名是testApp
,profile是dev
会创建下面几个property源:
1 | /config/testApp,dev |
配置优先级是从上至下的。/config/application
会应用到所有使用zookeeper作配置中心的项目上,/config/testApp
只对应用名为testApp
的项目有效.
下面的例子是
1 | <dependency> |
版本号跟你用的spring boot版本相关,2.1.x版本可以直接使用2.1.0.RELEASE,其他版本参考 https://spring.io/projects/spring-cloud
上文提到Spring cloud zookeeper config默认配置存在/confi
节点,默认读取/config/application
配置,application和profile之间用逗号分割,这些都是可配置的。
resources目录下创建bootstrap.properties:
1 | spring.cloud.zookeeper.config.enable=true |
不过没有特殊需要,不建议修改这些默认配置,原因下面会讲。
引用配置方法跟放properties里的一样,用@Value或者 @ConfigurationProperties,只有在使用@ConfigurationProperties才能动态更新, @Value要重启后生效
1 | @Value("${key}") |
1 | @ConfigurationProperties(prefix = "test") |
Spring并没有提供一个图形化修改配置的工具,但有第三方解决方案。zookeeper-config-keeper
docker一键部署:
1 | docker run --name=zkck -p 80:80 -e jwtsecret=asdfwefasdf -e zookeeperaddress=192.168.2.103:2181 pocketdigi/zookeeper-config-keeper:v0.1 |
Kong本身自带授权认证插件,如Basic Authentication,我们通过kong代理kong admin api,再配上Basic Authentication插件,访问kong admin api时,需要带上账号密码。根据Basic Authentication规范,账号密码就是base64编码后,通过header里的Authorization字段传输。而Custom Headers功能会在给Kong admin api接口发送请求时,加上您指定的header,所以可以实现带密码访问kong admin api,解决了安全问题。
假设部署kong的部署地址是 http://192.168.0.205/, admin api地址是 http://192.168.0.205:8001/ ,我们接下来把http://192.168.0.205/manage 转到 http://192.168.0.205:8001/ ,并配上Basic Authentication.
我们先使用http://192.168.0.205:8001/ 配置,完成后再禁止admin api外部访问 创建Service: 创建Route: 完成上面操作后,可以试试浏览器打开 http://192.168.0.205/manage ,可以访问admin api了。
Basic Authentication 需要Credential才可以访问,Credential属于某个Consumer,一个Consumer可以有多个Credential。 先创建 Consumer 再创建一个Basic Auth Credential,输入账号密码,这个账号密码就是Basic Authentication需要的。
完成配置以后,再用浏览器打开 http://192.168.0.205/manage 提示输入账号密码,刚刚我们配的都是admin。 Basic Authentication插件会校验header里的Authorization字段,值的格式是Basic base64(账号:密码)
admin:admin
base64以后得到的值是YWRtaW46YWRtaW4=
,所以在Kong Admin UI里使用,Custom Headers字段应该填 {"Authorization":"Basic YWRtaW46YWRtaW4="}
如果其他api没有用到Consumer,配置就到此结束了,接下来配置改kong的配置文件,限制本机外的ip无法访问admin api即可。但如果其他服务也有Consumer,那么需要用ACL限制一下指定的Consumer才能访问admin api,因为默认情况下,所有Consumer都能访问。
ACL黑白名单配置的是Consumer group,所以需要先把我们指定的Consumer加到一个group里。 ACL Group这个功能是我在写这篇教程时加的,老版本的用户要更新下。 再配置ACL: config.whitelist输入group. 现在,只有指定admin可以访问了。
]]>理论上所有openwrt路由都支持,因为下面的脚本没用到小米路由的特性,都是linux上的命令,但我没有测其他路由器。 dnspod开放了api,可以调接口更新记录,现在已经被腾讯收购,也可以用腾讯的api,但腾讯的api比较复杂,反正我没调通。其他的像阿里云也开放了云解析接口,有需要的同学可以自己研究。 重点不在脚本,而在于思路:
ddns 脚本内容:
#!/bin/sholdIPFile=/tmp/oldip.txtlogin_token=xxxxxxxdomain=pocketdigi.comrecord_id=xxxxxxxsub_domain=testupdateIp(){ result=$(curl -s -d "login_token=$login_token&format=json&domain=$domain&record_id=$record_id&sub_domain=$sub_domain&value=$myip&record_type=A&record_line=默认" https://dnsapi.cn/Record.Modify) grepResult=$(echo $result | grep "\"code\":\"1\"") if [[ "$grepResult" != "" ]] then echo '更新成功' echo "$myip" > $oldIPFile else echo '更新失败' fi}myip=$(curl -s http://myip.dnsomatic.com/)echo "当前ip:$myip"if [ ! -f "$oldIPFile" ]; then echo "文件不存在,更新" updateIp;else oldip=$(cat $oldIPFile) echo "旧IP:$oldip" if [ "$myip" = "$oldip" ]; then echo "当前IP与旧IP相同,不更新" else echo "当前IP与旧IP不同,更新" updateIp fifi
login_token需要登录dnspod获取,record_id可以使用chrome,在dnspod后台编辑保存那条记录时抓包找到。 使用scp将脚本拷到路由器上的/data目录,小米路由很多目录是只读的,写不进去 ssh登录路由器:
ssh root@192.168.0.1
密码需要到小米路由官网找 给ddns脚本增加可执行权限
chmod +x /data/ddns
添加定时任务
crontab -e
在末尾添加
* * * * * /data/ddns
大功告成!
]]> tilemap.url: 'http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}' #tilemap.url: 'http://mt2.google.cn/vt/lyrs=m&hl=zh-CN&gl=cn&x={x}&y={y}&z={z}' tilemap.options.maxZoom: 18
用google还是高德随意,google.cn在国内还是可以访问的,tilemap.options.maxZoom
配置的是最大缩放级别,默认只能缩放到区级。 重启kibana,6.3.1实测有效。
max file descriptors [4096] for elasticsearch process is too low, increase to at least [65536]
而且,我的配置文件已经指定使用elasticsearch用户,诡异的错误。 其实出现这个问题,原因在于/etc/security/limits.conf里的配置只针对PAM认证登录用户有效,而Systemd有自己的一套配置。全局配置在/etc/systemd/system.conf和/etc/systemd/user.conf,也可以对单个服务做配置,所以,elasticsearch的脚本要做下修改:[Unit]Description=Elasticsearch[Service]Environment=JAVA_HOME=/usr/local/jdk1.8.0_171LimitCORE=infinityLimitNOFILE=65536LimitNPROC=65536ExecStart=/usr/local/elasticsearch-6.3.1/bin/elasticsearchUser=elasticsearchGroup=elasticsearch[Install]WantedBy=multi-user.target
]]> private void fullScreenImmersive(View view) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_FULLSCREEN; view.setSystemUiVisibility(uiOptions); } } @Override public void show() { this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); super.show(); fullScreenImmersive(getWindow().getDecorView()); this.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); }
]]>Sonatype Nexus是一个仓库管理软件,支持Maven、Docker、Bower、PyPI、Yum等类型仓库。使用Maven管理依赖的Java程序员一定不陌生,搭建Maven私服基本是用这个。注意:3.x版本才支持docker。 今天我们就用Nexus作Docker的私服,管理我们自己的私有镜像。
很简单,下载,解压,我的路径是
/usr/local/nexus-3.11.0-01
创建开机启动脚本vim /etc/systemd/system/nexus.service
内容如下:
[Unit]Description=nexus serviceAfter=network.target[Service]Type=forkingLimitNOFILE=65536ExecStart=/usr/local/nexus-3.11.0-01/bin/nexus startExecStop=/usr/local/nexus-3.11.0-01/bin/nexus stopRestart=on-abort[Install]WantedBy=multi-user.target
设置开机启动systemctl enable nexus
启动systemctl start nexus
nexus默认端口是8081, 现在打开浏览器,访问http://ip:8081, 应该能看到如下界面
 点击右上角Sign in,账号admin 密码admin123,登录
 点击admin ,把默认的密码改掉,安装完成。
点击顶部配置图标,切到配置页面,左侧菜单选Repositories, 打开仓库配置,点击Create repository 创建仓库。
 我之前已经创建好,配置如下图
 注意,这里HTTP设置了一个10008端口,仅供docker使用,找个不冲突的就行。
docker仓库默认需要https支持,但nexus没有配ssl,需要将服务器加到insecure-registries。vim /etc/docker/daemon.json
{ "registry-mirrors": ["https://registry.docker-cn.com"], "insecure-registries":["192.168.0.201:10008"]}
我的nexus部署在192.168.0.201 后面的端口10008与创建仓库时对应。 登录仓库:docker login 192.169.0.201:10008
按提示输入nexus的账号密码。登录一次以后,会自动保存,以后pull/push都不需要再登录。
将正在运行的container打包成image

如图,nginx2容器基于nginx镜像,但是改了一些配置, 现在我们将其打包成imagedocker commit -m "nginx modified" -a "Exception" cb4cbbcde646 nginx-modified
-m
描述-a
作者cb4cbbcde646
container idnginx-modified
生成的image名
现在再看镜像列表:

发现我们刚刚commit的image已经在里面了。
给image打标签docker tag f3b408f36cf7 192.168.0.201:10008/nginx-modified
f3b408f36cf7
image id192.168.0.201:10008/nginx-modified
仓库地址和保存路径
推送镜像docker push 192.168.0.201:10008/nginx-modified
下载镜像
在另一台服务器上,如果要下载镜像,重复上面的docker配置步骤,然后pulldocker pull 192.168.0.201:10008/nginx-modified