0%

常用命令

1
2
3
4
5
6
7
8
9
10
#查询所有pvc
kubectl get pvc -n kubesphere-devops-system
#查看pvc详情信息
kubectl describe pvc ks-jenkins -n kubesphere-devops-system
#查询所有pv
kubectl get pv
#查看pv详情信息
kubectl describe pv pvc-fe2e8742-a736-4134-b89a-85eef0b6edce
kubectl get sc
kubectl describe sc local

基础概念

jQhr6g.png

  • PV(PersistentVolume) :描述的是持久化存储卷,是集群中的资源。
  • PVC(persistentVolumeClaim):申领是对这些资源的请求,也被用来执行对资源的申领检查。
  • SC(StorageClass):为管理员提供了描述存储 “类” 的方法。

pv和sc都属于集群所有,没有namespace之分,只有pvc才各属于每个空间的资源

reclaimPolicy两种常用取值:Delete、Retain;

Delete:表示删除PVC的时候,PV也会一起删除,同时也删除PV所指向的实际存储空间;
Retain:表示删除PVC的时候,PV不会一起删除,而是变成Released状态等待管理员手动清理;

volumeBindingMode 字段控制了卷绑定和动态制备应该发生在什么时候。 当未设置时,默认使用 Immediate 模式。

Immediate 模式表示一旦创建了 PersistentVolumeClaim 也就完成了卷绑定和动态制备。 对于由于拓扑限制而非集群所有节点可达的存储后端,PersistentVolume 会在不知道 Pod 调度要求的情况下绑定或者制备。

集群管理员可以通过指定 WaitForFirstConsumer 模式来解决此问题。 该模式将延迟 PersistentVolume 的绑定和制备,直到使用该 PersistentVolumeClaim 的 Pod 被创建。 PersistentVolume 会根据 Pod 调度约束指定的拓扑来选择或制备。 这些包括但不限于资源需求节点筛选器Pod 亲和性和互斥性、 以及污点和容忍度

简单nginx挂载持久卷实例

  1. 创建pvc:nginx-pvc.yaml文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
    name: nginx-pvc
    spec:
    accessModes: #访问模式的支持由storageClassName进行限定的
    - ReadWriteOnce
    #ReadWriteOnce(RWO)卷可以被一个节点以读写方式挂载。
    #ReadOnlyMany(ROX)卷可以被多个节点以只读方式挂载。
    #ReadWriteMany(RWX)卷可以被多个节点以读写方式挂载。
    #ReadWriteOncePod(RWOP)卷可以被单个 Pod 以读写方式挂载。
    resources:
    requests:
    storage: 1Gi #申请的资源大小
    storageClassName: local #指定存储类
  2. 执行kubectl apply -f nginx-pvc.yaml,可以通过kubectl get pvc进行查看

  3. 创建pod:nginx-pod-volumes.yaml文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    apiVersion: v1
    kind: Pod
    metadata:
    name: nginx
    labels: #标签
    app: nginx #标签为了后面service的selector
    spec:
    volumes:
    - name: nginx-data
    persistentVolumeClaim:
    claimName: nginx-pvc
    containers:
    - name: nginx
    image: nginx:alpine
    ports:
    - containerPort: 80
    volumeMounts:
    - mountPath: "/usr/share/nginx/html"
    name: nginx-data
  4. 执行kubectl apply -f nginx-pod-volumes.yaml,可以通过kubectl get po进行查看

  5. 创建对应的服务,暴露端口,见k8s 部署基础

  6. 访问nginx服务

  7. 通过kubectl get pvkubectl describe pv pvc-...查看source:path的路径/var/openebs/local/pvc-***

  8. 该路径在其中一个节点,找到有该路径的节点,在该目录新建一个index.html,里面输入hello pvc

  9. 重新访问nginx服务,就能在网页上看到hello pvc

