k8s使用nfs作为动态storageClass存储
概述
相信使用过pv和pvc的肯定会想到很多问题,比如每次申请 pvc 都需要手动添加pv,这岂不是太不方便了。那我们如何实现类似于公有云或者私有云的共享存储模式呢?kubernetes 提供了 storageclass 的概念,接下来我们来一探究竟。
先上一张图大家就比较清楚了:
环境
k8s集群环境
Node(宿主机上)都要安装nfs
1 | [root@node-1 ~]# yum -y install nfs-utils |
nfs 环境
- 搭建nfs服务端
1
2
3
4
5
6
7
8
9
10
11yum -y install rpcbind nfs-utils
systemctl start rpcbind
systemctl start nfs
systemctl enable rpcbind
systemctl enable nfs
mkdir /home/nfsfile
chmod -R 777 /home/nfsfile
cd /home/nfsfile
echo "This is a test file" > /nfsfile/test.txtvi /etc/exports
这行代码的意思是把共享目录1
/home/nfsfile *(rw,sync,root_squash,insecure)
/home/nfsfile
共享给*
这个客户端ip,后面括号里的内容是权限参数,其中:- rw 表示设置目录可读写。
- sync 表示数据会同步写入到内存和硬盘中,相反 rsync 表示数据会先暂存于内存中,而非直接写入到硬盘中。
- no_root_squash NFS客户端连接服务端时如果使用的是root的话,那么对服务端分享的目录来说,也拥有root权限。
- no_all_squash 不论NFS客户端连接服务端时使用什么用户,对服务端分享的目录来说都不会拥有匿名用户权限。
showmount -e localhost
1
2Export list for localhost:
/home/nfsfile *
- 客户端验证nfs
我们在客户端执行以下命令:showmount -e 10.8.111.153
客户端开始挂载共享目录:1
2Exports list on 10.8.111.153:
/home/nfsfile *客户端验证是否挂载成功:1
2mkdir nfsfile # 客户端新建挂载点
mount -t nfs 10.8.111.153:/home/nfsfile /root/nfsfile # 挂载服务端共享目录到新创建的挂载点最后,如果需要永久挂载该共享目录(即实现开机自动挂载),则可以通过如下方式实现:1
2cd /root/nfsfile # 进入该目录后,将会看到之前在服务端创建的 test.txt 文件
cat test.txt # 打开后,发现文件内容与服务端文件内容的一致。说明本次 nfs 共享文件系统搭建成功!1
echo "mount -t nfs 10.8.111.153:/home/nfsfile /root/nfsfile" >> /etc/rc.d/rc.local # 将挂载命令写入 rc.local
直接pod挂载nfs
1 | apiVersion: apps/v1 |
使用storageClass、pv、pvc
rbac.yaml
1 | apiVersion: v1 |
nfs-subdir-external-provisioner.yaml
1 | kind: Deployment |
nfs-storage-class.yaml
1 | apiVersion: storage.k8s.io/v1 |
nfs-test-pvc.yaml
1 | kind: PersistentVolumeClaim |
nfs-test-nginx-pod.yaml
1 | apiVersion: v1 |
其他
https://blog.51cto.com/u_16175526/6718397
https://blog.51cto.com/u_16213459/7344688
https://blog.csdn.net/qq_30051761/article/details/131055705
react笔记汇总(与vue差异)
最近因为要维护我自研的物联网平台,用了react,与vue有点小差异,这里记录一下使用差异
Go 指针
用GO搭建物联网平台一段时间了,还是依然在指针上习惯性误用,汇总一下
指针地址和指针类型
一个指针变量可以指向任何一个值的内存地址,它所指向的值的内存地址在 32 和 64 位机器上分别占用 4 或 8 个字节,占用字节的大小与所指向的值的大小无关。当一个指针被定义后没有分配到任何变量时,它的默认值为 nil。指针变量通常缩写为 ptr。
每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用在变量名前面添加&操作符(前缀)来获取变量的内存地址(取地址操作),格式如下:
1 | ptr := &v // v 的类型为 T |
其中 v 代表被取地址的变量,变量 v 的地址使用变量 ptr 进行接收,ptr 的类型为T,称做 T 的指针类型,代表指针。
指针实际用法,可以通过下面的例子了解:
1 | package main |
运行结果:
1 | 0xc042052088 0xc0420461b0 |
代码说明如下:
- 第 8 行,声明整型变量 cat。
- 第 9 行,声明字符串变量 str。
- 第 10 行,使用 fmt.Printf 的动词%p打印 cat 和 str 变量的内存地址,指针的值是带有0x十六进制前缀的一组数据。
提示:变量、指针和地址三者的关系是,每个变量都拥有地址,指针的值就是地址。
从指针获取指针指向的值
当使用&操作符对普通变量进行取地址操作并得到变量的指针后,可以对指针使用*操作符,也就是指针取值,代码如下
1 | package main |
运行结果:
1 | ptr type: *string |
代码说明如下:
- 第 10 行,准备一个字符串并赋值。
- 第 13 行,对字符串取地址,将指针保存到变量 ptr 中。
- 第 16 行,打印变量 ptr 的类型,其类型为 *string。
- 第 19 行,打印 ptr 的指针地址,地址每次运行都会发生变化。
- 第 22 行,对 ptr 指针变量进行取值操作,变量 value 的类型为 string。
- 第 25 行,打印取值后 value 的类型。
- 第 28 行,打印 value 的值。
取地址操作符&和取值操作符是一对互补操作符,&取出地址,根据地址取出地址指向的值。
变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:
- 对变量进行取地址操作使用&操作符,可以获得这个变量的指针变量。
- 指针变量的值是指针地址。
- 对指针变量进行取值操作使用*操作符,可以获得指针变量指向的原变量的值。
使用指针修改值
通过指针不仅可以取值,也可以修改值。
前面已经演示了使用多重赋值的方法进行数值交换,使用指针同样可以进行数值交换,代码如下:
1 | package main |
运行结果:
1 | 2 1 |
代码说明如下:
- 第 6 行,定义一个交换函数,参数为 a、b,类型都为 *int 指针类型。
- 第 9 行,取指针 a 的值,并把值赋给变量 t,t 此时是 int 类型。
- 第 12 行,取 b 的指针值,赋给指针 a 指向的变量。注意,此时*a的意思不是取 a 指针的值,而是“a 指向的变量”。
- 第 15 行,将 t 的值赋给指针 b 指向的变量。
- 第 21 行,准备 x、y 两个变量,分别赋值为 1 和 2,类型为 int。
- 第 24 行,取出 x 和 y 的地址作为参数传给 swap() 函数进行调用。
- 第 27 行,交换完毕时,输出 x 和 y 的值。
操作符作为右值时,意义是取指针的值,作为左值时,也就是放在赋值操作符的左边时,表示 a 指针指向的变量。其实归纳起来,操作符的根本意义就是操作指针指向的变量。当操作在右值时,就是取指向变量的值,当操作在左值时,就是将值设置给指向的变量。
如果在 swap() 函数中交换操作的是指针值,会发生什么情况?可以参考下面代码:
1 | package main |
运行结果:
1 | 1 2 |
结果表明,交换是不成功的。上面代码中的 swap() 函数交换的是 a 和 b 的地址,在交换完毕后,a 和 b 的变量值确实被交换。但和 a、b 关联的两个变量并没有实际关联。这就像写有两座房子的卡片放在桌上一字摊开,交换两座房子的卡片后并不会对两座房子有任何影响。
Vmware虚拟机根盘扩容
磁盘情况
- 查看扩容前的磁盘容量
1
2
3
4
5
6
7
8[root@k8s-node2 ~]# df -h
Filesystem Size Used Avail Use% Mounted on
devtmpfs 2.9G 0 2.9G 0% /dev
tmpfs 2.9G 0 2.9G 0% /dev/shm
tmpfs 2.9G 279M 2.7G 10% /run
tmpfs 2.9G 0 2.9G 0% /sys/fs/cgroup
/dev/mapper/centos-root 17G 13G 4.5G 75% / <-- 17G
/dev/sda1 1014M 187M 828M 19% /boot - 查看磁盘分区情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23[root@k8s-node2 ~]# fdisk -l
Disk /dev/sda: 107.4 GB, 107374182400 bytes, 209715200 sectors <-- 107.4 GB
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x000a6a43
Device Boot Start End Blocks Id System
/dev/sda1 * 2048 2099199 1048576 83 Linux
/dev/sda2 2099200 41943039 19921920 8e Linux LVM
Disk /dev/mapper/centos-root: 18.2 GB, 18249416704 bytes, 35643392 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk /dev/mapper/centos-swap: 2147 MB, 2147483648 bytes, 4194304 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
对扩容的磁盘分区操作
- 磁盘分区命令
1
fdisk /dev/sda
- 分区设置分区格式,在Fdisk命令处输入:t,分区号用默认 3(或回车),Hex代码输入:8e (代表适用Linux LVM分区类型),最后写入分区表,在Fdisk命令位置输入:w
fdisk -l
查看我们新创建的dev/sda3分区了,分区格式为Linux LVM类型。- 不重启的情况下重读分区,马上生效,格式化新增磁盘并分区
1
2partprobe /dev/sda
mkfs.ext3 /dev/sda3 - 进入lvm中合并磁盘
1
2
3
4
5
6#进入lvm
lvm
#初始化/dev/sda3
pvcreate /dev/sda3
#将新分区添加进系统默认的Volume group,centOS的默认Volume group为centos
vgextend centos /dev/sda31
2
3
4
5#查看一下当前的Volume卷详情
vgdisplay -v
#将系统盘/dev/mapper/centos-root与sda3的5119空余容量合并,输入如下命令:
lvextend -l +20479 /dev/mapper/centos-root
quit - 最后查看扩容及磁盘状态
1
2#文件系统进行扩容,以让系统识别,输入如下命令(只适用于CentOS7)
xfs_growfs /dev/mapper/centos-root1
fdisk -l
- 查看系统容量
1
df -h
swiftUI笔记之WCDB实践
目标
为chatGpt聊天窗口添加支持本地持久化,操作方式分为:
1、新增聊天项,即在chat数据库增加数据 (聊天项列表)
1 | CREATE TABLE "main"."chat" ( |
2、新增会话,即在session数据库中增加数据 (会话列表)
1 | CREATE TABLE "main"."session" ( |
可以类比使用微信的过程,数据进行的本地化存储持久化。
实践
封装文件目录操作
我们先封装一下对文件目录的操作
QFileManage.swift1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41import Foundation
struct QFileManage {
/// 创建文件夹
static func createDirectory(at path: String) {
let isExisted = FileManager.default.fileExists(atPath: path)
guard !isExisted else { return }
let url = URL(fileURLWithPath: path)
do {
try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
} catch let error {
debugPrint("创建文件夹失败!Path: \(path), Error: \(error.localizedDescription)")
}
}
}
extension QFileManage {
/// 库目录
static func libraryDirectory() -> String {
return NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true).last!
}
/// 数据库目录
static func databaseDirectory() -> String {
let path = libraryDirectory() + "/database"
createDirectory(at: path)
return path
}
/// 文档目录
static func documentsDirectory() -> String {
return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last!
}
/// 图片目录
static func imagesDirectory() -> String {
let path = documentsDirectory() + "/images"
createDirectory(at: path)
return path
}
}模型绑定
聊天模型:ChatDbModel.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31import Foundation
import WCDBSwift
final class ChatDbModel: TableCodable {
static var tableName: String { "chat" }
var sessionId: Int64 = 0
var chatId: String = ""
var content: String = ""
var isChatgpt: Bool = false
var createTime: Date? = nil
enum CodingKeys: String, CodingTableKey {
typealias Root = ChatDbModel
static let objectRelationalMapping = TableBinding(CodingKeys.self)
case sessionId
case chatId
case content
case isChatgpt
case createTime
}
init(sessionId: Int64, chatId: String, content: String, isChatgpt: Bool, createTime: Date) {
self.sessionId = sessionId
self.chatId = chatId
self.content = content
self.isChatgpt = isChatgpt
self.createTime = createTime
}
}会话模型:
SessionDbModel.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34import Foundation
import WCDBSwift
final class SessionDbModel: TableCodable {
static var tableName: String { "session" }
var sessionId: Int64 = 0
var title: String? = nil
var createTime: Date? = nil
enum CodingKeys: String, CodingTableKey {
typealias Root = SessionDbModel
static let objectRelationalMapping = TableBinding(CodingKeys.self)
case sessionId
case title
case createTime
}
init(sessionId: Int64, title: String? = nil, createTime: Date? = nil) {
self.sessionId = sessionId
self.title = title
self.createTime = createTime
}
}
extension SessionDbModel {
static func insert(objects: [SessionDbModel]) {
do {
try db?.insert(objects, intoTable: SessionDbModel.tableName)
} catch let error {
debugPrint("插入session失败 ->\n\(error.localizedDescription)")
}
}
}启动时,创建数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29import SwiftUI
import WCDBSwift
var db: Database?
@main
struct tableViewApp: App {
@AppStorage("appearance") var appearance: String = "system"
var body: some Scene {
WindowGroup {
ContentView().preferredColorScheme(appearance == "system" ? nil : (appearance == "dark" ? .dark : .light))
}
}
init() {
// 创建数据库
let path = QFileManage.databaseDirectory() + "/chatgpt.db"
debugPrint("数据库路径:\(path)")
db = Database(at:path)
do {
// 建表
try db?.run(transaction: {_ in
try db?.create(table: SessionDbModel.tableName, of: SessionDbModel.self)
try db?.create(table: ChatDbModel.tableName, of: ChatDbModel.self)
})
} catch let error {
debugPrint("创建数据库失败!Error: \(error.localizedDescription)")
}
}
}
操作数据库 TODO: viewModel方式操作
- 新增对话
1
SessionDbModel.insert(objects: [SessionDbModel(sessionId: 2, title: "111", createTime: Date())])
SAP笔记之MM模块初探
总揽
MM 模块的总揽图如下所示:
SAP MM 中的主数据
在 MM 模块中,需要维护和管理的主数据包括:
物料主数据管理
:将企业所有的物料数据集成在单一的物料数据库中,消除了数据冗余的问题,并实现各部门对数据的共享供应商主数据管理
:将为企业进行供应的供应商的信息集成在供应商主数据库中予以统一管理和维护,并实现共享采购主数据管理
:包含与采购活动相关的各类主数据:交易信息记录、货源清单、配额协议、框架协议、合同、订供货时间表、价格条件等
SAP MM 中的主要业务流程
MM 模块主要分为两部分业务功能:采购管理
与库存管理
采购管理
:重点关注物料或是服务的采购,货源的确定,采购订单到货与应付款的监控等;首先是要有需求,有了需求后,就要去找货源,有了货源后就要去选择供应商,通过一系统列的询、报价后,进行价格等方面的比较,选择适合的供应商,选择好供应商后就可以下单给供应商,然后监控这张订单和仓库的回货情况,等仓库全部收完货后,作发票校验。做完后财务就可以付款给供应商。
库存管理
:重点关注物料的移动、物料数量与金额的管理以及库存盘点等。
SAP MM 最常用的业务流程事务代码
MM01 【创建物料主数据】
VD01 【创建客户主数据】
FD01 【维护客户财务数据】
VD51 【创建客户-物料信息记录】
FD32 【更改客户信贷管理】
MK01 【创建供应商主数据】
FK01 【维护供应商财务数据】
ME11 【创建采购信息记录】
ME01 【创建货源清单】
CS01 【创建物料清单BOM】
swiftUI笔记之MenuBarExtra实践(mac应用)
背景
最近在推进我的一款mac应用产品,需使用到menuBar,以前都使用swift appDelegate,现在使用新的MenuBarExtras验证下。
实践
在 macOS 13 Ventura 中,Apple 终于提供了一种实现MenuBarExtras
SwiftUI 方式的方法。它首次在 WWDC 演讲“为您的 SwiftUI 应用程序带来多个窗口”中引入,并使在 Swift UI 中编写实用程序应用程序变得轻而易举。
现在MenuBarExtras可以直接将您的应用程序主体与您的Windows或WindowGroups.
1 | @main |
MenuBarExtras
大多数时候采用三个参数:
TitleKey
: 标识它的字符串。很可能是您的应用程序的名称Image
:菜单栏中显示的符号。最好是,一个SFSymbol。通过这种方式,您可以开箱即用地获得浅色和深色主题行为。Content
: 这几乎可以是任何东西。不过,这取决于所选样式的呈现方式。
有两种预定义样式MenuBarExtras
:
菜单
两者中较容易的是.menu。它是默认样式,将 Menu Bar Extra 的内容呈现为标准菜单。
1 | @main |
当然你也可以显性的使用 .menuBarExtraStyle(.menu)
窗户
窗口样式允许您将任何类型的内容呈现到的MenuBarExtra
弹出窗口中,并且可用于需要更多自定义控件(如滑块或开关)的应用程序。
为了启用窗口样式,将.menuBarExtraStyle修饰符添加到MenuBarExtra并将其设置为.window。
1 | @main |
从 Dock 隐藏应用程序
如果您的应用仅包含一个MenuBarExtra并且不需要额外的窗口,您可以WindowGroup完全删除该窗口。在这些情况下,您很可能也不希望您的应用出现在 Dock 中。
这可以通过UIElement在您的应用程序的info.plist. 代理应用程序不会出现在用户的 Dock 中。
swiftUI笔记之async&await实践MVVM网络请求
实践
我们来实践一下使用MVVM模式,并使用async await进行网络请求。
Library.swift
1 | import Foundation |
LibraryViewModel.swift
1 | import Foundation |
LibraryView.swift
1 | import SwiftUI |
LibraryDetailView.swift
1 |
|
swiftUI笔记之EnvironmentObject之暗黑模式适配实践
我们以暗黑模式的适配来实践EnvironmentObject
暗黑模式适配
定义AppSetting
,设置 @Published var darkModeSettings
1 | class AppSetting: ObservableObject { |
传入 .environmentObject(AppSetting())
1 | @main |
使用@EnvironmentObject
1 |
|