pvc部署文件
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
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: ks-jenkins
namespace: kubesphere-devops-system
labels:
app: ks-jenkins
app.kubernetes.io/managed-by: Helm
chart: jenkins-0.19.0
heritage: Helm
release: ks-jenkins
annotations:
meta.helm.sh/release-name: ks-jenkins
meta.helm.sh/release-namespace: kubesphere-devops-system
pv.kubernetes.io/bind-completed: 'yes'
pv.kubernetes.io/bound-by-controller: 'yes'
volume.beta.kubernetes.io/storage-provisioner: openebs.io/local
volume.kubernetes.io/selected-node: k8s-worker-242-17 #在那个节点上
finalizers:
- kubernetes.io/pvc-protection
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
volumeName: pvc-fe2e8742-a736-4134-b89a-85eef0b6edce #pv的name
storageClassName: local
volumeMode: Filesystem
Pod部署文件:
1
2
3
4
5
6
7
8
9
10
11
12
kind: Pod
apiVersion: v1
spec:
volumes:
- name: jenkins-home #定义挂载卷
persistentVolumeClaim: #使用的pvc
claimName: ks-jenkins #使用的pvc名字
containers:
- name: ks-jenkins
volumeMounts:
- name: jenkins-home #使用的挂载卷
mountPath: /var/jenkins_home #容器内需要挂载的目录
pvc信息解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Name:          ks-jenkins #pvc名字
Namespace: kubesphere-devops-system
StorageClass: local
Status: Bound
Volume: pvc-fe2e8742-a736-4134-b89a-85eef0b6edce #挂载卷名字也就是pv名字
Labels: app=ks-jenkins
app.kubernetes.io/managed-by=Helm
chart=jenkins-0.19.0
heritage=Helm
release=ks-jenkins
Annotations: meta.helm.sh/release-name: ks-jenkins
meta.helm.sh/release-namespace: kubesphere-devops-system
pv.kubernetes.io/bind-completed: yes
pv.kubernetes.io/bound-by-controller: yes
volume.beta.kubernetes.io/storage-provisioner: openebs.io/local
volume.kubernetes.io/selected-node: k8s-worker-242-17 #那个节点
Finalizers: [kubernetes.io/pvc-protection]
Capacity: 20Gi #大小
Access Modes: RWO
VolumeMode: Filesystem
Mounted By: ks-jenkins-76d6b84fdf-qvj98
Events: <none>
pv信息解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Name:              pvc-fe2e8742-a736-4134-b89a-85eef0b6edce #pv名字
Labels: openebs.io/cas-type=local-hostpath
Annotations: pv.kubernetes.io/provisioned-by: openebs.io/local
Finalizers: [kubernetes.io/pv-protection]
StorageClass: local
Status: Bound
Claim: kubesphere-devops-system/ks-jenkins
Reclaim Policy: Delete
Access Modes: RWO
VolumeMode: Filesystem
Capacity: 20Gi
Node Affinity:
Required Terms:
Term 0: kubernetes.io/hostname in [k8s-worker-242-17] #在那个节点
Message:
Source:
Type: LocalVolume (a persistent volume backed by local storage on a node) #类型
Path: /var/openebs/local/pvc-fe2e8742-a736-4134-b89a-85eef0b6edce #在节点的路径
Events: <none>
SC信息解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Name:            local
IsDefaultClass: Yes
Annotations: cas.openebs.io/config=- name: StorageType
value: "hostpath"
- name: BasePath
value: "/var/openebs/local/"
,kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"storage.k8s.io/v1","kind":"StorageClass","metadata":{"annotations":{"cas.openebs.io/config":"- name: StorageType\n value: \"hostpath\"\n- name: BasePath\n value: \"/var/openebs/local/\"\n","openebs.io/cas-type":"local","storageclass.beta.kubernetes.io/is-default-class":"true","storageclass.kubesphere.io/supported-access-modes":"[\"ReadWriteOnce\"]"},"name":"local"},"provisioner":"openebs.io/local","reclaimPolicy":"Delete","volumeBindingMode":"WaitForFirstConsumer"}
,openebs.io/cas-type=local,storageclass.beta.kubernetes.io/is-default-class=true,storageclass.kubesphere.io/support-snapshot=false,storageclass.kubesphere.io/supported-access-modes=["ReadWriteOnce"] #限定pvc的访问模式
Provisioner: openebs.io/local #制备器(包含NFS、CephFS、AzureFile、Local等等)
Parameters: <none>
AllowVolumeExpansion: <unset>
MountOptions: <none>
ReclaimPolicy: Delete
VolumeBindingMode: WaitForFirstConsumer #卷绑定模式
Events: <none>

常见问题

  1. 错误信息,申明错误的访问模式,访问模式由sc存储类定义,local存储类不支持该访问模式

    1
    Warning  ProvisioningFailed    48s (x4 over 2m33s)    openebs.io/local_openebs-localpv-provisioner-7bbb56d7dc-zsq5k_8a90d62c-5e9f-4544-a7bb-50ca898c55a0  failed to provision volume with StorageClass "local": Only support ReadWriteOnce access mode
  2. 使用local存储卷时,节点不会从一个节点游走到另一个节点,因此节点挂机,该服务直接不可用了,损失了高可用,这是因为sc限制了访问模式为ReadWriteOnce,影响了节点亲和性

官方文档

centos7安装helm

1
2
3
4
5
$ curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
$ chmod 700 get_helm.sh
$ ./get_helm.sh
$ helm version
version.BuildInfo{Version:"v3.12.3", GitCommit:"3a31588ad33fe3b89af5a2a54ee1d25bfe6eaa5e", GitTreeState:"clean", GoVersion:"go1.20.7"}

仓库ArtifactHUB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#官方仓库
helm repo add stable https://charts.helm.sh/stable
#常用仓库
helm repo add elastic https://helm.elastic.co
helm repo add gitlab https://charts.gitlab.io
helm repo add harbor https://helm.goharbor.io
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add incubator https://kubernetes-charts-incubator.storage.googleapis.com
helm repo add stable https://kubernetes-charts.storage.googleapis.com
#国内
helm repo add stable http://mirror.azure.cn/kubernetes/charts
helm repo add aliyun https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
helm repo update
#仓库列表
helm repo list

常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# mac 安装helm
brew install helm
#获取服务的配置
helm get values -n namespaseName server-name
# 查看服务的部署历史
helm history -n namespaseName server-name
# 回滚版本
helm rollback -n namespaseName server-name revision
# 查看组件所有配置信息
helm get manifest -n exxk-lab exxk-api-v1
# 查看组件部署的详细信息
helm get manifest -n exxk-lab exxk-api-v1 | kubectl describe -n exxk-lab -f -
#安装
helm upgrade --install my-influxdb influxdata/influxdb --set image.tags=2.5.1-alpine
#删除
helm delete my-influxdb

charts模版

模版目录结构

1
2
3
4
5
6
7
8
9
10
mychart/
Chart.yaml # 文件包含了该chart的描述。你可以从模板中访问它。
values.yaml #父亲values.yml文件,会被-f命令覆盖,-f命令会被--set覆盖
charts/ #可以 包含其他的chart(称之为 子chart)。
templates/ #目录包括了模板文件,Helm会通过模板渲染引擎将所有文件发送到templates/目录中,然后收集模板的结果并发送给k8s。
NOTES.txt # chart的"帮助文本"。这会在你的用户执行helm install时展示给他们
deployment.yaml #创建Kubernetes 工作负载的基本清单
service.yaml #为你的工作负载创建一个 service终端基本清单。
_helpers.tpl #放置可以通过chart复用的模板辅助对象
...

helm upgrade

helm upgrade [RELEASE] [CHART] [flags]

常见参数:

  • -i, --install :如果版本不存在就运行安装
  • --history-max <int>:限制每个版本保存的最大修订数。使用 0 表示无限制(默认 10)
  • -n, --namespace <string>:指定命名空间范围
  • -f, --values <strings>:在 YAML 文件或 URL 中指定值(可以指定多个)
  • --set stringArray: 在命令行设置值(可以用逗号指定多个或单独的值:key1=val1,key2=val2)
1
2
3
4
5
6
7
8
9
10
#env环境变量
CHARTS_REPO = 'https://harbor.exxk.com/chartrepo/baseimage/charts'
CHARTS_NAME = 'appchart-uat-latest.tgz'
CICD_REGISTRY = 'harbor.exxk.com.cn'
HARBOR_NAMESPACE = 'dev-1'
#---示例
#获取当前镜像
CURRENT_IMAGE=`helm3 get values -n exxk exxk-server-v1 | grep repository | awk -F ': ' '{print \\$2}' `
#更新或安装服务
helm3 upgrade -i --history-max=5 exxk-server-v1 $CHARTS_REPO/$CHARTS_NAME -n exxk -f z-k8s-helm/uat/val-exxk-server.yaml --set image.repository=$CICD_REGISTRY/$HARBOR_NAMESPACE/exxk-server:$CICD_SEQUENCE,currentImage=$CURRENT_IMAGE

创建helm chrat

常见语法

1
2
3
4
{{- with .Values.nodeSelector }} #with可以指定变量范围
nodeSelector:
{{- toYaml . | nindent 8 }} #nindent 4 是指换行后加4个空格,toYaml是一个函数,指定映射路径并全部取出。
{{- end }}
1
2
3
4
5
6
7
8
9
10
#mac安装helm
brew install helm
#centos 安装helm
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh --version v3.2.4
#创建项目
helm create nginx-file-brower
#检查依赖
helm lint mychart

常见问题

  1. helm Error: UPGRADE FAILED: another operation (install/upgrade/rollback) is in progress

    错误原因:是因为历史更新有未成功的,导致一直Preparing upgrade

    解决:执行下面命令回退版本,然后重新跑流水线即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [root@k8s-master-242-10 ~]# helm history -n namespaseName server-name
    REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
    12 Wed Jun 1 07:00:04 2022 superseded appchart-uat-latest 1.16.0 Upgrade complete
    13 Thu Jun 2 02:01:17 2022 superseded appchart-uat-latest 1.16.0 Upgrade complete
    14 Thu Jun 2 02:23:25 2022 pending-upgrade appchart-uat-latest 1.16.0 Preparing upgrade
    [root@k8s-master-242-10 ~]# helm rollback -n namespaseName server-name 13
    Rollback was a success! Happy Helming!
    [root@k8s-master-242-10 ~]# helm history -n namespaseName server-name
    REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
    12 Wed Jun 1 07:00:04 2022 superseded appchart-uat-latest 1.16.0 Upgrade complete
    13 Thu Jun 2 02:01:17 2022 superseded appchart-uat-latest 1.16.0 Upgrade complete
    14 Thu Jun 2 02:23:25 2022 pending-upgrade appchart-uat-latest 1.16.0 Preparing upgrade
    15 Tue Jun 28 16:35:18 2022 deployed appchart-uat-latest 1.16.0 Rollback to 13
    [root@mh-k8s-master-242-10 ~]#

常用语法表达式

1
2
3
4
5
6
7
8
9
10
11
12
//----??表达式----
String a;
print(a??'defaul value'); //a为null就打印右边的值,不为空就左边
//----函数缩写----
(){print()}//回掉中只含有一句可以缩写成如下
()=>print()
//-----dynamic 相当于object类型------
Map<String,dynamic> data={}; //初始化map
data['key']="value";//赋值
String a=data['key']; //取值
//----list 如何转 json
String jsonStr= jsonEncode(<List>);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class DraftBean {
String title = "";
String summary = "";
SoftAllBean soft = SoftAllBean();
List<StepBean> steps = [];

DraftBean(); //无参构造

DraftBean.fromJson(Map<String, dynamic> json) //自定义初始化方法
: title = json['title'],
summary = json['summary'],
soft = SoftAllBean.fromJson(json['soft']), //对象初始化
steps = (json['steps'] as List).map((e) => StepBean.fromJson(e)).toList(); //list初始化

Map<String, dynamic> toJson() =>
{'title': title, 'summary': summary, 'soft': soft, 'steps': steps};
}

提示语法错误,检查是否有const修饰,他不能修饰可变的东西。

Flutter项目基本介绍

pubspec.yaml:依赖包管理文件,添加依赖后,点击`Pub get可以进行下载添加的依赖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
name: draft #包名或项目名
description: write a draft #项目描述
publish_to: 'none' # 发布包配置,不发布
version: 1.0.0+1 #项目版本
environment: #环境变量
sdk: ">=2.17.1 <3.0.0" #Dart语言版本
dependencies: #应用或包依赖的其他包或插件,会编译到app应用里
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
dev_dependencies: #开发环境依赖的工具包,用于辅助开发和测试,不会编译到app应用里
flutter_test:
sdk: flutter
flutter_lints: ^2.0.0
flutter: #flutter相关配置
uses-material-design: true

控件(material

  • TextField 输入框

    1
    2
    3
    4
    5
    6
    7
    child: TextField(
    obscureText: true,
    decoration: InputDecoration(
    border: OutlineInputBorder(), //边框样式
    labelText: '用户名', //提示标签
    ),
    ),
  • Text 文本框

    1
    child: Text("欢迎登录"),
  • TextButton 按钮

    1
    2
    3
    4
    5
    6
    child: TextButton(
    onPressed: (){
    print("点击了");
    },
    child: const Text("登录")
    )
  • Autocomplete 自动补齐输入框框(带下拉搜索输入框)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    child: Autocomplete<Soft>(
    displayStringForOption: (Soft option) => option.name, //输入框显示的值
    optionsBuilder: (TextEditingValue textEditingValue) {
    return HttpUtil().getSoftListString(
    context, "20", textEditingValue.text.toLowerCase());
    }, //选项构建,数据来源支持异步
    fieldViewBuilder: (context, textEditingController, focusNode, onFieldSubmitted) {
    return TextField(
    controller: textEditingController, //传递
    focusNode: focusNode, //传递
    decoration: const InputDecoration(
    border: OutlineInputBorder(), labelText: '软件'),
    ); //自定义样式
    }, //输入框自定义
    onSelected: (Soft selection) {
    versionController.text = selection.version;
    }, //选中回掉
    ),
  • Image图片显示

    1
    Image(image: NetworkImage('url'))
  • GestureDetector 给一个控件添加一个事件

    1
    2
    3
    4
    GestureDetector(
    onTap:() //触摸事件
    child: //给该child添加一个触摸事件
    )

布局(layout

  • Column:列布局

  • Row:行布局

    1
    2
    3
    4
    5
    6
    Column( //或者Row布局
    children: [
    const Text('行一'),
    const Text('行二'),
    ]
    )
  • CustomScrollView:滚动布局

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    1. 继承State
    2. 设置中心key
    3. 触发setState((){num++}) 动态更新布局
    4. 基础结构:
    CustomScrollView
    center:
    slivers:[
    SliverList(key: centerKey,delegate:SliverChildBuilderDelegate(childCount:num)),//num是指重复布局几个
    SliverList()),
    ]

启动类

1
2
3
4
5
6
import 'package:flutter/material.dart';
import 'login.dart';

void main() {
runApp(const Login());
}

一个页面(类)基本结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import 'package:flutter/material.dart';

class Login extends StatelessWidget {
const Login({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return MaterialApp(
title: '登录',
theme: ThemeData(
primarySwatch: Colors.blueGrey,
),
home: Scaffold(
appBar: AppBar(
title: const Text('登录'),
),
body: Colum(),
),
);
}
}

简单页面跳转

1
2
//跳转到Home页面 
Navigator.push(context, MaterialPageRoute(builder:(context)=> const Home()));

多复杂页面跳转-路由(router)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//-----主页面
void main() {
runApp(MaterialApp(
routes: { //配置路由
'/': (context) => const Home(),
'/login': (context) => const Login(),
},
));
}
//----跳转到登录页面----
Navigator.pushNamed(context, '/login');
//----登录页面退回主页----
Navigator.pop(context);
//---移除当前页面,再加载/页面,可以用于页面刷新
Navigator.pushReplacementNamed(context, '/');

获取输入框的值

  • 方法一:定一个变量,然后通过输入框的onChanged设置值到变量,然后获取

  • 方法二:设置controller

    1
    2
    3
    4
    5
    TextEditingController userNameController =TextEditingController(); //定义controller
    TextField(
    controller: userNameController, //设置controller
    )
    print("输入了${userNameController.text}"); //获取controller的值

toast提示框-fluttertoast

  1. 添加依赖fluttertoast: ^8.0.9
  2. 导包import 'package:fluttertoast/fluttertoast.dart';
  3. 使用Fluttertoast.showToast(msg: "请登录");

网络请求-dio框架

  1. pubspec.yamldependencies下添加依赖dio: ^4.0.6,然后点击文件顶部Pub get更新依赖

  2. 导入包import 'package:dio/dio.dart';

  3. 简单使用

    1
    2
    3
    4
    5
    6
    /// 登录请求
    Future<Response> loginPost(String userName, String password) async {
    Response response = await Dio.post('/api/v1/user/login',
    data: {'username': userName, 'password': password});
    return response;
    }

文件选择-File Picker

1
2
3
///选择图片文件
FilePickerResult? result = await FilePicker.platform
.pickFiles(type: FileType.image);

文件压缩-flutter_native_image

1
2
File compressedFile = await FlutterNativeImage.compressImage(file.path,
quality: 50);

缓存-文件模式-shared_preferences

工具类用Sputil

问题

  1. 如下错误:

    1
    2
    Error: Not a constant expression.
    onPressed: (){},

    解决:Not a constant expression 关键字const,使用const修饰的变量及方法不能包含可变的,所以onPressed就不可以使用了,const关键字使用要点

    • 不可修改
    • 编译期初始化
    • const 构造函数
  2. 错误:Navigator operation requested with a context that does not include a Navigator.

    解决:启动类添加MaterialApp包括,修改如下

    1
    2
    3
    4
    5
    void main() {
    runApp(const MaterialApp(
    home: Login(),
    ));
    }
  3. flutter 如何动态更新界面或控件?

    解决:使用setState((){})

  4. flutter如何重新加载主页?

    解决:使用Navigator.pushReplacementNamed();Navigator.popAndPushNamed()

  5. flutter的File Picker如何选择相册?

    解决:设置typeFileType.image,完整示例FilePickerResult? result = await FilePicker.platform.pickFiles(type: FileType.image);

  6. dio如何开启网络请求日志?

    解决:添加过滤器_dio.interceptors.add(LogInterceptor(responseBody: true, requestBody: true)); //debug开启权限日志

  7. flutter如何获取输入框的值,或者动态修改输入框的值?

    解决:添加公共变量TextEditingController versionController = TextEditingController(); 然后在TextField设置controller: versionController,最后在其他地方获取或设置输入框的值versionController.text

  8. Row布局如何按比例分配空间大小?

    解决:添加Expanded包装控件,然后设置flex比例,例如1:2

  9. 如何修改输入框提示文字的颜色?

    解决:添加labelStyle: TextStyle(color: Colors.amberAccent)属性

官方英文文档

Flutter中文网

flutter常用命令

1
2
3
flutter run --release
flutter devices #查看连接的设备
flutter doctor #检查环境

安装-环境搭建-debug-运行发布一个应用到手机上

  1. 下载flutter_macos_3.0.1-stable.zip
  2. 解压到~/flutter
  3. 配置环境变量,在~/.zshrc末尾添加export PATH="~/flutter/bin:$PATH"
  4. idea安装flutter插件并附带安装Dart插件
  5. 配置idea的flutter路径~/flutter
  6. 新建一个Flutter项目

IOS设置

  1. 安装Xcode
  2. 设置IOS模拟器,在idea或bash执行open -a Simulator
  3. 在idea项目上面右键,然后点击Flutter->Opne IOS module in Xcode
  4. 然后在Xcode里面点击运行即可
  5. (可选)启用其他平台执行flutter config --enable-linux-desktop
  6. 设置开发者账号,在Xcode-runner-setting-team添加苹果账号
  7. 点击即可debug测试
  8. iOS提示不受信任的开发者,点击设置-通用-VPN与设备管理-添加信任
  9. 断开数据线会提示In iOS 14+,debug mode Flutter apps can only 执行flutter run --release进行发布安装

常见问题

  1. 找不到ios设备,健康检查提示CocoaPods not installed

    解决:执行sudo gem install cocoapods安装cocoapods

    1
    2
    3
    4
    5
    6
    7
    #无sudo安装,报错
    gem install cocoapods --user-install
    #错误信息,解决,用sudo gem install cocoapods安装
    /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/include/ruby-2.6.0/ruby/ruby.h:24:10: fatal error: 'ruby/config.h' file not found
    #include "ruby/config.h"
    ^~~~~~~~~~~~~~~
    /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/include/ruby-2.6.0/ruby/ruby.h:24:10: note: did not find header 'config.h' in framework 'ruby' (loaded from '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks')
  2. 健康检查提示`HTTP host “https://maven.google.com/" is not reachable. Reason: An error

    occurred while checking the HTTP host: Operation timed out
    

    ✗ HTTP host “https://cloud.google.com/" is not reachable. Reason: An error

    occurred while checking the HTTP host: Operation timed out`
    

    解决:在~/.zshrc添加环境如下变量

    1
    2
    3
    # 国内用户需要设置
    export PUB_HOSTED_URL=https://pub.flutter-io.cn
    export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn
  3. (该问题似乎未解决)提示错误Searching for inspections failed: undefined method 'map' for nil:NilClass

    解决:执行sudo arch -x86_64 gem install ffi

  4. 执行flutter doctor提示

    1
    2
    [!] Proxy Configuration
    ! NO_PROXY is not set

    解决:执行vim .zshrc添加export NO_PROXY=localhost,127.0.0.1然后保存。

  5. 苹果IOS:xcode提示“codesign 想要访问您的钥匙串中的密钥”

    解决(最终采用):钥匙串访问APP=>登录=>我的证书=>双击“Apple Development:xxx“(这个选择系统,使用的签名的开发证书)=>访问控制=>允许所有应用

    解决方案二(采用):一直输用户名和密码大概8次左右

  6. 出现错误Error (Xcode): File not found: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_iphoneos.a

    解决:将Podfile文件内容末位post_install do |installer|..... end替换为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    post_install do |installer|
    installer.generated_projects.each do |project|
    project.targets.each do |target|
    target.build_configurations.each do |config|
    config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
    end
    end
    end
    end

    在ios目录,执行命令pod install

Brotli压缩

  • 阿里云CDN的一个功能
  • 同时开启Brotli压缩和智能压缩,且客户端请求头Accept-Encoding同时携带brgzip时,仅Brotli压缩生效。

错误原因:请求头设置了headers.put("Accept-Encoding","gzip, deflate, br");

解决方法:

  1. 解压br
  2. 请求头Accept-Encoding中去掉br

Waline官方文档

LeanCloud数据库添加
  1. 注册登录创建国际版LeanCloud数据库
  2. 绑定db域名
  3. 阿里云解析CNAME域名
  4. 记录APP IDAPP KeyMaster Key
Vercel服务端部署
  1. 使用github授权登录vercel
  2. 点击deploy
  3. 输入仓库名点击创建
  4. 环境变量设置里面添加三个环境变量 LEAN_ID, LEAN_KEYLEAN_MASTER_KEY 值分别为 APP ID, APP KEY, Master Key
  5. 绑定服务端域名
  6. 登录waline.iexxk.com进行管理员注册
hexo博客next主题配置waline评论
  1. 部署脚本文件添加命令npm install @waline/hexo-next

  2. theme/next/_config.yml配置文件添加如下内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # Waline
    # For more information: https://waline.js.org, https://github.com/walinejs/waline
    waline:
    enable: true #是否开启
    serverURL: waline.iexxk.com # Waline #服务端地址,我们这里就是上面部署的 Vercel 地址
    placeholder: 请文明评论呀 # #评论框的默认文字
    avatar: mm # 头像风格
    meta: [nick, mail, link] # 自定义评论框上面的三个输入框的内容
    pageSize: 10 # 评论数量多少时显示分页
    lang: zh-cn # 语言, 可选值: en, zh-cn
    # Warning: 不要同时启用 `waline.visitor` 以及 `leancloud_visitors`.
    visitor: false # 文章阅读统计
    comment_count: true # 如果为 false , 评论数量只会在当前评论页面显示, 主页则不显示
    requiredFields: [] # 设置用户评论时必填的信息,[nick,mail]: [nick] | [nick, mail]
    libUrl: # Set custom library cdn url

常见问题

  1. 版本太低问题,升级到next 8+

    1
    Error: Unable to call `next_data`, which is undefined or falsey

参考:

LeanCloud如何绑定域名

waline如何创建Vercel服务

Vercel如何绑定域名

Hexo博客进阶:为 Next 主题添加 Waline 评论系统

需求

因博客修改了域名,以前收录的旧域名需要自动重定向到新域名,而且百度和google需要添加新站点收录。因为博客采用netlify部署的。

操作步骤

  1. 在阿里云域名修改新域名解析

  2. 修改netlify里面的https认证及新域名添加

  3. 兼容旧域名,添加310重定向,修改hexo博客目录下的netlify.toml文件,增加如下内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [[redirects]]
    from = "https://xuan.netlify.app/*"
    # :splat 是为了原路径保持,不然会跳转到主页
    to = "https://iexxk.com/:splat"
    status = 301
    force = true
    [[redirects]]
    from = "https://blog.iexxk.com/*"
    to = "https://iexxk.com/:splat"
    status = 301
    force = true
  4. 修改百度收录并在网站改版里面添加旧域名和新域名的映射

  5. 修改Google收录

常用命令:

1
2
3
4
#进入容器的一个网络空间,查看网络代理
ps aux|grep 容器名 #显示所有包含其他使用者的行程
nsenter -n --target <上一步的pid> #进入pid的网络空间
iptables -t nat -L #查看iptables规则

概念

  • Istio:开源的服务网格

  • Istio 由两个部分组成:控制平面和数据平面。

  • Istio 主要功能:流量管理、可观测性、安全性能

Istio组件

Components Description
istio-citadel 通过内置身份和凭证管理赋能强大的服务间和最终用户身份验证
istio-galley 代表其他的 Istio 控制平面组件,用来验证用户编写的 Istio API 配置
istio-ingressgateway 提供外网访问的网关
istio-pilot 为 Envoy Sidecar 提供服务发现功能
istio-policy 用于向 Envoy 提供准入策略控制,黑白名单控制,速率限制等相关策略
istio-sidecar-injector 为配置注入的pod自动注入 Sidecar
istio-telemetry 为 Envoy 提供了数据上报和日志搜集服务
jaeger-collector 收集sidecar的数据,Istio 里面 sidecar 就是 jaeger-agent
jaeger-collector-headless 收集 sidecar 的数据,Istio 里面 Sidecar 就是 jaeger-agent
jaeger-query 接收查询请求,然后从后端存储系统中检索 trace 并通过 UI 进行展示
jaeger-operator 负责创建 Jaeger 服务,并在配置更新时自动应用到 jaeger 服务

需要解决的问题

  1. 集群外的服务如何访问。
  2. 两个集群架构不一致,如何互相提供服务。

实战

kiali启用

服务=>Istio-system=>kiali=>更多操作=》编辑外网访问=〉NodePort,然后使用节点ip➕20001对应的NodePort进行访问。

多注册中心实现,修改注册中心(Istio文档)(最新版本移除了Consul

  1. 编辑istiod的配置文件,添加registries参数,该参数要读取的平台服务注册表的逗号分隔列表(从 {Kubernetes, Consul, Mock} 中选择一个或多个)(默认 [Kubernetes]

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    kind: Deployment
    apiVersion: apps/v1
    metadata:
    name: istiod-1-6-10
    namespace: istio-system
    .....
    containers:
    - name: discovery
    image: 'registry.cn-beijing.aliyuncs.com/kubesphereio/pilot:1.6.10'
    args:
    - discovery
    - '--monitoringAddr=:15014'
    - '--registries=Kubernetes,Consul' #新加该参数
    - '--log_output_level=default:info'
    - '--domain'
    - cluster.local
    - '--trust-domain=cluster.local'
    - '--keepaliveMaxServerConnectionAge'
    - 30m
    .....

第三方注册中心集成到Isito

dubbo注册转istio(dubbo2istio)

如何将第三方服务中心注册集成到 Istio ?

Dubbo to Mesh 云原生架构改造方案解析

Pilot 服务模型源码分析

SpringCloud使用K8S(etcd)作为注册中心(Spring Cloud Kubernetes官方文档

SpringCloud project transformed into Spring Cloud Kubernetes project

替换eureka依赖为:

1
2
3
4
5
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-client</artifactId>
<version>2.1.2</version>
</dependency>

Dubbo改造k8s云原生项目

Dubbo Mesh 总体架构

Dubbo 3.0云原生架构

Dubbo Sidecar Mesh(完全改造模型)

使用实战
  1. 消费者替换或添加dubbo依赖

    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
    <dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo</artifactId>
    <version>3.1.5</version>
    </dependency>
    <dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.20.3</version>
    </dependency>
    <dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-dependencies-zookeeper-curator5</artifactId>
    <version>3.1.5</version>
    <type>pom</type>
    <exclusions>
    <exclusion>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    </exclusion>
    <exclusion>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    <dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-all</artifactId>
    <version>1.31.1</version>
    </dependency>
  2. 添加提供者配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    server.port = 8081
    dubbo.application.name= exxk-provider
    dubbo.registry.address= N/A
    #传输协议tri、dubbo
    dubbo.protocol.name=tri
    dubbo.protocol.port=50052
    dubbo.application.qosEnable=true
    # 为了使 Kubernetes 集群能够正常访问到探针,需要开启 QOS 允许远程访问,此操作有可能带来安全风险,请仔细评估后再打开
    dubbo.application.qosAcceptForeignIp=true

Dubbo Proxyless Mesh(兼容模型)

自动注入Sidecar

请注意,区别于手动注入,自动注入发生在 Pod 层面。您将看不到 Deployment 本身有任何更改。取而代之,需要检查单独的 Pod(使用 kubectl describe)来查询被注入的代理。

1
2
3
kubectl label namespace exxk-lab istio-injection=enabled --overwrite #为exxk-lab命名空间添加标签就会开启自动注入,该功能开启只对新部署的pod生效,旧的需要重启
kubectl get namespace -L istio-injection #查看那些空间开启了自动注入
kubectl label namespace exxk-lab istio-injection- #删除istio-injection标签,禁用命名空间的注入

问题

  1. 自动注入未生效

    检查集群->容器足->kube-apiserver*->进入容器控制台->执行kube-apiserver -h | grep enable-admission-plugins

    查看NamespaceLifecycle,LimitRanger是否启用

    1
    2
    3
    4
    5
    #未启用执行下面启用命令,未实验
    kube-apiserver --enable-admission-plugins=NamespaceLifecycle,LimitRanger ...
    #手动在deployment添加注解 sidecar.istio.io/inject: 'true' 即可生效产生istio-proxy容器
    annotations:
    sidecar.istio.io/inject: 'true'
  2. 开启mesh功能,会影响remote debug功能,idea提示如下错误

    1
    Unable to open debugger port (ip:5005): java.io.IOException "handshake failed - unrecognized message from target VM"

    代码会提示:

    1
    Debugger failed to attach: handshake failed - received >GET / HTTP/1.1< - expected >JDWP-Handshake<

    相关测试命令:jdb -attach ip:5005

微服务的通信模式

概念

  • 微服务(Microservices)是一种架构风格,一个大型复杂应用服务由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务,每个任务代表着一个小的业务能力。

    优势 成本
    降低复杂度、可独立部署、容错、扩展 资源增加、沟通成本、网络通信开销、网络安全、服务监控、运维成本
  • RPC:请求/响应式的通信技术,它使得我们可以像调用本地函数一样调用一个远程服务,通常能够自动生成客户端和服务端的代码,常用于服务间的访问。

  • REST:是基于HTTP(s)的RESTful风格的通信技术,架构模型倾向于遵循 HTTP 协议,浏览器对其兼容性更好。数据结构默认使用Json/Xml。

  • gRPC:是基于 RCP 架构的变体,该技术遵循一个使用 HTTP 2.0 协议的 RPC API 实现。数据结构默认使用Protobuf。

    比较 gRPC 服务和 HTTP API

问题背景

  1. 现有微服务架构,涵盖了dubbo、SpringCloud、K8s多个框架,有些冗余不统一。

  2. 现有微服务架构,有安全隐患,在服务与服务之间的内部访问采用无权限校验时,存在接口外露的安全问题。

    例如:使用feign的时候,你要给别的服务提供接口,就要对feign的接口不做登录校验,然后feign的服务提供不同于dubbo是单独的一个端口,因此对服务的端口进行nginx对外配置时,也让内部服务队外进行暴露了,如果不在nginx或网关做特殊处理,外网随时可以访问内部接口,且没有安全认知机制。

  3. 由于服务网格的风生水起,作为一个研究方向还是值得去研究的。

微服务通信主要解决的问题?

服务之间访问API的便利性

  • DUBBO RPC: 有dubbo api jar支持,使用函数式访问调用,只用为对方提供一个api jar包即可,文档要求低,不易出错,采用RPC封装,可以使用Protobuf压缩网络资源的消耗。

  • Spring feign: 也能生成feign的接口 jar为其他服务提供调用,而且支持文档自动生成,不易出错。

  • Http:http直接访问的模式很多,可以用很多工具例如hutool等,但是文档要求高、而且需要知道提供者服务的ip、域名、在k8s模式下访问其他服务,需要知道其他服务在k8s中的服务名。

复原能力

  • 重试
  • 熔断

负载均衡

服务A调用服务B,服务B为一组Pod,存在多个ip地址,默认情况通过负载均衡实现随机访问一个pod。

分布式追踪

链路追踪,快速排除服务之间访问的问题。

服务版本控制

解决服务之前版本依赖问题。

微服务通信设计

服务间通信越少,性能越好。

交互类型:

查询:一个服务向另一个服务查询数据。
  • 请求/响应消息传送:简单的微服务之间调用,层级比较少时适用

    上图就是层级较多时,调用链复杂,存在大量耦合。

  • 具体化视图模式

  • 聚合器微服务:减少服务依赖。

    此模式隔离了对多个后端微服务进行调用的操作,将其逻辑集中到专门的微服务中。 上图中的紫色签出聚合器微服务协调签出操作的工作流。 它包括按顺序调用多个后端微服务。 工作流中的数据将聚合并返回给调用方。 尽管它仍实现直接 HTTP 调用,但聚合器微服务减少了后端微服务之间的直接依赖关系。

  • 请求/答复模式:利用队列进行设计。

命令:一个服务让另一个服务执行一些操作。
事件:订阅通知模式。

K8S服务与服务之间访问

Service的类型

  1. clusterIP:通过集群内部IP地址暴露服务,但该地址仅在集群内部可见、可达,它无法被集群外部的客户端访问;默认类型;建议由K8S动态指定一个,也支持用户手动明确指定;

    特点:安全性高,内部访问速度比较快,不支持跨集群访问。

  2. NodePort: NodePort是ClusterIP的增强类型,它会于ClusterIP的功能之外,在每个节点上使用一个相同的端口号将外部流量引入到该Service上来。

    特点:支持跨集群访问,方便外部访问调试、安全性低(接口暴露)

  3. LoadBalancer: 是NodePort的增强类型,为各节点上的NodePort提供一个外部负载均衡器;需要公有云支持

  4. ExternalName:外部流程引入到K8S内部,借助集群上KubeDNS来实现,服务的名称会被解析为一个CNAME记录,而CNAME名称会被DNS解析为集群外部的服务的TP地址,实现内部服务与外部服务的数据交互 ExternallP 可以与ClusterIP、NodePort一起使用 使用其中一个IP做出口IP

pSDSCcR.png

问题:

  1. feign访问如果没有网关,内部服务之间透明访问,存在安全隐患
  2. nodePort需要固定,随机分配,迁移复制能力弱,nodeport资源占用大
  3. eureka注册和dubbo注册需要使用节点地址和端口

Spring Cloud

Spring家族产品,包含一系列组件,其实就是整个Spring生态组件框架的统称。也是基于SpringBoot框架的基础上与众多组件框架相结合从而形成一个整的微服务架构生态,SpringCloud提供配置管理,服务智能,断路器,智能路由,微代理,控制总线,全局锁,决策竞选分布式会话和集群状态管理一系列解决方案。

spring Cloud架构

spring Cloud架构

优缺点

优势 缺点
在spring开发体系下,原生支持SpringCloud 强依赖java语言
基于HTTP通信协议门槛低
提供大量组件(生态完善)

Dubbo

Dubbo是阿里巴巴公司自主研发并开源的一款高性能、轻量级的开源RPC分布式服务框架,服务消费方引用服务提供方二方库本地化调用远程接口,通过注册中心zookeeper做服务提供者、服务消费者服务的注册与发现来实现远程服务间的数据交互,支持服务智能容错、智能均衡、灰度,可与Spring集成。

Dubbo 服务架构

Dubbo 服务架构

优缺点

优势 缺点
灵活的通信协议(HTTP、gRPC)

Istio mesh

主要解决:日志记录、监控、流量控制、服务注册、服务发现、服务限流、服务熔断、鉴权、访问控制和服务调用可视化等。

服务网格是处理服务间通信的软件层。 服务网格旨在解决上一部分中列出的许多问题,并将这些问题的责任从微服务本身转移到共享层。 服务网格充当代理,可截获群集中微服务之间的网络通信。 目前,服务网格概念主要适用于容器业务流程协调程序,而不是无服务器体系结构。

目前,Kubernetes 中服务网格的主要选项包括 LinkerdIstio。 这两种技术正在快速演进。 但是,Linkerd 和 Istio 具有一些共同的功能,包括:

  • 根据观测到的延迟或未完成的请求数,在会话级别进行负载均衡。 这样,便可以基于 Kubernetes 提供的第 4 层负载均衡来提高性能。
  • 基于 URL 路径、主机标头、API 版本或其他应用程序级规则进行第 7 层路由。
  • 失败请求重试。 服务网格可识别 HTTP 错误代码,并可以自动重试失败的请求。 可以配置最大重试次数和超时期限,以限制最大延迟。
  • 断路。 如果某个实例一直无法完成请求,则服务网格会暂时性地将它标记为不可用。 在回退期过后,服务将重试该实例。 可以根据多个条件(例如连续失败次数)来配置断路器。
  • 服务网格会捕获有关服务间调用的指标,例如请求量、延迟、错误和成功率,以及响应大小。 此外,服务网格可以通过添加请求中每个跃点的关联信息,来启用分布式跟踪。
  • 服务间调用的相互 TLS 身份验证。

是否需要服务网格? 视情况而定。 如果不使用服务网格,则需要考虑本文开头所述的难题。 不使用服务网格也可以解决重试、断路器和分布式跟踪等方面的问题,但是,服务网格可将这些问题从单个服务转移到专用的层。 另一方面,服务网格会增大群集设置和配置的复杂性。 另外,它可能造成性能影响,因为请求现在是通过服务网格代理路由的,并且附加的服务在群集中的每个节点上运行。 在生产环境中部署服务网格之前,应该执行全面的性能和负载测试。

Istio 服务网格架构

 Istio架构

优缺点

优势 缺点
语言无关(兼容多种平台语言) 增加服务间延迟
减少应用的连接数(例如:多个微服务连接数据库) 增加运维复杂
容器友好,和k8s集成度高 强依赖k8s平台
可视化程度高(方便监控服务间调用情况/apm链路追踪) 增加硬件资源
支持动态调整流量、负载均衡等

注册中心兼容比较

Nacos Eureka Consul CoreDNS Zookeeper etcd(k8s)
一致性协议 CP+AP AP(可用性高) CP(一致性高) CP CP
一致性算法 Raft Raft Paxos Raft
健康检查 TCP/HTTP/
MYSQL/Client Beat
Client Beat TCP/HTTP/
gRPC/Cmd
Keep Alive 连接心条
负载均衡策略 权重/
metadata/Selector
Ribbon Fabio RoundRobin Envoy
雪崩保护
自动注销实例 支持 支持 不支持 不支持 支持
访问协议 HTTP/DNS HTTP HTTP/DNS DNS TCP HTTP/GRPC
监听支持 支持 支持 支持 不支持 支持 支持
多数据中心 支持 支持 支持 不支持 不支持
跨注册中心同步 支持 不支持 支持 不支持 不支持
SpringCloud集成 支持 支持 支持 不支持 支持 支持
Dubbo集成 支持 不支持 不支持 不支持 支持
K8S集成 支持 不支持 支持 支持 不支持 支持

参考

为微服务设计服务间通信