diff --git a/404.html b/404.html index 72de055f..200bdaf8 100644 --- a/404.html +++ b/404.html @@ -1,2 +1,2 @@ 404 Page not found | 秋河落叶 -

404

Page Not Found

秋河落叶

\ No newline at end of file +

404

Page Not Found

秋河落叶

\ No newline at end of file diff --git a/algo/index.html b/algo/index.html index 1a217efc..dda69965 100644 --- a/algo/index.html +++ b/algo/index.html @@ -3,7 +3,7 @@ 快速排序">Algo | 秋河落叶 - +
Algo

🏠 首页 / 数据结构与算法

数据结构与算法 diff --git "a/algo/\345\240\206\346\216\222\345\272\217/index.html" "b/algo/\345\240\206\346\216\222\345\272\217/index.html" index d9bd983f..3fb0d32b 100644 --- "a/algo/\345\240\206\346\216\222\345\272\217/index.html" +++ "b/algo/\345\240\206\346\216\222\345\272\217/index.html" @@ -5,7 +5,7 @@ 堆排序 # 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种**选择排序,**它的最坏,最好,平均时间复杂度均为 O(nlogn),它也是不稳定排序。首先简单了解下堆结构。 堆 # 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。: 算法实现(golang) # package main import "fmt" type BinaryTreeNode struct { Value int Left, Right *BinaryTreeNode } func main() { tree := &BinaryTreeNode{ Left: &BinaryTreeNode{ Left: &BinaryTreeNode{ Value: 1, }, Right: &BinaryTreeNode{ Value: 2, }, Value: 3, }, Right: &BinaryTreeNode{ Value: 4, }, Value: 5, } res := HeapSort(tree) fmt.Println(res) } func HeapSort(tree *BinaryTreeNode) []int { var res []int if tree == nil { return []int{} } res = heapSortHelper(tree, res) return res } func heapSortHelper(tree *BinaryTreeNode, res []int) []int { if tree.'>堆排序 | 秋河落叶 - +
堆排序

🏠 首页 / diff --git "a/algo/\345\277\253\351\200\237\346\216\222\345\272\217/index.html" "b/algo/\345\277\253\351\200\237\346\216\222\345\272\217/index.html" index 28581cae..7ebc34da 100644 --- "a/algo/\345\277\253\351\200\237\346\216\222\345\272\217/index.html" +++ "b/algo/\345\277\253\351\200\237\346\216\222\345\272\217/index.html" @@ -5,7 +5,7 @@ 快速排序 # 步骤如下: 先从数列中取出一个数作为基准数。一般取第一个数。 分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。 再对左右区间重复第二步,直到各区间只有一个数。 举一个例子:5 9 1 6 8 14 6 49 25 4 6 3。 一般取第一个数 5 作为基准,从它左边和最后一个数使用[]进行标志, 如果左边的数比基准数大,那么该数要往右边扔,也就是两个[]数交换,这样大于它的数就在右边了,然后右边[]数左移,否则左边[]数右移。 5 [9] 1 6 8 14 6 49 25 4 6 [3] 因为 9 > 5,两个[]交换位置后,右边[]左移 5 [3] 1 6 8 14 6 49 25 4 [6] 9 因为 3 !> 5,两个[]不需要交换,左边[]右移 5 3 [1] 6 8 14 6 49 25 4 [6] 9 因为 1 !">快速排序 | 秋河落叶 - +

快速排序

🏠 首页 / diff --git a/aws/build-eks-cluster/index.html b/aws/build-eks-cluster/index.html index a0c7be2e..336de8b1 100644 --- a/aws/build-eks-cluster/index.html +++ b/aws/build-eks-cluster/index.html @@ -3,7 +3,7 @@ 安装示例(windows) # 下载安装包: https://awscli.amazonaws.com/AWSCLIV2.msi 运行下载的安装包 确实安装是否成功 aws --version 使用Security Credentials配置aws cli # 访问aws控制台:Service => IAM 选择IAM User,使用子用户,强烈不建议使用root用户 进入用户详情页面,Security Credentials页 创建Access Key 拷贝Access Key ID和Secret Access Key 使用aws命令配置 $ aws configure AWS Access Key ID [None]: ABCDEFGHIAZBERTUCNGG (替换Access Key ID) AWS Secret Access Key [None]: uMe7fumK1IdDB094q2sGFhM5Bqt3HQRw3IHZzBDTm (替换Secret Access Key) Default region name [None]: us-east-1 Default output format [None]: json 测试配置是否生效 aws ec2 describe-vpcs 卸载(windows) # 控制面板 => 程序和功能,找到aws cli,卸载即可。 安装kubectl cli # 如果你确实是使用EKS的Kubernetes,建议使用aws提供的kubectl命令工具。">Build Eks Cluster | 秋河落叶 - +

Build Eks Cluster

🏠 首页 / diff --git a/aws/cluster-autoscaler/index.html b/aws/cluster-autoscaler/index.html index 80c92a94..e0f50aaf 100644 --- a/aws/cluster-autoscaler/index.html +++ b/aws/cluster-autoscaler/index.html @@ -29,7 +29,7 @@ 添加Policy # 创建IAM策略,将新创建的策略Attach到集群节点绑定的IAM role上,让你的集群节点拥有自动伸缩的能力。 IAM策略Json内容: { "Version": "2012-10-17", "Statement": [ { "Action": [ "autoscaling:DescribeAutoScalingGroups", "autoscaling:DescribeAutoScalingInstances", "autoscaling:DescribeLaunchConfigurations", "autoscaling:DescribeTags", "autoscaling:SetDesiredCapacity", "autoscaling:TerminateInstanceInAutoScalingGroup", "ec2:DescribeLaunchTemplateVersions" ], "Resource": "*", "Effect": "Allow" } ] } 部署Cluster AutoScaler # 在集群中部署Cluster AutoScaler,准备资源清单文件cluster-autoscaler.'>Cluster Autoscaler | 秋河落叶 - +

Cluster Autoscaler

🏠 首页 / diff --git a/aws/create-eks-cluster/index.html b/aws/create-eks-cluster/index.html index 85e8252f..59a88153 100644 --- a/aws/create-eks-cluster/index.html +++ b/aws/create-eks-cluster/index.html @@ -9,7 +9,7 @@ eksctl更多了解 https://eksctl.io 2.2 为什么用eksctl # 创建EKS集群可以在AWS的控制台创建,也可以使用AWS开发的eksctl工具创建,为什么选择使用eksctl创建eks集群呢,有以下几点原因: 直接在AWS的控制台创建集群,需要手动创建各种Role,以及选择合适的Subnet,Security Group等繁杂操作,你需要在浏览器中打开多个页面,操作过程可能也要时不时参阅文档; eksctl创建EKS集群只需要一行eksctl create cluster <参数>命令即可,会自动的给你创建Role等资源; eksctl的命令可以记录到脚本,便于复用。 2.3 安装eksctl(基于ubuntu) # 使用以下命令下载并提取最新版本的 eksctl。 curl --silent --location "https://github.com/weaveworks/eksctl/releases/download/latest_release/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp 将提取的二进制文件移至 /usr/local/bin。 sudo mv /tmp/eksctl /usr/local/bin 使用以下命令测试您的安装是否成功。 eksctl version 注意:'>Create Eks Cluster | 秋河落叶 - +

Create Eks Cluster

🏠 首页 / diff --git a/aws/eks-config-alb-ingress/index.html b/aws/eks-config-alb-ingress/index.html index fa13a6f0..07218da3 100644 --- a/aws/eks-config-alb-ingress/index.html +++ b/aws/eks-config-alb-ingress/index.html @@ -7,7 +7,7 @@ 部署Alb Ingress Controller # IAM中创建Policy,给集群的Node节点的Role添加该Policy。 Policy的JSON配置如下: { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "acm:DescribeCertificate", "acm:ListCertificates", "acm:GetCertificate" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "ec2:AuthorizeSecurityGroupIngress", "ec2:CreateSecurityGroup", "ec2:CreateTags", "ec2:DeleteTags", "ec2:DeleteSecurityGroup", "ec2:DescribeAccountAttributes", "ec2:DescribeAddresses", "ec2:DescribeInstances", "ec2:DescribeInstanceStatus", "ec2:DescribeInternetGateways", "ec2:DescribeNetworkInterfaces", "ec2:DescribeSecurityGroups", "ec2:DescribeSubnets", "ec2:DescribeTags", "ec2:DescribeVpcs", "ec2:ModifyInstanceAttribute", "ec2:ModifyNetworkInterfaceAttribute", "ec2:RevokeSecurityGroupIngress" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "elasticloadbalancing:AddListenerCertificates", "elasticloadbalancing:AddTags", "elasticloadbalancing:CreateListener", "elasticloadbalancing:CreateLoadBalancer", "elasticloadbalancing:CreateRule", "elasticloadbalancing:CreateTargetGroup", "elasticloadbalancing:DeleteListener", "elasticloadbalancing:DeleteLoadBalancer", "elasticloadbalancing:DeleteRule", "elasticloadbalancing:DeleteTargetGroup", "elasticloadbalancing:DeregisterTargets", "elasticloadbalancing:DescribeListenerCertificates", "elasticloadbalancing:DescribeListeners", "elasticloadbalancing:DescribeLoadBalancers", "elasticloadbalancing:DescribeLoadBalancerAttributes", "elasticloadbalancing:DescribeRules", "elasticloadbalancing:DescribeSSLPolicies", "elasticloadbalancing:DescribeTags", "elasticloadbalancing:DescribeTargetGroups", "elasticloadbalancing:DescribeTargetGroupAttributes", "elasticloadbalancing:DescribeTargetHealth", "elasticloadbalancing:ModifyListener", "elasticloadbalancing:ModifyLoadBalancerAttributes", "elasticloadbalancing:ModifyRule", "elasticloadbalancing:ModifyTargetGroup", "elasticloadbalancing:ModifyTargetGroupAttributes", "elasticloadbalancing:RegisterTargets", "elasticloadbalancing:RemoveListenerCertificates", "elasticloadbalancing:RemoveTags", "elasticloadbalancing:SetIpAddressType", "elasticloadbalancing:SetSecurityGroups", "elasticloadbalancing:SetSubnets", "elasticloadbalancing:SetWebACL" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "iam:CreateServiceLinkedRole", "iam:GetServerCertificate", "iam:ListServerCertificates" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "cognito-idp:DescribeUserPoolClient" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "waf-regional:GetWebACLForResource", "waf-regional:GetWebACL", "waf-regional:AssociateWebACL", "waf-regional:DisassociateWebACL" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "tag:GetResources", "tag:TagResources" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "waf:GetWebACL" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "shield:DescribeProtection", "shield:GetSubscriptionState", "shield:DeleteProtection", "shield:CreateProtection", "shield:DescribeSubscription", "shield:ListProtections" ], "Resource": "*" } ] } 下载alb-ingress-controller.'>Eks Config Alb Ingress | 秋河落叶 - +

Eks Config Alb Ingress

🏠 首页 / diff --git a/aws/eks-details/index.html b/aws/eks-details/index.html index d8c19143..6cfb1276 100644 --- a/aws/eks-details/index.html +++ b/aws/eks-details/index.html @@ -5,7 +5,7 @@ EKS小细节汇总 # 如果alb的ingress使用了自定义的security group,那么需要将该安全组加入到worker « EKS配置 ALB Ingress » EKS实践 集成Gitlab自动发布(一)">Eks Details | 秋河落叶 - +

Eks Details

🏠 首页 / diff --git a/aws/eks-intergrate-gitlab-auto-release-01/index.html b/aws/eks-intergrate-gitlab-auto-release-01/index.html index c935a432..4dced192 100644 --- a/aws/eks-intergrate-gitlab-auto-release-01/index.html +++ b/aws/eks-intergrate-gitlab-auto-release-01/index.html @@ -23,7 +23,7 @@ API URL: 运行以下命令得到输出值: kubectl cluster-info | grep 'Kubernetes master' | awk '/http/ {print $NF}' CA Certificate: 运行以下命令得到输出值: kubectl get secret $(kubectl get secret | grep default-token | awk '{print $1}') -o jsonpath="{['data']['ca\.">Eks Intergrate Gitlab Auto Release 01 | 秋河落叶 - +

Eks Intergrate Gitlab Auto Release 01

🏠 首页 / diff --git a/aws/eks-intergrate-gitlab-auto-release-02/index.html b/aws/eks-intergrate-gitlab-auto-release-02/index.html index 51726f41..535f74e8 100644 --- a/aws/eks-intergrate-gitlab-auto-release-02/index.html +++ b/aws/eks-intergrate-gitlab-auto-release-02/index.html @@ -19,7 +19,7 @@ docker build . -t gitlab-runner-base:latest --rm --no-cache docker push gitlab-runner-base:latest 仓库配置 # 项目仓库根目录下新增.gitlab-ci.yml文件,然后文件内容 « EKS实践 集成Gitlab自动发布(一) » EKS-使用EFS">Eks Intergrate Gitlab Auto Release 02 | 秋河落叶 - +

Eks Intergrate Gitlab Auto Release 02

🏠 首页 / diff --git a/aws/eks-use-efs/index.html b/aws/eks-use-efs/index.html index a76b700d..c652bfe9 100644 --- a/aws/eks-use-efs/index.html +++ b/aws/eks-use-efs/index.html @@ -5,7 +5,7 @@ EKS-使用EFS # 创建EFS aws efs create-file-system \ --performance-mode generalPurpose \ --throughput-mode bursting \ --encrypted \ --tags Key=Name,Value= Key=creator,Value=dp Key=env:dev,Value=1 # 上面的命令会得到fs-id aws efs create-mount-target \ --file-system-id \ --subnet-id subnet-08d7609e614373fb8 \ --security-groups sg-0af0f0e8705380529 aws efs create-mount-target \ --file-system-id \ --subnet-id subnet-09c0707ea8ad281bb \ --security-groups sg-0af0f0e8705380529 aws efs create-mount-target \ --file-system-id \ --subnet-id subnet-063a8f10feb97868d \ --security-groups sg-0af0f0e8705380529 « EKS实践 集成Gitlab自动发布(二) » Gitlab & EKS">Eks Use Efs | 秋河落叶 - +

Eks Use Efs

🏠 首页 / diff --git a/aws/gitlab-eks/index.html b/aws/gitlab-eks/index.html index f887884e..bfed535c 100644 --- a/aws/gitlab-eks/index.html +++ b/aws/gitlab-eks/index.html @@ -13,7 +13,7 @@ - "groups": - "system:masters" "userarn": "arn:aws:iam::xxxxxxx:user/gitlab-ci" "username": "gitlab-ci" Gitlab仓库设置 # Setting => CI/CD => Variables,添加变量: AWS_ACCESS_KEY_ID: AWS_SECRET_ACCESS_KEY: Gitlab仓库.gitlab-ci.yml # « EKS-使用EFS » K8s 部署 Kong 服务'>Gitlab Eks | 秋河落叶 - +

Gitlab Eks

🏠 首页 / diff --git a/aws/index.html b/aws/index.html index fb6ab1b5..9133f412 100644 --- a/aws/index.html +++ b/aws/index.html @@ -25,7 +25,7 @@ K8s 部署 konga K8s 部署 Postgres Terraform 重新管理资源">Aws | 秋河落叶 - +

Aws

🏠 首页 / AWS

AWS diff --git a/aws/k8s-deploy-kong/index.html b/aws/k8s-deploy-kong/index.html index 17d8ae6e..862a29a1 100644 --- a/aws/k8s-deploy-kong/index.html +++ b/aws/k8s-deploy-kong/index.html @@ -7,7 +7,7 @@ 提前准备 # K8s 集群,本文使用的是 AWS EKS 集群服务 一台可以连接 K8s 集群的服务器,已经安装 kubectl 和 docker 等基础应用,之后称之为操作机器 Postgres 数据库,作为 Kong 服务的后端数据库 初始化数据库 # 使用 Postgres 作为 Kong 服务的后端数据库,我们需要提前做数据库的初始化,准备 Kong 服务需要的数据表等。这里使用 K8s-Job 来实现数据库的初始化工作。 kong-migrations-job.yaml: apiVersion: batch/v1 kind: Job metadata: name: kong-migrations namespace: kong spec: template: metadata: name: kong-migrations spec: containers: - command: - /bin/sh - -c - kong migrations bootstrap env: - name: KONG_PG_PASSWORD value: "kong" - name: KONG_PG_HOST value: "postgres/postgres" - name: KONG_PG_PORT value: "5432" image: kong:1.'>K8s Deploy Kong | 秋河落叶 - +
K8s Deploy Kong

🏠 首页 / diff --git a/aws/k8s-deploy-konga/index.html b/aws/k8s-deploy-konga/index.html index 4d40ae34..455b1b1f 100644 --- a/aws/k8s-deploy-konga/index.html +++ b/aws/k8s-deploy-konga/index.html @@ -11,7 +11,7 @@ 确保 K8s 集群中已经创建了 nginx-ingress,nginx-ingress 用于根据定制的 Rule(如后文 kong-ingress 的配置)将流量转发至 K8s 集群的 Service 中去。 创建 nginx-ingress 指令(可参照 https://kubernetes.github.io/ingress-nginx/deploy/)步骤如下: Step 1. 执行以下强制命令 kubectl apply -f https://raw.">K8s Deploy Konga | 秋河落叶 - +

K8s Deploy Konga

🏠 首页 / diff --git a/aws/k8s-deploy-postgres/index.html b/aws/k8s-deploy-postgres/index.html index 4b29fae1..a75cc201 100644 --- a/aws/k8s-deploy-postgres/index.html +++ b/aws/k8s-deploy-postgres/index.html @@ -13,7 +13,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: postgres-config namespace: postgres labels: app: postgres data: POSTGRES_DB: master POSTGRES_USER: dba POSTGRES_PASSWORD: pg_pass 这里的数据库密码涉及到信息敏感,更建议使用 Secret 资源而非 ConfigMap,这里就偷懒了。 postgres-statefulset.yaml: apiVersion: apps/v1 kind: StatefulSet metadata: name: postgres namespace: postgres spec: serviceName: "postgres" replicas: 1 selector: matchLabels: app: postgres template: metadata: labels: app: postgres spec: containers: - name: postgres image: postgres:9.'>K8s Deploy Postgres | 秋河落叶 - +

K8s Deploy Postgres

🏠 首页 / diff --git a/aws/terraform-remanage-resource/index.html b/aws/terraform-remanage-resource/index.html index 14098c53..e115bba1 100644 --- a/aws/terraform-remanage-resource/index.html +++ b/aws/terraform-remanage-resource/index.html @@ -5,7 +5,7 @@ Terraform 重新管理资源 # 看到这个标题你可能会有点懵,我先来解释下。 在使用Terraform管理AWS的VPC-Subnet资源时(下面是定义资源的代码清单),我遇到了一个问题:当我修改aws_subnet.eks-private-subnet-1资源的cidr_block时,假设我修改成了172.28.2.0/24,这时候旧的 resource "aws_subnet" "eks-private-subnet-1" { vpc_id = "${var.vpc_id}" cidr_block = "172.28.1.0/24" map_public_ip_on_launch = "false" availability_zone = "${var.region}a" tags = merge( {Name = "${var.cluster_name}-private-subnet-1a"}, "${local.cluster_private_subnet_tags}") } « K8s 部署 Postgres'>Terraform Remanage Resource | 秋河落叶 - +

Terraform Remanage Resource

🏠 首页 / diff --git a/categories/index.html b/categories/index.html index 734e84e7..ec74031b 100644 --- a/categories/index.html +++ b/categories/index.html @@ -1,5 +1,5 @@ Categories | 秋河落叶 - +

Categories
\ No newline at end of file diff --git a/cka/001/index.html b/cka/001/index.html index 4efbc2d4..bb4d00ef 100644 --- a/cka/001/index.html +++ b/cka/001/index.html @@ -3,7 +3,7 @@ Deployment StatefulSet DaemonSet Create a new ServiceAccount named cicd-token in the existing namespace app-team1. Limited to namespace app-team1, bind the new ClusterRole deployment-clusterrole to the new ServiceAccount cicd-token. kubectl create ns app-team1 kubectl create serviceaccount cicd-token -n app-team1 kubectl create clusterrole deployment-clusterrole --verb=create --resource=deployment,statefulset,daemonset #limted to the namespace app-team1。需要限制的是namespace级别,clusterrolebinding为设置全局,rolebinding正确 kubectl create rolebinding cicd-clusterrole -n app-team1 --clusterrole=deployment-clusterrole --serviceaccount=app-team1:cicd-token 02 Task - 英文 # Set the node named ek8s-node-1 as unavaliable and reschedule all the pods running on it.">1st | 秋河落叶 - +
1st

🏠 首页 / diff --git a/cka/index.html b/cka/index.html index 6e595d62..bb0916a7 100644 --- a/cka/index.html +++ b/cka/index.html @@ -5,7 +5,7 @@ CKA # 001 准备CKA 考题">Cka | 秋河落叶 - +

Cka

🏠 首页 / CKA

CKA diff --git a/cka/prepare-cka/index.html b/cka/prepare-cka/index.html index 0dbb5338..06d74899 100644 --- a/cka/prepare-cka/index.html +++ b/cka/prepare-cka/index.html @@ -9,7 +9,7 @@ Frequently Asked Questions: CKA and CKAD & CKS - T&C DOC (linuxfoundation.org) « 001 » 考题">Prepare Cka | 秋河落叶 - +
Prepare Cka

🏠 首页 / diff --git a/cka/tasks/index.html b/cka/tasks/index.html index 73eea759..e7079dc3 100644 --- a/cka/tasks/index.html +++ b/cka/tasks/index.html @@ -5,7 +5,7 @@ 考题 # kubectl scale deployment nginx --replicas=3 创建busybox: kubectl run busybox --image=busybox --generator=run-pod/v1 --command=true -- sleep 7d # nslookup kubectl run nginx-dns --image=nginx kubectl run busybox --image=busybox --generator=run-pod/v1 --command=true -- sleep 7d kubectl exec -it busybox -- nslookup nginx-dns kubectl exec -it busybox -- nslookup etcd备份和还原: ETCDCTL_API=3 etcdctl --endpoints=https://[127.0.0.1]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key \ snapshot save ETCDCTL_API=3 etcdctl --endpoints=https://[127.0.0.1]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --name=master \ --cert=/etc/kubernetes/pki/etcd/server.">Tasks | 秋河落叶 - +

Tasks

🏠 首页 / diff --git a/cn.search-data.min.495215f6d4282bbc8a5b922de4710cd6b33ec859629954392204982c5d00fa30.json b/cn.search-data.min.7eb822a030f71374703a53128fdf74db4a0934550de0457d2e380a5a6833769f.json similarity index 99% rename from cn.search-data.min.495215f6d4282bbc8a5b922de4710cd6b33ec859629954392204982c5d00fa30.json rename to cn.search-data.min.7eb822a030f71374703a53128fdf74db4a0934550de0457d2e380a5a6833769f.json index 509074f2..57e32560 100644 --- a/cn.search-data.min.495215f6d4282bbc8a5b922de4710cd6b33ec859629954392204982c5d00fa30.json +++ b/cn.search-data.min.7eb822a030f71374703a53128fdf74db4a0934550de0457d2e380a5a6833769f.json @@ -1 +1 @@ -[{"id":0,"href":"/algo/","title":"Algo","section":"","content":" 🏠 首页 / 数据结构与算法\n数据结构与算法 # 堆排序\n快速排序\n"},{"id":1,"href":"/algo/%E5%A0%86%E6%8E%92%E5%BA%8F/","title":"堆排序","section":"Algo","content":" 🏠 首页 / 数据结构与算法 / 堆排序\n堆排序 # 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种**选择排序,**它的最坏,最好,平均时间复杂度均为 O(nlogn),它也是不稳定排序。首先简单了解下堆结构。\n堆 # 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。:\n算法实现(golang) # package main import \u0026#34;fmt\u0026#34; type BinaryTreeNode struct { Value int Left, Right *BinaryTreeNode } func main() { tree := \u0026amp;BinaryTreeNode{ Left: \u0026amp;BinaryTreeNode{ Left: \u0026amp;BinaryTreeNode{ Value: 1, }, Right: \u0026amp;BinaryTreeNode{ Value: 2, }, Value: 3, }, Right: \u0026amp;BinaryTreeNode{ Value: 4, }, Value: 5, } res := HeapSort(tree) fmt.Println(res) } func HeapSort(tree *BinaryTreeNode) []int { var res []int if tree == nil { return []int{} } res = heapSortHelper(tree, res) return res } func heapSortHelper(tree *BinaryTreeNode, res []int) []int { if tree.Left == nil { res = append(res, tree.Value) } else { res = heapSortHelper(tree.Left, res) res = heapSortHelper(tree.Right, res) res = append(res, tree.Value) } return res } » 快速排序\n"},{"id":2,"href":"/algo/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F/","title":"快速排序","section":"Algo","content":" 🏠 首页 / 数据结构与算法 / 快速排序\n快速排序 # 步骤如下:\n先从数列中取出一个数作为基准数。一般取第一个数。 分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。 再对左右区间重复第二步,直到各区间只有一个数。 举一个例子:5 9 1 6 8 14 6 49 25 4 6 3。\n一般取第一个数 5 作为基准,从它左边和最后一个数使用[]进行标志, 如果左边的数比基准数大,那么该数要往右边扔,也就是两个[]数交换,这样大于它的数就在右边了,然后右边[]数左移,否则左边[]数右移。 5 [9] 1 6 8 14 6 49 25 4 6 [3] 因为 9 \u0026gt; 5,两个[]交换位置后,右边[]左移 5 [3] 1 6 8 14 6 49 25 4 [6] 9 因为 3 !\u0026gt; 5,两个[]不需要交换,左边[]右移 5 3 [1] 6 8 14 6 49 25 4 [6] 9 因为 1 !\u0026gt; 5,两个[]不需要交换,左边[]右移 5 3 1 [6] 8 14 6 49 25 4 [6] 9 因为 6 \u0026gt; 5,两个[]交换位置后,右边[]左移 5 3 1 [6] 8 14 6 49 25 [4] 6 9 因为 6 \u0026gt; 5,两个[]交换位置后,右边[]左移 5 3 1 [4] 8 14 6 49 [25] 6 6 9 因为 4 !\u0026gt; 5,两个[]不需要交换,左边[]右移 5 3 1 4 [8] 14 6 49 [25] 6 6 9 因为 8 \u0026gt; 5,两个[]交换位置后,右边[]左移 5 3 1 4 [25] 14 6 [49] 8 6 6 9 因为 25 \u0026gt; 5,两个[]交换位置后,右边[]左移 5 3 1 4 [49] 14 [6] 25 8 6 6 9 因为 49 \u0026gt; 5,两个[]交换位置后,右边[]左移 5 3 1 4 [6] [14] 49 25 8 6 6 9 因为 6 \u0026gt; 5,两个[]交换位置后,右边[]左移 5 3 1 4 [14] 6 49 25 8 6 6 9 两个[]已经汇总,因为 14 \u0026gt; 5,所以 5 和[]之前的数 4 交换位置 第一轮切分结果:4 3 1 5 14 6 49 25 8 6 6 9 现在第一轮快速排序已经将数列分成两个部分: 4 3 1 和 14 6 49 25 8 6 6 9 左边的数列都小于 5,右边的数列都大于 5。 使用递归分别对两个数列进行快速排序。 package main import \u0026#34;fmt\u0026#34; type BinaryTreeNode struct { Value int Left, Right *BinaryTreeNode } func main() { nums := []int{4, 7, 2, 9, 3, 1} quickSort(nums, 0, len(nums)-1) fmt.Println(nums) } func quickSort(nums []int, r, l int) { if r \u0026lt; l { loc := quickSortHelper(nums, r, l) quickSort(nums, r, loc-1) quickSort(nums, loc+1, l) } } func quickSortHelper(nums []int, r, l int) int { i := r + 1 j := l for i \u0026lt; j { if nums[i] \u0026gt; nums[r] { nums[i], nums[j] = nums[j], nums[i] j-- } else { i++ } } if nums[r] \u0026lt;= nums[i] { i-- } nums[r], nums[i] = nums[i], nums[r] return i } « 堆排序\n"},{"id":3,"href":"/aws/","title":"Aws","section":"","content":" 🏠 首页 / AWS\nAWS # 搭建EKS集群\nCluster AutoScaler\n创建 EKS 集群\nEKS配置 ALB Ingress\nEKS小细节汇总\nEKS实践 集成Gitlab自动发布(一)\nEKS实践 集成Gitlab自动发布(二)\nEKS-使用EFS\nGitlab \u0026amp; EKS\nK8s 部署 Kong 服务\nK8s 部署 konga\nK8s 部署 Postgres\nTerraform 重新管理资源\n"},{"id":4,"href":"/aws/build-eks-cluster/","title":"Build Eks Cluster","section":"Aws","content":" 🏠 首页 / AWS / 搭建EKS集群\n搭建EKS集群 # 安装aws,kubectl和eksctl命令行工具 # 引言 # 安装aws cli 安装kubectl cli 安装eksctl cli 安装aws cli # 参考 # https://docs.aws.amazon.com/zh_cn/cli/latest/userguide/cli-chap-install.html\n安装示例(windows) # 下载安装包: https://awscli.amazonaws.com/AWSCLIV2.msi 运行下载的安装包 确实安装是否成功 aws --version 使用Security Credentials配置aws cli # 访问aws控制台:Service =\u0026gt; IAM 选择IAM User,使用子用户,强烈不建议使用root用户 进入用户详情页面,Security Credentials页 创建Access Key 拷贝Access Key ID和Secret Access Key 使用aws命令配置 $ aws configure AWS Access Key ID [None]: ABCDEFGHIAZBERTUCNGG (替换Access Key ID) AWS Secret Access Key [None]: uMe7fumK1IdDB094q2sGFhM5Bqt3HQRw3IHZzBDTm (替换Secret Access Key) Default region name [None]: us-east-1 Default output format [None]: json 测试配置是否生效 aws ec2 describe-vpcs 卸载(windows) # 控制面板 =\u0026gt; 程序和功能,找到aws cli,卸载即可。 安装kubectl cli # 如果你确实是使用EKS的Kubernetes,建议使用aws提供的kubectl命令工具。\n参考 # AWS\nhttps://docs.aws.amazon.com/zh_cn/eks/latest/userguide/install-kubectl.html\n官方\nhttps://kubernetes.io/docs/tasks/tools/\n安装示例(windows) # 下载安装包,示例中下载路径:C:\\apps\\kubectl curl -o kubectl.exe https://amazon-eks.s3.us-west-2.amazonaws.com/1.16.8/2020-04-16/bin/windows/amd64/kubectl.exe 更新系统PATH环境变量,添加C:\\apps\\kubectl\n验证kubectl版本\nkubectl version --client 卸载(windows) # 直接删除程序文件以及PATH环境变量即可。\n安装eksctl cli # 参考 # https://docs.aws.amazon.com/zh_cn/eks/latest/userguide/eksctl.html\n安装示例(windows) # 卸载(windows) # EKS最佳实践:\nhttps://aws.github.io/aws-eks-best-practices/security/docs/iam/\n» Cluster AutoScaler\n"},{"id":5,"href":"/aws/cluster-autoscaler/","title":"Cluster Autoscaler","section":"Aws","content":" 🏠 首页 / AWS / Cluster AutoScaler\nCluster AutoScaler # 我当前已经有了一个EKS服务搭建起来的K8s集群,我现在希望我的集群拥有自动伸缩(体现在节点的扩缩)的能力。\n当我的集群资源充足,而我部署在集群中的应用只使用到了很少量的资源,我希望集群回收资源以节省费用;当我的应用服务越来越多,当前集群资源不足时,我希望集群能增加节点,以满足应用的部署条件。\n那么本篇就是介绍如何通过使用Kubernetes Cluster Autoscaler让你的集群拥有自动伸缩的能力,而不用你时刻关注集群的资源是否过于宽松或紧张。\nNodeGroup添加Tag # 我们创建EKS时,需要定义NodeGroup,一般在这个NodeGroup中定义:\nasg_desired_capacity:期望创建的Node数量;\nasg_max_size:最小Node数量,默认为1;\nasg_min_size:最大Node数量。\n定义的NodeGroup会生成Auto Scaling Group资源,并且由Auto Scaling Group来管理Node的创建。\n现在需要做的就是为你的Auto Scaling Group添加Tag。\ntag键值:\nTag Key Tag Value k8s.io/cluster-autoscaler/\u0026lt;your_cluster_name\u0026gt; owned k8s.io/cluster-autoscaler/enabled true 第一个Tag Key中的集群名\u0026lt;your_cluster_name\u0026gt;需要替换;\nTag Value的值是什么不重要,主要是需要这两个Tag Key来识别是否对这个集群开启Auto Scaling的能力。\n添加Policy # 创建IAM策略,将新创建的策略Attach到集群节点绑定的IAM role上,让你的集群节点拥有自动伸缩的能力。\nIAM策略Json内容:\n{ \u0026#34;Version\u0026#34;: \u0026#34;2012-10-17\u0026#34;, \u0026#34;Statement\u0026#34;: [ { \u0026#34;Action\u0026#34;: [ \u0026#34;autoscaling:DescribeAutoScalingGroups\u0026#34;, \u0026#34;autoscaling:DescribeAutoScalingInstances\u0026#34;, \u0026#34;autoscaling:DescribeLaunchConfigurations\u0026#34;, \u0026#34;autoscaling:DescribeTags\u0026#34;, \u0026#34;autoscaling:SetDesiredCapacity\u0026#34;, \u0026#34;autoscaling:TerminateInstanceInAutoScalingGroup\u0026#34;, \u0026#34;ec2:DescribeLaunchTemplateVersions\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34;, \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34; } ] } 部署Cluster AutoScaler # 在集群中部署Cluster AutoScaler,准备资源清单文件cluster-autoscaler.yaml:\n--- apiVersion: v1 kind: ServiceAccount metadata: labels: k8s-addon: cluster-autoscaler.addons.k8s.io k8s-app: cluster-autoscaler name: cluster-autoscaler namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: cluster-autoscaler labels: k8s-addon: cluster-autoscaler.addons.k8s.io k8s-app: cluster-autoscaler rules: - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;events\u0026#34;, \u0026#34;endpoints\u0026#34;] verbs: [\u0026#34;create\u0026#34;, \u0026#34;patch\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;pods/eviction\u0026#34;] verbs: [\u0026#34;create\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;pods/status\u0026#34;] verbs: [\u0026#34;update\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;endpoints\u0026#34;] resourceNames: [\u0026#34;cluster-autoscaler\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;update\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;nodes\u0026#34;] verbs: [\u0026#34;watch\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;get\u0026#34;, \u0026#34;update\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: - \u0026#34;pods\u0026#34; - \u0026#34;services\u0026#34; - \u0026#34;replicationcontrollers\u0026#34; - \u0026#34;persistentvolumeclaims\u0026#34; - \u0026#34;persistentvolumes\u0026#34; verbs: [\u0026#34;watch\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;get\u0026#34;] - apiGroups: [\u0026#34;extensions\u0026#34;] resources: [\u0026#34;replicasets\u0026#34;, \u0026#34;daemonsets\u0026#34;] verbs: [\u0026#34;watch\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;get\u0026#34;] - apiGroups: [\u0026#34;policy\u0026#34;] resources: [\u0026#34;poddisruptionbudgets\u0026#34;] verbs: [\u0026#34;watch\u0026#34;, \u0026#34;list\u0026#34;] - apiGroups: [\u0026#34;apps\u0026#34;] resources: [\u0026#34;statefulsets\u0026#34;, \u0026#34;replicasets\u0026#34;, \u0026#34;daemonsets\u0026#34;] verbs: [\u0026#34;watch\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;get\u0026#34;] - apiGroups: [\u0026#34;storage.k8s.io\u0026#34;] resources: [\u0026#34;storageclasses\u0026#34;, \u0026#34;csinodes\u0026#34;] verbs: [\u0026#34;watch\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;get\u0026#34;] - apiGroups: [\u0026#34;batch\u0026#34;, \u0026#34;extensions\u0026#34;] resources: [\u0026#34;jobs\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;watch\u0026#34;, \u0026#34;patch\u0026#34;] - apiGroups: [\u0026#34;coordination.k8s.io\u0026#34;] resources: [\u0026#34;leases\u0026#34;] verbs: [\u0026#34;create\u0026#34;] - apiGroups: [\u0026#34;coordination.k8s.io\u0026#34;] resourceNames: [\u0026#34;cluster-autoscaler\u0026#34;] resources: [\u0026#34;leases\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;update\u0026#34;] --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: cluster-autoscaler namespace: kube-system labels: k8s-addon: cluster-autoscaler.addons.k8s.io k8s-app: cluster-autoscaler rules: - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;configmaps\u0026#34;] verbs: [\u0026#34;create\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;watch\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;configmaps\u0026#34;] resourceNames: [\u0026#34;cluster-autoscaler-status\u0026#34;, \u0026#34;cluster-autoscaler-priority-expander\u0026#34;] verbs: [\u0026#34;delete\u0026#34;, \u0026#34;get\u0026#34;, \u0026#34;update\u0026#34;, \u0026#34;watch\u0026#34;] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: cluster-autoscaler labels: k8s-addon: cluster-autoscaler.addons.k8s.io k8s-app: cluster-autoscaler roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-autoscaler subjects: - kind: ServiceAccount name: cluster-autoscaler namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: cluster-autoscaler namespace: kube-system labels: k8s-addon: cluster-autoscaler.addons.k8s.io k8s-app: cluster-autoscaler roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: cluster-autoscaler subjects: - kind: ServiceAccount name: cluster-autoscaler namespace: kube-system --- apiVersion: apps/v1 kind: Deployment metadata: name: cluster-autoscaler namespace: kube-system labels: app: cluster-autoscaler spec: replicas: 1 selector: matchLabels: app: cluster-autoscaler template: metadata: labels: app: cluster-autoscaler annotations: prometheus.io/scrape: \u0026#34;true\u0026#34; prometheus.io/port: \u0026#34;8085\u0026#34; cluster-autoscaler.kubernetes.io/safe-to-evict: \u0026#34;false\u0026#34; spec: serviceAccountName: cluster-autoscaler containers: - image: us.gcr.io/k8s-artifacts-prod/autoscaling/cluster-autoscaler:v1.16.5 name: cluster-autoscaler resources: limits: cpu: 100m memory: 300Mi requests: cpu: 100m memory: 300Mi command: - ./cluster-autoscaler - --v=4 - --stderrthreshold=info - --cloud-provider=aws - --skip-nodes-with-local-storage=false - --expander=least-waste - --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/\u0026lt;your_cluster_name\u0026gt; - --balance-similar-node-groups - --skip-nodes-with-system-pods=false volumeMounts: - name: ssl-certs mountPath: /etc/ssl/certs/ca-certificates.crt readOnly: true imagePullPolicy: \u0026#34;Always\u0026#34; volumes: - name: ssl-certs hostPath: path: \u0026#34;/etc/ssl/certs/ca-bundle.crt\u0026#34; 资源清单内容比较长,但是需要注意的是 cluster-autoscaler 的 Deployment 资源中定义的 command 中,有一行你会觉得眼熟,没错就是我们上面为 Auto Scale Group 添加的 Tag,这里你也需要替换你的集群名 \u0026lt;your_cluster_name\u0026gt;,此外你可能需要替换使用到的镜像,你可以在 Cluster AutoScaler 发版页面去找对应的镜像版本。例如我的K8s集群是1.16,那么我需要找一个1.16.x版本的镜像。上面资源清单里就是使用了 us.gcr.io/k8s-artifacts-prod/autoscaling/cluster-autoscaler:v1.16.5 的镜像。\n使用下面的命令创建 Cluster AutoScaler 资源:\nkubectl apply -f cluster-autoscaler.yaml 查看Cluster AutoScaler # 我们部署完成之后,Cluster 默认每 10s 检查一次集群的资源情况,并根据资源情况判断是否需要扩缩容。\n使用命令查看Cluster AutoScaler的检查情况:\nkubectl logs -f deployment.apps/cluster-autoscaler -n kube-system 分析原理 # 你可以通过突然增加Pod示例数量,并且增加 Pod 的 resource.request.cpu/memory 设置为较高的值,这样来模拟集群资源紧张的场景,观察集群节点是否会自动扩容,之后将增加的 Pod 资源删除,再观察集群节点是否自动回收。这里我已经实践过了,由于演示起来比较复杂,可能会耗费很长的篇幅,所以这里就省去了,你可以亲自动手实践尝试。\n相信有一个疑问一直环绕着,Cluster AutoScaler是怎么做到自动伸缩的呢,我们也没有给出类似于CPU超过75%就扩容的伸缩条件。我没有深入去研究,但是我自己根据Cluster AutoScaler的日志输出理解了一下。\nPod的申请的资源(resources.requests)与现有节点资源作比较,如果发现集群资源超出申请的资源能够使得我们移除一个节点的话,那么Cluster AutoScaler便会修改Auto Scaling Group的asg_desired_capacity值,例如-1,但是不会小于asg_max_size。当有新Pod部署,发现集群资源达不到新Pod申请的资源时,那么Cluster AutoScaler便会修改Auto Scaling Group的asg_desired_capacity值,例如+1,但是不会大于asg_max_size。只要修改了Auto Scaling Group的asg_desired_capacity值,那么集群节点便会自动伸缩。\n« 搭建EKS集群\n» 创建 EKS 集群\n"},{"id":6,"href":"/aws/create-eks-cluster/","title":"Create Eks Cluster","section":"Aws","content":" 🏠 首页 / AWS / 创建 EKS 集群\n创建 EKS 集群 # 1. EKS简介 # Amazon Elastic Kubernetes Service (Amazon EKS) 是一项托管服务,可让您在 AWS 上轻松运行 Kubernetes,而无需支持或维护您自己的 Kubernetes 控制层面。Kubernetes 是一个用于实现容器化应用程序的部署、扩展和管理的自动化的开源系统。(该段介绍来自Amazon EKS文档,更多了解 https://docs.aws.amazon.com/zh_cn/eks/latest/userguide/what-is-eks.html)\n2. eksctl创建eks集群 # 2.1 什么是eksctl # eksctl是一种用于在 Amazon EKS 上创建和管理 Kubernetes 集群的简单命令行实用程序。eksctl 命令行实用程序提供了使用工作线程节点为 Amazon EKS 创建新集群的最快、最简单的方式。\neksctl更多了解 https://eksctl.io\n2.2 为什么用eksctl # 创建EKS集群可以在AWS的控制台创建,也可以使用AWS开发的eksctl工具创建,为什么选择使用eksctl创建eks集群呢,有以下几点原因:\n直接在AWS的控制台创建集群,需要手动创建各种Role,以及选择合适的Subnet,Security Group等繁杂操作,你需要在浏览器中打开多个页面,操作过程可能也要时不时参阅文档; eksctl创建EKS集群只需要一行eksctl create cluster \u0026lt;参数\u0026gt;命令即可,会自动的给你创建Role等资源; eksctl的命令可以记录到脚本,便于复用。 2.3 安装eksctl(基于ubuntu) # 使用以下命令下载并提取最新版本的 eksctl。 curl --silent --location \u0026#34;https://github.com/weaveworks/eksctl/releases/download/latest_release/eksctl_$(uname -s)_amd64.tar.gz\u0026#34; | tar xz -C /tmp 将提取的二进制文件移至 /usr/local/bin。 sudo mv /tmp/eksctl /usr/local/bin 使用以下命令测试您的安装是否成功。 eksctl version 注意:\nGitTag 版本应至少为 0.11.1。否则,请检查您的终端输出是否有任何安装或升级错误。\n2.4 使用eksctl创建集群 # 准备工作:\n一台拥有创建eks权限的机器 已经安装好eksctl eksctl create cluster \\ --name prod-eks \\ --version 1.14 \\ --region us-east-1 \\ --nodegroup-name prod-eks-workers \\ --node-type t3.medium \\ --nodes 2 \\ --nodes-min 2 \\ --nodes-max 4 \\ --ssh-access \\ --ssh-public-key -eks-public-key \\ --managed --ssh-public-key 可选的,但建议您在创建包含节点组的集群时指定该选项。通过此选项,可以对托管节点组中的节点进行 SSH 访问。启用 SSH 访问后,如果出现问题,您可以连接到实例并收集诊断信息。\n注意:在us-east-1区中,创建eks集群时您可能会遇到UnsupportedAvailabilityZoneException类型的异常。如果遇到这种情况,可以传递zones参数,例如,\neksctl create cluster \\ --name prod-eks \\ --version 1.14 \\ --region us-east-1 \\ --zones us-east-1a,us-east-1b \\ --nodegroup-name prod-eks-workers \\ --node-type t3.medium \\ --nodes 2 \\ --nodes-min 2 \\ --nodes-max 4 \\ --ssh-access \\ --ssh-public-key prod-eks-public-key \\ --managed 以上创建eks集群的命令会大致输出以下内容,集群的整个创建过程可能会花费10到15分钟:\n[ℹ] CloudWatch logging will not be enabled for cluster \u0026#34;prod-eks\u0026#34; in \u0026#34;us-east-1\u0026#34; [ℹ] you can enable it with \u0026#39;eksctl utils update-cluster-logging --region=us-east-1 --cluster=prod-eks\u0026#39; [ℹ] Kubernetes API endpoint access will use default of {publicAccess=true, privateAccess=false} for cluster \u0026#34;prod-eks\u0026#34; in \u0026#34;us-east-1\u0026#34; [ℹ] 2 sequential tasks: { create cluster control plane \u0026#34;prod-eks\u0026#34;, create managed nodegroup \u0026#34;prod-eks-workers\u0026#34; } [ℹ] building cluster stack \u0026#34;eksctl-prod-eks-cluster\u0026#34; [ℹ] deploying stack \u0026#34;eksctl-prod-eks-cluster\u0026#34; [ℹ] building managed nodegroup stack \u0026#34;eksctl-prod-eks-nodegroup-prod-eks-workers\u0026#34; [ℹ] deploying stack \u0026#34;eksctl-prod-eks-nodegroup-prod-eks-workers\u0026#34; [✔] all EKS cluster resources for \u0026#34;prod-eks\u0026#34; have been created ... 如果输出中存在 could not find any of the authenticator commands: aws-iam-authenticator, heptio-authenticator-aws,则需要安装aws-iam-authenticator,使用以下命令:\ncurl -o aws-iam-authenticator https://amazon-eks.s3-us-west-2.amazonaws.com/1.14.6/2019-08-22/bin/linux/amd64/aws-iam-authenticator chmod +x ./aws-iam-authenticator mkdir -p $HOME/bin \u0026amp;\u0026amp; cp ./aws-iam-authenticator $HOME/bin/aws-iam-authenticator \u0026amp;\u0026amp; export PATH=$PATH:$HOME/bin echo \u0026#39;export PATH=$PATH:$HOME/bin\u0026#39; \u0026gt;\u0026gt; ~/.bashrc aws-iam-authenticator help 如果本次操作机器没有安装kubectl,可以使用以下命令安装,如果已经安装则跳过:\nsudo apt-get update \u0026amp;\u0026amp; sudo apt-get install -y apt-transport-https curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - echo \u0026#34;deb https://apt.kubernetes.io/ kubernetes-xenial main\u0026#34; | sudo tee -a /etc/apt/sources.list.d/kubernetes.list sudo apt-get update sudo apt-get install -y kubectl 可以通过kubectl命令操作本次创建的集群:\nkubectl get svc 输出如下,可以说明eks集群已经成功创建了。\nNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.100.0.1 \u0026lt;none\u0026gt; 443/TCP 31m 3. 部署 Kubernetes Metrics Server # 打开终端窗口,导航到您要下载最新 metrics-server 版本的目录。\n将以下命令复制并粘贴到您的终端窗口,然后键入 Enter 执行这些命令。这些命令将下载最新版本,提取它,然后在集群中应用版本 1.8+ 清单。\nDOWNLOAD_URL=$(curl -Ls \u0026#34;https://api.github.com/repos/kubernetes-sigs/metrics-server/releases/latest\u0026#34; | jq -r .tarball_url) DOWNLOAD_VERSION=$(grep -o \u0026#39;[^/v]*$\u0026#39; \u0026lt;\u0026lt;\u0026lt; $DOWNLOAD_URL) curl -Ls $DOWNLOAD_URL -o metrics-server-$DOWNLOAD_VERSION.tar.gz mkdir metrics-server-$DOWNLOAD_VERSION tar -xzf metrics-server-$DOWNLOAD_VERSION.tar.gz --directory metrics-server-$DOWNLOAD_VERSION --strip-components 1 kubectl apply -f metrics-server-$DOWNLOAD_VERSION/deploy/1.8+/ 使用以下命令验证 metrics-server 部署是否运行所需数量的 Pod: kubectl get deployment metrics-server -n kube-system 输出:\nNAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE metrics-server 1 1 1 1 56m eksctl create cluster --name dev-devops-eks2 --version 1.14 --region ap-southeast-1 --nodegroup-name dev-devops-eks2-workers --node-type t3.medium --nodes 3 --nodes-min 1 --nodes-max 4 --ssh-access --ssh-public-key dev-devops-eks2-public-key --managed EKS Node 默认给的Role权限需要attch s3、secrets manager的权限。\n« Cluster AutoScaler\n» EKS配置 ALB Ingress\n"},{"id":7,"href":"/aws/eks-config-alb-ingress/","title":"Eks Config Alb Ingress","section":"Aws","content":" 🏠 首页 / AWS / EKS配置 ALB Ingress\nEKS配置 ALB Ingress # 官方文档: https://kubernetes-sigs.github.io/aws-load-balancer-controller/latest/guide/controller/installation/\n部署Alb Ingress Controller # IAM中创建Policy,给集群的Node节点的Role添加该Policy。\nPolicy的JSON配置如下:\n{ \u0026#34;Version\u0026#34;: \u0026#34;2012-10-17\u0026#34;, \u0026#34;Statement\u0026#34;: [ { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;acm:DescribeCertificate\u0026#34;, \u0026#34;acm:ListCertificates\u0026#34;, \u0026#34;acm:GetCertificate\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;ec2:AuthorizeSecurityGroupIngress\u0026#34;, \u0026#34;ec2:CreateSecurityGroup\u0026#34;, \u0026#34;ec2:CreateTags\u0026#34;, \u0026#34;ec2:DeleteTags\u0026#34;, \u0026#34;ec2:DeleteSecurityGroup\u0026#34;, \u0026#34;ec2:DescribeAccountAttributes\u0026#34;, \u0026#34;ec2:DescribeAddresses\u0026#34;, \u0026#34;ec2:DescribeInstances\u0026#34;, \u0026#34;ec2:DescribeInstanceStatus\u0026#34;, \u0026#34;ec2:DescribeInternetGateways\u0026#34;, \u0026#34;ec2:DescribeNetworkInterfaces\u0026#34;, \u0026#34;ec2:DescribeSecurityGroups\u0026#34;, \u0026#34;ec2:DescribeSubnets\u0026#34;, \u0026#34;ec2:DescribeTags\u0026#34;, \u0026#34;ec2:DescribeVpcs\u0026#34;, \u0026#34;ec2:ModifyInstanceAttribute\u0026#34;, \u0026#34;ec2:ModifyNetworkInterfaceAttribute\u0026#34;, \u0026#34;ec2:RevokeSecurityGroupIngress\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;elasticloadbalancing:AddListenerCertificates\u0026#34;, \u0026#34;elasticloadbalancing:AddTags\u0026#34;, \u0026#34;elasticloadbalancing:CreateListener\u0026#34;, \u0026#34;elasticloadbalancing:CreateLoadBalancer\u0026#34;, \u0026#34;elasticloadbalancing:CreateRule\u0026#34;, \u0026#34;elasticloadbalancing:CreateTargetGroup\u0026#34;, \u0026#34;elasticloadbalancing:DeleteListener\u0026#34;, \u0026#34;elasticloadbalancing:DeleteLoadBalancer\u0026#34;, \u0026#34;elasticloadbalancing:DeleteRule\u0026#34;, \u0026#34;elasticloadbalancing:DeleteTargetGroup\u0026#34;, \u0026#34;elasticloadbalancing:DeregisterTargets\u0026#34;, \u0026#34;elasticloadbalancing:DescribeListenerCertificates\u0026#34;, \u0026#34;elasticloadbalancing:DescribeListeners\u0026#34;, \u0026#34;elasticloadbalancing:DescribeLoadBalancers\u0026#34;, \u0026#34;elasticloadbalancing:DescribeLoadBalancerAttributes\u0026#34;, \u0026#34;elasticloadbalancing:DescribeRules\u0026#34;, \u0026#34;elasticloadbalancing:DescribeSSLPolicies\u0026#34;, \u0026#34;elasticloadbalancing:DescribeTags\u0026#34;, \u0026#34;elasticloadbalancing:DescribeTargetGroups\u0026#34;, \u0026#34;elasticloadbalancing:DescribeTargetGroupAttributes\u0026#34;, \u0026#34;elasticloadbalancing:DescribeTargetHealth\u0026#34;, \u0026#34;elasticloadbalancing:ModifyListener\u0026#34;, \u0026#34;elasticloadbalancing:ModifyLoadBalancerAttributes\u0026#34;, \u0026#34;elasticloadbalancing:ModifyRule\u0026#34;, \u0026#34;elasticloadbalancing:ModifyTargetGroup\u0026#34;, \u0026#34;elasticloadbalancing:ModifyTargetGroupAttributes\u0026#34;, \u0026#34;elasticloadbalancing:RegisterTargets\u0026#34;, \u0026#34;elasticloadbalancing:RemoveListenerCertificates\u0026#34;, \u0026#34;elasticloadbalancing:RemoveTags\u0026#34;, \u0026#34;elasticloadbalancing:SetIpAddressType\u0026#34;, \u0026#34;elasticloadbalancing:SetSecurityGroups\u0026#34;, \u0026#34;elasticloadbalancing:SetSubnets\u0026#34;, \u0026#34;elasticloadbalancing:SetWebACL\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;iam:CreateServiceLinkedRole\u0026#34;, \u0026#34;iam:GetServerCertificate\u0026#34;, \u0026#34;iam:ListServerCertificates\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;cognito-idp:DescribeUserPoolClient\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;waf-regional:GetWebACLForResource\u0026#34;, \u0026#34;waf-regional:GetWebACL\u0026#34;, \u0026#34;waf-regional:AssociateWebACL\u0026#34;, \u0026#34;waf-regional:DisassociateWebACL\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;tag:GetResources\u0026#34;, \u0026#34;tag:TagResources\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;waf:GetWebACL\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;shield:DescribeProtection\u0026#34;, \u0026#34;shield:GetSubscriptionState\u0026#34;, \u0026#34;shield:DeleteProtection\u0026#34;, \u0026#34;shield:CreateProtection\u0026#34;, \u0026#34;shield:DescribeSubscription\u0026#34;, \u0026#34;shield:ListProtections\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; } ] } 下载alb-ingress-controller.yaml\nwget https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.6/docs/examples/alb-ingress-controller.yaml 修改alb-ingress-controller.yaml,修改以下三个配置项\n--cluster-name=dev-eks\n--aws-vpc-id=vpc-xxxxxx\n--aws-region=us-west-1\n部署:\nkubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.6/docs/examples/rbac-role.yaml kubectl apply -f alb-ingress-controller.yaml 验证部署情况:\nkubectl logs -n kube-system $(kubectl get po -n kube-system | egrep -o \u0026#34;alb-ingress[a-zA-Z0-9-]+\u0026#34;) 配置Alb Ingress Controller # 设置External DNS # 本篇内容来源: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v1.1/guide/external-dns/setup/\n背景 # 在AWS中,如果我们希望以某个域名访问K8s集群的某个服务,我们可以通过维护Route 53中Record Set和LoadBalancer映射来实现,这无疑会增加一些手动操作的成本,本篇即介绍使用external-dns来自动维护它们之间的关系。\nexternal-dns项目地址: https://github.com/kubernetes-incubator/external-dns\nexternal-dns根据host的信息提供DNS记录,它将建立和管理Route 53中Record Set与LoadBalancer的映射关系。\n前提条件 # 角色权限 # 必须给K8s集群运行external-dns的Node配置角色和策略。\nIAM中创建Policy,给集群的Node节点的Role添加该Policy。\nPolicy的JSON配置如下:\n{ \u0026#34;Version\u0026#34;: \u0026#34;2012-10-17\u0026#34;, \u0026#34;Statement\u0026#34;: [ { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;route53:ChangeResourceRecordSets\u0026#34; ], \u0026#34;Resource\u0026#34;: [ \u0026#34;arn:aws:route53:::hostedzone/*\u0026#34; ] }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;route53:ListHostedZones\u0026#34;, \u0026#34;route53:ListResourceRecordSets\u0026#34; ], \u0026#34;Resource\u0026#34;: [ \u0026#34;*\u0026#34; ] } ] } 安装 # 下载external-dns.yaml示例文件 wget https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.6/docs/examples/external-dns.yaml 修改external-dns.yaml文件,主要修改``\u0026ndash;domain-filter`这个配置内容 --domain-filter=example.com 部署external-dns kubectl apply -f external-dns.yaml 验证部署是否成功和实时记录 kubectl logs -f $(kubectl get po | egrep -o \u0026#39;external-dns[A-Za-z0-9-]+\u0026#39;) 使用方式 # 为了在子域名中建立Record Set,需要在Ingress或者Service资源中添加如下注释: annotations: # for creating record-set external-dns.alpha.kubernetes.io/hostname: nginx.example.com # give your domain name here 如果是service,需要将type设置成LoadBalancer\n查看实时日志,2分钟后在Route 53中查看记录是否生成或更新。 实践记录 # Pod内容器使用Secrets Manager,需要给Node的Role添加相应的权限; « 创建 EKS 集群\n» EKS小细节汇总\n"},{"id":8,"href":"/aws/eks-details/","title":"Eks Details","section":"Aws","content":" 🏠 首页 / AWS / EKS小细节汇总\nEKS小细节汇总 # 如果alb的ingress使用了自定义的security group,那么需要将该安全组加入到worker\n« EKS配置 ALB Ingress\n» EKS实践 集成Gitlab自动发布(一)\n"},{"id":9,"href":"/aws/eks-intergrate-gitlab-auto-release-01/","title":"Eks Intergrate Gitlab Auto Release 01","section":"Aws","content":" 🏠 首页 / AWS / EKS实践 集成Gitlab自动发布(一)\nEKS实践 集成Gitlab自动发布(一) # 系列介绍如何使用Gitlab CI/CD自动部署应用到EKS(K8s)集群中。本篇介绍如何在EKS(K8s)集群中为Gitlab的CI/CD创建Gitlab Runner。\nGitlab添加K8s集群 # 添加方式 # 第一种方式,基于单个仓库添加K8s集群:\n进入Gitlab仓库,依次从左边菜单栏Operations =\u0026gt; Kubernetes进入添加页面,点击Add Kubernetes cluster按钮。这种方式添加的K8s集群只对该项目仓库有效。\n第二种方式,基于Group添加K8s集群:\n进入Gitlab主页,依次从上边菜单栏Groups =\u0026gt; Your groups,选择Group进入页面,然后依次从左边菜单栏Kuberentes进入添加页面,点击Add Kubernetes cluster。这种方式添加的K8s集群对该Group下的项目仓库有效。\n第三种方式,基于全局添加K8s集群:\n这种方式需要用到gitlab的root权限。进入Gitlab主页,从上边菜单栏Admin Area(扳手图标) 进入页面,然后依次从左边菜单栏Kuberentes进入添加页面,点击Add Kubernetes cluster。这种方式添加的K8s集群对所有项目仓库有效。\n添加步骤 # 添加已有的K8s集群,按照如下步骤获取到对应的值填入表单即可。\nCluster Name: 这个可以自定义,能自行区分就行。\nAPI URL: 运行以下命令得到输出值:\nkubectl cluster-info | grep \u0026#39;Kubernetes master\u0026#39; | awk \u0026#39;/http/ {print $NF}\u0026#39; CA Certificate: 运行以下命令得到输出值:\nkubectl get secret $(kubectl get secret | grep default-token | awk \u0026#39;{print $1}\u0026#39;) -o jsonpath=\u0026#34;{[\u0026#39;data\u0026#39;][\u0026#39;ca\\.crt\u0026#39;]}\u0026#34; | base64 --decode Service Token: 创建文件gitlab-admin-service-account.yaml:\nvim gitlab-admin-service-account.yaml 文件写入如下内容:\napiVersion: v1 kind: ServiceAccount metadata: name: gitlab-admin namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: gitlab-admin roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: gitlab-admin namespace: kube-system 运行以下命令得到输出值:\nkubectl apply -f gitlab-admin-service-account.yaml kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep gitlab-admin | awk \u0026#39;{print $1}\u0026#39;) 安装Helm以及Runner # 添加完K8s集群之后,在集群中安装Helm Tiller。\n安装完Helm Tiller之后,再安装Helm TIller下面的Runner。\n之后,你可以发现K8s资源中多了一个gitlab-managed-apps命令空间,并且在该命名空间下,包含了tiller和runner的一些资源。\n安装Gitlab Runner # 上面介绍了Gitlab集成EKS,并安装Gitlab Runner的操作过程,其实也可以完全不用这种方法。下面介绍另一种安装Gitlab Runner的方法。\n在Group或项目仓库下都可以,Settings =\u0026gt; CI / CD =\u0026gt; Runners 获取到Url和Token。\n从这里下载values.yaml文件: https://gitlab.com/gitlab-org/charts/gitlab-runner/blob/master/values.yaml\n下载完之后,将上面获取到的Url和Token替换文件中gitlabUrl和runnerRegistrationToken值。\n然后使用helm命令安装Gitlab Runner:\nkubectl create -n gitlab-managed-apps # 手动创建命名空间 helm install -n gitlab-managed-apps \u0026lt;gitlab-runner-name\u0026gt; -f values.yaml gitlab/gitlab-runner # 删除gitlab runner # helm uninstall -n gitlab-managed-apps \u0026lt;gitlab-runner-name\u0026gt; 在安装了之后,查看安装的Gitlab Runner:\n此时,进入Gitlab Group页面 Setting =\u0026gt; CI / CD =\u0026gt; Runners下可以看到有一个可用的Group Runner。\n一些经验 # 要求Gitlab的服务和K8s可以互相访问,也就是网络是连通的;\n重复添加集群安装Helm Tiller\n同一个K8s集群可以被同一个Gitlab服务多次添加,但是Helm Tiller和Runner是不能多次安装的,这是因为安装Helm Tiller和Gitlab Runner创建K8s资源(gitlab-managed-apps命名空间下的Deployment)会冲突。\nGitlab Runner的作用域\n一个项目可以选择使用多个Gitlab Runner:\nSpecific Runners:基于当前项目添加的K8s集群中安装的Gitlab Runner\nShared Runners:Root权限基于全局添加的K8s集群中安装的Gitlab Runner\nGroup Runners:当前项目所属Group添加的K8s集群中安装的Gitlab Runner\n如果使用Root权限基于全局添加了一个K8s集群,并且安装了Gitlab Runner之后,随便进入一个项目仓库 Settings =\u0026gt; CI / CD =\u0026gt; Runners下面可以看到Shared Runners下面会有一个可用的Runner,如下图所示。你可能注意到了这个Runner默认有两个Tag:cluster,kubernetes,这两个Tag的作用下次将会使用到。\n结束语 # 本篇介绍将EKS(K8s)集群添加到项目仓库中,并且安装所需的Helm Tiller和GItlab Runner。\nHelm是K8s的类似包管理工具,Helm的各类Charts可以帮助我们快速的定义,安装和升级K8s应。Helm对K8s来说是一个客户端,它的服务端则是安装在K8s中的Tiller。\n可以使用Helm在K8s集群中安装Gitlab Runner,使用Gitlab Runner实现Gitlab 自动CI/CD,前面我们的步骤就是使用Helm安装GItlab Runner。\n« EKS小细节汇总\n» EKS实践 集成Gitlab自动发布(二)\n"},{"id":10,"href":"/aws/eks-intergrate-gitlab-auto-release-02/","title":"Eks Intergrate Gitlab Auto Release 02","section":"Aws","content":" 🏠 首页 / AWS / EKS实践 集成Gitlab自动发布(二)\nEKS实践 集成Gitlab自动发布(二) # 系列介绍如何使用Gitlab CI/CD自动部署应用到EKS(K8s)集群中。本篇介绍如何为Runnr镜像的制作。\n上文中创建的Gitlab Runner会持续存活在EKS集群中,但是它不做具体的Pipeline任务,当有Pipeline任务来临时,由它来创建临时的Runner来执行。而临时拆功创建的Runner使用什么容器环境以及具体执行什么任务是由仓库目录下.gitlab-ci.yml文件定义的。\n制作Temp Runner镜像 # 我们制作这个镜像的目的是为了能让镜像运行起来后,可以完成我们的自动发布任务。比如在容器temp-runner容器需要将我们的代码build成应用镜像,然后将应用镜像发布到EKS集群,可以看到,在容器中我们就必须可以使用docker build功能以及kubectl apply功能。\n按照以上的需要,我们制作的镜像至少需要安装好docker 以及kubectl。\nDockerfile如下:\nFROM docker:18 RUN apk add --no-cache curl jq python3 git tar tree \u0026amp;\u0026amp; \\ curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl \u0026amp;\u0026amp; chmod +x ./kubectl \u0026amp;\u0026amp; mv ./kubectl /usr/local/bin/kubectl 直接使用docker镜像作为基础镜像,然后安装kubectl\n使用命令制作并推送镜像到dockerhub(实际我的镜像推动到了ECR)\ndocker build . -t gitlab-runner-base:latest --rm --no-cache docker push gitlab-runner-base:latest 仓库配置 # 项目仓库根目录下新增.gitlab-ci.yml文件,然后文件内容\n« EKS实践 集成Gitlab自动发布(一)\n» EKS-使用EFS\n"},{"id":11,"href":"/aws/eks-use-efs/","title":"Eks Use Efs","section":"Aws","content":" 🏠 首页 / AWS / EKS-使用EFS\nEKS-使用EFS # 创建EFS\naws efs create-file-system \\ --performance-mode generalPurpose \\ --throughput-mode bursting \\ --encrypted \\ --tags Key=Name,Value=\u0026lt;fs-name\u0026gt; Key=creator,Value=dp Key=env:dev,Value=1 # 上面的命令会得到fs-id aws efs create-mount-target \\ --file-system-id \u0026lt;fs-id\u0026gt; \\ --subnet-id subnet-08d7609e614373fb8 \\ --security-groups sg-0af0f0e8705380529 aws efs create-mount-target \\ --file-system-id \u0026lt;fs-id\u0026gt; \\ --subnet-id subnet-09c0707ea8ad281bb \\ --security-groups sg-0af0f0e8705380529 aws efs create-mount-target \\ --file-system-id \u0026lt;fs-id\u0026gt; \\ --subnet-id subnet-063a8f10feb97868d \\ --security-groups sg-0af0f0e8705380529 « EKS实践 集成Gitlab自动发布(二)\n» Gitlab \u0026amp; EKS\n"},{"id":12,"href":"/aws/gitlab-eks/","title":"Gitlab Eks","section":"Aws","content":" 🏠 首页 / AWS / Gitlab \u0026amp; EKS\nGitlab \u0026amp; EKS # 创建IAM User\u0026amp;Group # User:gitlab-ci,保存生成的Access key ID和Secret Access Key,后面会用到\nGroup:Gitlab.CI,添加Policy如下:\nPolicy Name AmazonEKSWorkerNodePolicy AmazonEC2ContainerRegistryFullAccess AmazonEC2ContainerRegistryReadOnly AmazonEC2ContainerServiceFullAccess AmazonEKS_CNI_Policy 将user gitlab-ci添加到Group Gitlab.CI\n将IAM User添加到ConfigMap # kubectl edit cm aws-auth -n kube-system 在mapUsers键追加:\n- \u0026#34;groups\u0026#34;: - \u0026#34;system:masters\u0026#34; \u0026#34;userarn\u0026#34;: \u0026#34;arn:aws:iam::xxxxxxx:user/gitlab-ci\u0026#34; \u0026#34;username\u0026#34;: \u0026#34;gitlab-ci\u0026#34; Gitlab仓库设置 # Setting =\u0026gt; CI/CD =\u0026gt; Variables,添加变量:\nAWS_ACCESS_KEY_ID:\u0026lt;gitlab-ci用户的Access key ID\u0026gt; AWS_SECRET_ACCESS_KEY:\u0026lt;gitlab-ci用户的Secret Access Key\u0026gt; Gitlab仓库.gitlab-ci.yml # « EKS-使用EFS\n» K8s 部署 Kong 服务\n"},{"id":13,"href":"/aws/k8s-deploy-kong/","title":"K8s Deploy Kong","section":"Aws","content":" 🏠 首页 / AWS / K8s 部署 Kong 服务\nK8s 部署 Kong 服务 # 本篇只涉及 Kong 服务在 K8s 集群的部署操作,不涉及概念知识。\n提前准备 # K8s 集群,本文使用的是 AWS EKS 集群服务 一台可以连接 K8s 集群的服务器,已经安装 kubectl 和 docker 等基础应用,之后称之为操作机器 Postgres 数据库,作为 Kong 服务的后端数据库 初始化数据库 # 使用 Postgres 作为 Kong 服务的后端数据库,我们需要提前做数据库的初始化,准备 Kong 服务需要的数据表等。这里使用 K8s-Job 来实现数据库的初始化工作。\nkong-migrations-job.yaml:\napiVersion: batch/v1 kind: Job metadata: name: kong-migrations namespace: kong spec: template: metadata: name: kong-migrations spec: containers: - command: - /bin/sh - -c - kong migrations bootstrap env: - name: KONG_PG_PASSWORD value: \u0026#34;kong\u0026#34; - name: KONG_PG_HOST value: \u0026#34;postgres/postgres\u0026#34; - name: KONG_PG_PORT value: \u0026#34;5432\u0026#34; image: kong:1.4 name: kong-migrations initContainers: - command: - /bin/sh - -c - until nc -zv $KONG_PG_HOST $KONG_PG_PORT -w1; do echo \u0026#39;waiting for db\u0026#39;; sleep 1; done env: - name: KONG_PG_HOST value: \u0026#34;localhost:5432\u0026#34; - name: KONG_PG_PORT value: \u0026#34;5432\u0026#34; image: busybox name: wait-for-postgres restartPolicy: OnFailure 使用的是 kong 的镜像,这个镜像运行起来的容器里执行 kong migrations bootstrap 命令来完成 kong 数据库的初始化操作。\ninitContainers 容器的作用是保证 ping postgres 的网络可以连通,如果无法连通,不会执行初始化。\n在操作机器上使用以下命令开始 kong 数据库的初始化:\nkubectl apply -f kong-migrations-job.yaml 如果 kong 数据库初始化成功,你可以在数据库中看到新出现的表\nK8s资源文件 # kong-configmap.yaml:\napiVersion: v1 data: servers.conf: | # Prometheus metrics server server { server_name kong_prometheus_exporter; listen 0.0.0.0:9542; # can be any other port as well access_log off; location /metrics { default_type text/plain; content_by_lua_block { local prometheus = require \u0026#34;kong.plugins.prometheus.exporter\u0026#34; prometheus:collect() } } location /nginx_status { internal; stub_status; } } # Health check server server { server_name kong_health_check; listen 0.0.0.0:9001; # can be any other port as well access_log off; location /health { return 200; } } kind: ConfigMap metadata: name: kong-server-blocks namespace: kong 使用 nginx 对外暴露 kong 指标数据的接口,通过这个接口,prometheus 可以抓取到 kong 服务指标数据。\n« Gitlab \u0026amp; EKS\n» K8s 部署 konga\n"},{"id":14,"href":"/aws/k8s-deploy-konga/","title":"K8s Deploy Konga","section":"Aws","content":" 🏠 首页 / AWS / K8s 部署 konga\nK8s 部署 konga # 本篇只涉及 konga 的部署操作,不涉及概念知识。\n提前准备 # K8s 集群,本文使用的是 AWS EKS 集群服务 一台可以连接 K8s 集群的服务器,已经安装 kubectl 和 docker 等基础应用,之后称之为操作机器 Postgres数据库 !注意:该数据库使用 9.5 版本,其他最新版本的数据库在初始化 konga 数据库时会报如下错:\nerror: Failed to prepare database: error: column r.consrc does not exist 这是部署 konga 踩过的坑之一。\n确保 K8s 集群中已经创建了 nginx-ingress,nginx-ingress 用于根据定制的 Rule(如后文 kong-ingress 的配置)将流量转发至 K8s 集群的 Service 中去。 创建 nginx-ingress 指令(可参照 https://kubernetes.github.io/ingress-nginx/deploy/)步骤如下:\nStep 1. 执行以下强制命令\nkubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/mandatory.yaml Step 2. 针对我们使用的 AWS 服务,并且根据你想配置的 ELB 类型选择层(L4或L7),执行以下命令\n​ # Layer 4: use TCP as the listener protocol for ports 80 and 443.\n​ # Layer 7: use HTTP as the listener protocol for port 80 and terminate TLS in the ELB\n# FOR Layer 4 kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/aws/service-l4.yaml kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/aws/patch-configmap-l4.yaml # FOR Layer 7 kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/aws/service-l7.yaml kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/aws/patch-configmap-l7.yaml 初始化数据库 # 在操作机器上执行如下指令\ndocker run --rm pantsel/konga:latest -c prepare -a {{adapter}} -u {{connection-uri}} adapter:数据库类型,可选,\u0026lsquo;mongo\u0026rsquo;,\u0026lsquo;postgres\u0026rsquo;,\u0026lsquo;sqlserver\u0026rsquo; or \u0026lsquo;mysql\u0026rsquo;\nconnection-uri:数据库的连接字符串\n使用 postgres 数据库作为 konga 的存储,指令大概长这个样子:\ndocker run --rm pantsel/konga:latest -c prepare -a postgres -u postgresql://\u0026lt;user\u0026gt;:\u0026lt;password\u0026gt;@\u0026lt;host\u0026gt;:\u0026lt;port\u0026gt;/\u0026lt;database\u0026gt; 运行指令,输出如下,可以说明数据库初始化成功。\ndebug: Preparing database... Using postgres DB Adapter. Database exists. Continue... debug: Hook:api_health_checks:process() called debug: Hook:health_checks:process() called debug: Hook:start-scheduled-snapshots:process() called debug: Hook:upstream_health_checks:process() called debug: Hook:user_events_hook:process() called debug: Seeding User... debug: User seed planted debug: Seeding Kongnode... debug: Kongnode seed planted debug: Seeding Emailtransport... debug: Emailtransport seed planted debug: Database migrations completed! 可以连接postgres发现你的数据库中多出了下面这些表\nK8s 资源文件 # kanga-namespace.yaml:\napiVersion: v1 kind: Namespace metadata: name: konga konga-deployment.yaml:\napiVersion: extensions/v1beta1 kind: Deployment metadata: name: konga namespace: konga spec: replicas: 1 template: metadata: labels: name: konga app: konga spec: containers: - env: - name: \u0026#34;DB_ADAPTER\u0026#34; value: \u0026#34;postgres\u0026#34; - name: \u0026#34;DB_HOST\u0026#34; value: \u0026#34;postgres/postgres\u0026#34; - name: \u0026#34;DB_PORT\u0026#34; value: \u0026#34;5432\u0026#34; - name: \u0026#34;DB_USER\u0026#34; value: konga - name: \u0026#34;DB_PASSWORD\u0026#34; value: konga - name: \u0026#34;DB_DATABASE\u0026#34; value: konga - name: \u0026#34;DB_PG_SCHEMA\u0026#34; value: \u0026#34;public\u0026#34; # - name: BASE_URL # value: /konga/ name: konga image: pantsel/konga ports: - containerPort: 1337 该文件需要设置 postgres 的数据库配置,需要自行替换;\n可以注意到 yaml 文件中的 BASE_URL 环境变量被我注释掉了,这个参数的作用本来是设置成我们可以通过 http://www.example.com/konga/来访问konga程序的,但是镜像的作者目前还没有处理好相关的路由问题。所以目前还只能通过http://konga.example.com使用。\nkonga-service.yaml:\napiVersion: v1 kind: Service metadata: name: konga namespace: konga spec: ports: - name: tcp port: 1337 targetPort: 1337 protocol: TCP selector: app: konga kong-ingress.yaml:\napiVersion: extensions/v1beta1 kind: Ingress metadata: name: konga-ingress namespace: konga annotations: nginx.ingress.kubernetes.io/ssl-redirect: \u0026#34;false\u0026#34; nginx.ingress.kubernetes.io/force-ssl-redirect: \u0026#34;false\u0026#34; nginx.ingress.kubernetes.io/rewrite-target: / spec: tls: - hosts: - konga.example.com secretName: tls-secret rules: - host: konga.example.com http: paths: - path: / backend: serviceName: konga servicePort: 1337 这里host(s)需要替换成真正的域名。\n创建Konga # 先创建konga命令空间\nkubectl apply -f konga-namespace.yaml 再创建其他资源即可\nkubectl apply -f konga-deployment.yaml kubectl apply -f konga-service.yaml kubectl apply -f konga-ingress.yaml 验证konga # 在以上创建命令执行后大概1-2分钟,浏览器访问 http://konga.example.com,出现konga注册页面说明已经部署成功。\n« K8s 部署 Kong 服务\n» K8s 部署 Postgres\n"},{"id":15,"href":"/aws/k8s-deploy-postgres/","title":"K8s Deploy Postgres","section":"Aws","content":" 🏠 首页 / AWS / K8s 部署 Postgres\nK8s 部署 Postgres # 本篇只涉及 Postgres 的部署操作,不涉及概念知识。\n特别说明:Postgres 相对于普通的程序应用而言,属于有状态的服务,因为它存储的数据是需要持久保存的,这点决定了我们选择 K8s-StatefulSet 的部署而非 K8s-Deployment。\n提前准备 # K8s 集群,本文使用的是AWS EKS集群服务 一台可以连接 K8s 集群的服务器,已经安装 kubectl 和 docker 等基础应用 K8s资源文件 # postgres-namespace.yaml:\napiVersion: v1 kind: Namespace metadata: name: postgres postgres-config.yaml:\napiVersion: v1 kind: ConfigMap metadata: name: postgres-config namespace: postgres labels: app: postgres data: POSTGRES_DB: master POSTGRES_USER: dba POSTGRES_PASSWORD: pg_pass 这里的数据库密码涉及到信息敏感,更建议使用 Secret 资源而非 ConfigMap,这里就偷懒了。\npostgres-statefulset.yaml:\napiVersion: apps/v1 kind: StatefulSet metadata: name: postgres namespace: postgres spec: serviceName: \u0026#34;postgres\u0026#34; replicas: 1 selector: matchLabels: app: postgres template: metadata: labels: app: postgres spec: containers: - name: postgres image: postgres:9.5 envFrom: - configMapRef: name: postgres-config ports: - containerPort: 5432 name: postgredb volumeMounts: - name: postgres-data mountPath: /var/lib/postgresql/data subPath: postgres volumeClaimTemplates: - metadata: name: postgres-data spec: accessModes: [\u0026#34;ReadWriteOnce\u0026#34;] storageClassName: gp2 resources: requests: storage: 4Gi 在这个 yaml 中,可以看到我们使用了之前创建的 configmap 来设置环境变量;\n在文件的底部,我们使用了 gp2 的 storageClass 帮助我们自动创建 PV、PVC, 以及关联,你可能需要替换。\npostgres-service.yaml:\napiVersion: v1 kind: Service metadata: name: postgres namespace: postgres labels: app: postgres spec: ports: - port: 5432 name: postgres type: LoadBalancer selector: app: postgres 创建Postgres # 先创建 postgres 命令空间\nkubectl apply -f postgres-namespace.yaml 再创建其他资源即可\nkubectl apply -f postgres-config.yaml kubectl apply -f postgres-statefulset.yaml kubectl apply -f postgres-service.yaml 因为我们是在 AWS 的 EKS 集群中创建资源,所以在以上命令执行完成后,我们可以在 AWS 的 Volume 中找到我们创建的存储卷。\n查看该卷信息,发现它其实已经挂载在集群的一个节点上去了。\n验证Postgres # 我们使用以下命令查看我们创建的 postgres-service\nkubectl get pod -n postgres kubectl get svc -n postgres 输出如下,说明我们的 postgres 已经部署成功\n推荐使用 DBeaver 数据库可视化工具测试连接 postgres,使用的 host 正是输出的 EXTERNAL-IP 列的信息,而其他连接信息在我们的 ConfigMap 中。\n« K8s 部署 konga\n» Terraform 重新管理资源\n"},{"id":16,"href":"/aws/terraform-remanage-resource/","title":"Terraform Remanage Resource","section":"Aws","content":" 🏠 首页 / AWS / Terraform 重新管理资源\nTerraform 重新管理资源 # 看到这个标题你可能会有点懵,我先来解释下。\n在使用Terraform管理AWS的VPC-Subnet资源时(下面是定义资源的代码清单),我遇到了一个问题:当我修改aws_subnet.eks-private-subnet-1资源的cidr_block时,假设我修改成了172.28.2.0/24,这时候旧的\nresource \u0026#34;aws_subnet\u0026#34; \u0026#34;eks-private-subnet-1\u0026#34; { vpc_id = \u0026#34;${var.vpc_id}\u0026#34; cidr_block = \u0026#34;172.28.1.0/24\u0026#34; map_public_ip_on_launch = \u0026#34;false\u0026#34; availability_zone = \u0026#34;${var.region}a\u0026#34; tags = merge( {Name = \u0026#34;${var.cluster_name}-private-subnet-1a\u0026#34;}, \u0026#34;${local.cluster_private_subnet_tags}\u0026#34;) } « K8s 部署 Postgres\n"},{"id":17,"href":"/cka/001/","title":"1st","section":"Cka","content":" 🏠 首页 / CKA / 001\n001 # 01 Task - 英文 # Create a new ClusterRole named deployment-clusterrole that only allows the creation of the following resource types:\nDeployment StatefulSet DaemonSet Create a new ServiceAccount named cicd-token in the existing namespace app-team1. Limited to namespace app-team1, bind the new ClusterRole deployment-clusterrole to the new ServiceAccount cicd-token. kubectl create ns app-team1 kubectl create serviceaccount cicd-token -n app-team1 kubectl create clusterrole deployment-clusterrole --verb=create --resource=deployment,statefulset,daemonset #limted to the namespace app-team1。需要限制的是namespace级别,clusterrolebinding为设置全局,rolebinding正确 kubectl create rolebinding cicd-clusterrole -n app-team1 --clusterrole=deployment-clusterrole --serviceaccount=app-team1:cicd-token 02 Task - 英文 # Set the node named ek8s-node-1 as unavaliable and reschedule all the pods running on it.\nkubectl cordon ek8s-node-1 kubectl drain ek8s-node-1 --delete-local-data --ignore-daemonsets --force //删除所有pod(包括daemonset管理的pod),则需要--ignore-daemonsets或--ignore-daemonsets=true 03 Task - 英文 # Given an existing Kubernetes cluster running version 1.18.8,upgrade all of Kubernetes control plane and node components on the master node only to version 1.19.0。\nYou are also expected to upgrade kubelet and kubectl on the master node。\nBe sure to drain the master node before upgrading it and uncordon it after the upgrade. Do not upgrade the worker nodes,etcd,the container manager,the CNI plugin,the DNS service or any other addons.\napt update apt-cache policy kubeadm apt-get update \u0026amp;\u0026amp; apt-get install -y --allow-change-held-packages kubeadm=1.19.0 kubeadm version #检查kubeadm版本 kubectl drain master --ignore-daemonsets --delete-local-data --force #腾空控制平面节点 sudo kubeadm upgrade plan # 命令查看可升级的版本信息 sudo kubeadm upgrade apply v1.19.0 --etcd-upgrade=false #查看版本信息时,排除etcd从3.4.3-0升到3.4.7-0 kubectl uncordon master sudo kubeadm upgrade node #升级其他控制面节点 apt-get update \u0026amp;\u0026amp; apt-get install -y --allow-change-held-packages kubelet=1.19.0 kubectl=1.19.0 #升级其他控制面节点 sudo systemctl daemon-reload sudo systemctl restart kubelet 04 Task - 中文 # 首先,为运行在 https://127.0.0.1:2379 上的现有etcd 实例创建快照并将快照保存到/data/backup/etcd-snapshot.db。\n为给定实例创建快照预计能在几秒钟内完成。如果该操作似乎挂起,则命令可能有问题。用ctrl+c 来取消操作,然后重试。\n然后还原位于/var/data/etcd-snapshot-previous.db的现有先前快照。\n提供了以下TLS证书和密钥,以通过etcdctl连接到服务器。\nca证书:/opt/KUIN00601/ca.crt 客户端证书:/opt/KUIN00601/etcd-client.crt 客户端密钥:/opt/KUIN00601/etcd-client.key 一定要把这参数用熟练,如果考试时有问题,不要急,多试试!!!\n一旦正确配置了 etcd,只有具有有效证书的客户端才能访问它。要让 Kubernetes API 服务器访问,可以使用参数 \u0026ndash;etcd-certfile=k8sclient.cert,–etcd-keyfile=k8sclient.key 和 \u0026ndash;etcd-cafile=ca.cert 配置它。\n我记得我考试进用的是:–certfile=/opt/KUIN00601/etcd-client.crt \u0026ndash;keyfile=/opt/KUIN00601/etcd-client.key \u0026ndash;cafile=/opt/KUIN00601/ca.crt\nETCDCTL_API=3 etcdctl --endpoint=https://127.0.0.1:2379 --certfile=/opt/KUIN00601/etcd-client.crt --keyfile=/opt/KUIN00601/etcd-client.key --cafile=/opt/KUIN00601/ca.crt snapshot save /data/backup/etcd-snapshot.db ETCDCTL_API=3 etcdctl --endpoint=https://127.0.0.1:2379 --cert-file=/opt/KUIN00601/etcd-client.crt --key-file=/opt/KUIN00601/etcd-client.key --ca-file=/opt/KUIN00601/ca.crt snapshot restore /var/data/etcd-snapshot-previous.db 05 Task - 英文 # Create a new NetworkPolicy named allow-port-from-namespace to allow Pods in the existing namespace internal to connect to port 8080 of other Pods in the same namespace. Ensure that the new NetworkPolicy:\ndoes not allow access to Pods not listening on port 8080. does not allow access from Pods not in namespace internal. #network.yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-port-from-namespace namespace: internal spec: podSelector: matchLabels: {} policyTypes: - Ingress ingress: - from: - podSelector: {} ports: - protocol: TCP port: 8080 #spec.podSelector限定了这个namespace里的pod可以访问 kubectl create -f network.yaml 06 Task - 英文 # Reconfigure the existing deployment front-end and add a port specifiction named http exposing port 80/tcp of the existing container nginx.\nCreate a new service named front-end-svc exposing the container prot http.\nConfigure the new service to also expose the individual Pods via a NodePort on the nodes on which they are scheduled.\nkubectl get deploy front-end kubectl edit deploy front-end -o yaml #port specification named http #service.yaml apiVersion: v1 kind: Service metadata: name: front-end-svc labels: app: nginx spec: ports: - port: 80 protocol: tcp name: http selector: app: nginx type: NodePort # kubectl create -f service.yaml # kubectl get svc #或者一条命令搞定,注意会遗漏port specification named http kubectl expose deployment front-end --name=front-end-svc --port=80 --tarport=80 --type=NodePort 07 Task - 英文 # Create a new nginx Ingress resource as follows:\nName: ping: Namespace: ing-internal: Exposing service hi on path /hi using service port 5678: The avaliability of service hi can be checked using the following command,which should return hi: curl -kL /hi\nvi ingress.yaml # apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ping namespace: ing-internal spec: rules: - http: paths: - path: /hi pathType: Prefix backend: service: name: hi port: number: 5678 # kubectl create -f ingress.yaml 08 Task - 英文 # Scale the deployment presentation to 3 pods.\nkubectl get deployment kubectl scale deployment.apps/presentation --replicas=3 09 Task - 英文 # Task\nSchedule a pod as follows:\nname: nginx-kusc00401: Image: nginx: Node selector: disk-spinning: #yaml apiVersion: v1 kind: Pod metadata: name: nginx-kusc00401 spec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent nodeSelector: disk: spinning # kubectl create -f node-select.yaml 10 Task - 英文 # Task Check to see how many nodes are ready (not including nodes tainted NoSchedule)and write the number to /opt/KUSC00402/kusc00402.txt.:\nkubectl describe nodes | grep ready|wc -l kubectl describe nodes | grep -i taint | grep -i noschedule |wc -l echo 3 \u0026gt; /opt/KUSC00402/kusc00402.txt # 查询集群Ready节点数量 kubectl get node | grep -i ready |wc -l # 找出节点taints、noSchedule kubectl describe nodes | grep -i taints | grep -i noschedule |wc -l #将得到的减数,写入到文件 echo 2 \u0026gt; /opt/KUSC00402/kusc00402.txt 11 Task - 英文 # Create a pod named kucc8 with a single app container for each of the following images running inside (there may be between 1 and 4 images specified): nginx + redis + memcached + consul .:\nkubectl run kucc8 --image=nginx --dry-run -o yaml \u0026gt; kucc8.yaml # vi kucc8.yaml apiVersion: v1 kind: Pod metadata: creationTimestamp: null name: kucc8 spec: containers: - image: nginx name: nginx - image: redis name: redis - image: memcached name: memcached - image: consul name: consul # kubectl create -f kucc8.yaml #12.07 12 Task - 英文 # Task Create a persistent volume whit name app-config, of capacity 1Gi and access mode ReadOnlyMany . the type of volume is hostPath and its location is /srv/app-config .:\n#vi pv.yaml apiVersion: v1 kind: PersistentVolume metadata: name: app-config spec: capacity: storage: 1Gi accessModes: - ReadOnlyMany hostPath: path: /srv/app-config # kubectl create -f pv.yaml 13 Task - 英文 # Task Create a new PersistentVolumeClaim:\nName: pv-volume: Class: csi-hostpath-sc: Capacity: 10Mi: Create a new Pod which mounts the PersistentVolumeClaim as a volume:\nName: web-server: Image: nginx: Mount path: /usr/share/nginx/html: Configure the new Pod to have ReadWriteOnce access on the volume.\nFinally,using kubectl edit or Kubectl patch expand the PersistentVolumeClaim to a capacity of 70Mi and record that change.\nvi pvc.yaml #使用指定storageclass创建一个pvc apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pv-volume spec: accessModes: - ReadWriteOnce volumeMode: Filesystem resources: requests: storage: 10Mi storageClassName: csi-hostpath-sc # vi pod-pvc.yaml apiVersion: v1 kind: Pod metadata: name: web-server spec: containers: - name: web-server image: nginx volumeMounts: - mountPath: \u0026#34;/usr/share/nginx/html\u0026#34; name: my-volume volumes: - name: my-volume persistentVolumeClaim: claimName: pv-volume # craete kubectl create -f pod-pvc.yaml #edit 修改容量 kubectl edit pvc pv-volume --record 14 Task - 英文 # Task Monitor the logs of pod bar and:\nExtract log lines corresponding to error unable-to-access-website: Write them to /opt/KUTR00101/bar: kubectl logs bar | grep \u0026#39;unable-to-access-website\u0026#39; \u0026gt; /opt/KUTR00101/bar cat /opt/KUTR00101/bar 15 Task - 英文 # Context Without changing its existing containers,an existing Pod needs to be integrated into Kubernetes’s build-in logging architecture (e.g. kubectl logs). Adding a streaming sidecar container is a good and common way to accomplish this requirement.\nTask Add a busybox sidecar container to the existing Pod big-corp-app. The new sidecar container has to run the following command:\n/bin/sh -c tail -n+1 -f /var/log/big-corp-app.log Use a volume mount named logs to make the file /var/log/big-corp-app.log available to the sidecar container.\nDon’t modify the existing container. Don’t modify the path of the log file,both containers must access it at /var/log/big-corp-app.log.\n# kubectl get pod big-corp-app -o yaml # apiVersion: v1 kind: Pod metadata: name: big-corp-app spec: containers: - name: big-corp-app image: busybox args: - /bin/sh - -c - \u0026gt; i=0; while true; do echo \u0026#34;$(date) INFO $i\u0026#34; \u0026gt;\u0026gt; /var/log/big-corp-app.log; i=$((i+1)); sleep 1; done volumeMounts: - name: logs mountPath: /var/log - name: count-log-1 image: busybox args: [/bin/sh, -c, \u0026#39;tail -n+1 -f /var/log/big-corp-app.log\u0026#39;] volumeMounts: - name: logs mountPath: /var/log volumes: - name: logs emptyDir: {} #验证: kubectl logs big-corp-app -c count-log-1 16 Task - 英文 # Form the pod label name-cpu-loader,find pods running high CPU workloads and write the name of the pod consuming most CPU to the file /opt/KUTR00401/KURT00401.txt(which alredy exists).\n查看Pod标签为name=cpu-user-loader 的CPU使用率并且把cpu使用率最高的pod名称写入/opt/KUTR00401/KUTR00401.txt文件里\nkubectl top pods -l name=name-cpu-loader --sort-by=cpu echo \u0026#39;排名第一的pod名称\u0026#39; \u0026gt;\u0026gt;/opt/KUTR00401/KUTR00401.txt 17 Task - 英文 # Task A Kubernetes worker node,named wk8s-node-0 is in state NotReady . Investigate why this is the case,and perform any appropriate steps to bring the node to a Ready state,ensuring that any changes are made permanent.\nYon can ssh to teh failed node using:\nssh wk8s-node-o You can assume elevated privileges on the node with the following command:\nsudo -i #名为wk8s-node-1 的节点处于NotReady状态,将其恢复成Ready状态,并且设置为开机自启 # 连接到NotReady节点 ssh wk8s-node-0 #获取权限 sudo -i # 查看服务是否运行正常 systemctl status kubelet #如果服务非正常运行进行恢复 systemctl start kubelet #设置开机自启 systemctl enable kubelet 19 Task - 英文 # Set configuration context $ kubectl config use-context wk8s\nconfigure the kubelet systemed managed service, on the node labelled with name=wk8s-node-1,to launch a pod containing a single container of image nginx named myservice automatically.\nAny spec file requried should be placed in the /etc/kuberneteds/mainfests directory on the node\nHints:\nYou can ssh to the failed node using $ ssh wk8s-node-0\nYou can assume elevated privileges on the node with the following command $ sudo -i\n静态Pod创建方法与注意点 # Set configuration context $ kubectl config use-context wk8s\nconfigure the kubelet systemed managed service, on the node labelled with name=wk8s-node-1,to launch a pod containing a single container of image nginx named myservice automatically.\nAny spec file requried should be placed in the /etc/kuberneteds/mainfests directory on the node\nHints:\nYou can ssh to the failed node using $ ssh wk8s-node-0\nYou can assume elevated privileges on the node with the following command $ sudo -i\nkubectl config use-context wk8s kubectl get node -l=name=wk8s-node-0 -o wide # or kubectl get node -l name=wk8s-node-0 -o wide sudo wk8s-node-0 sudo -i systemctl status kubelet -l |grep config #找到--config配置的文件路径 cat /var/lib/kubelet/config.yaml |grep staticPodPath # 得到/etc/kubernetes/manifests cd /etc/kubernetes/manifests kubectl run myservice --image=nginx --dry-run=client -o yaml \u0026gt; myservice.yaml kubectl get pod -A|grep myservice #可以得到静态Pod » 准备CKA\n"},{"id":18,"href":"/cka/","title":"Cka","section":"","content":" 🏠 首页 / CKA\nCKA # 001\n准备CKA\n考题\n"},{"id":19,"href":"/cka/prepare-cka/","title":"Prepare Cka","section":"Cka","content":" 🏠 首页 / CKA / 准备CKA\n准备CKA # 办理护照 # 优惠券:\nAffkub95-268483\nFrequently Asked Questions: CKA and CKAD \u0026amp; CKS - T\u0026amp;C DOC (linuxfoundation.org)\n« 001\n» 考题\n"},{"id":20,"href":"/cka/tasks/","title":"Tasks","section":"Cka","content":" 🏠 首页 / CKA / 考题\n考题 # kubectl scale deployment nginx --replicas=3 创建busybox:\nkubectl run busybox --image=busybox --generator=run-pod/v1 --command=true -- sleep 7d # nslookup kubectl run nginx-dns --image=nginx kubectl run busybox --image=busybox --generator=run-pod/v1 --command=true -- sleep 7d kubectl exec -it busybox -- nslookup nginx-dns kubectl exec -it busybox -- nslookup \u0026lt;pod-ip\u0026gt; etcd备份和还原:\nETCDCTL_API=3 etcdctl --endpoints=https://[127.0.0.1]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt \\ --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key \\ snapshot save ETCDCTL_API=3 etcdctl --endpoints=https://[127.0.0.1]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt \\ --name=master \\ --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key \\ --data-dir /var/lib/etcd-from-backup \\ --initial-cluster=master=https://127.0.0.1:2380 \\ --initial-cluster-token etcd-cluster-1 \\ --initial-advertise-peer-urls=https://127.0.0.1:2380 \\ snapshot restore ... « 准备CKA\n"},{"id":21,"href":"/cs/","title":"Cs","section":"","content":" 🏠 首页 / 计算机科学\n计算机科学 # 互联网如何运作?\n网络通信\n虚拟内存\n"},{"id":22,"href":"/cs/internet/","title":"Internet","section":"Cs","content":" 🏠 首页 / 计算机科学 / 互联网如何运作?\n互联网如何运作? # 本篇文章翻译自: 原文地址\n作为开发人员,深入了解互联网是什么及其工作原理非常重要。它是构建大多数现代软件应用程序的基础。为了构建有效、安全且可扩展的应用程序和服务,您需要深入了解互联网的工作原理以及如何利用其功能和连接性。\n在本文中,我们将介绍互联网的基础知识,包括它的工作原理、一些基本概念、术语和一些用于在互联网上构建应用程序和服务的通用协议。\n我们有很多内容要介绍,所以让我们开始吧!\n互联网简介 # 在了解什么是互联网之前,我们需要先了解什么是网络。网络是一组相互连接的计算机或其他设备。例如,您家里可能有一个由计算机和设备组成的网络。您住在隔壁的朋友可能有类似的设备网络。他们的邻居可能有类似的设备网络。所有这些网络连接在一起就形成了互联网。\n互联网是一个网络的网络。\n互联网是美国国防部于 20 世纪 60 年代末开发的,作为创建能够抵御核攻击的去中心化通信网络的一种手段。多年来,它已发展成为一个遍布全球的复杂而精密的网络。\n如今,互联网已成为现代生活的重要组成部分,世界各地数十亿人使用互联网来获取信息、与朋友和家人交流、开展业务等等。作为开发人员,必须充分了解互联网的工作原理以及支撑互联网的各种技术和协议。\n互联网如何运作:概述 # 在较高层面上,互联网的工作原理是使用一组标准化协议将设备和计算机系统连接在一起。这些协议定义了设备之间如何交换信息,并确保数据可靠、安全地传输。\n互联网的核心是互连路由器的全球网络,负责引导不同设备和系统之间的流量。当您通过互联网发送数据时,数据会被分解为小数据包,然后从您的设备发送到路由器。路由器检查数据包并将其转发到通往目的地的路径中的下一个路由器。这个过程一直持续到数据包到达最终目的地。\n为了确保数据包的正确发送和接收,互联网使用了多种协议,包括互联网协议(IP)和传输控制协议(TCP)。 IP 负责将数据包路由到正确的目的地,而 TCP 确保数据包以正确的顺序可靠地传输。\n除了这些核心协议之外,还有许多其他技术和协议用于通过互联网实现通信和数据交换,包括域名系统 (DNS)、超文本传输​​协议 (HTTP) 和安全协议套接字层/传输层安全 (SSL/TLS) 协议。作为开发人员,深入了解这些不同的技术和协议如何协同工作以实现互联网上的通信和数据交换非常重要。\n基本概念和术语 # 要了解互联网,熟悉一些基本概念和术语很重要。以下是一些需要注意的关键术语和概念:\n数据包:通过互联网传输的小数据单位。 路由器:在不同网络之间引导数据包的设备。 IP 地址:分配给网络上每个设备的唯一标识符,用于将数据路由到正确的目的地。 域名:用于识别网站的人类可读名称,例如 google.com。 DNS:域名系统负责将域名转换为IP地址。 HTTP:超文本传输​​协议用于在客户端(例如网络浏览器)和服务器(例如网站)之间传输数据。 HTTPS:HTTP 的加密版本,用于在客户端和服务器之间提供安全通信。 SSL/TLS:安全套接字层和传输层安全协议用于通过互联网提供安全通信。 了解这些基本概念和术语对于使用互联网和开发基于互联网的应用程序和服务至关重要。\n协议在互联网中的作用 # 协议在通过互联网进行通信和数据交换方面发挥着至关重要的作用。协议是一组规则和标准,定义设备和系统之间如何交换信息。\n互联网通信中使用许多不同的协议,包括互联网协议 (IP)、传输控制协议 (TCP)、用户数据报协议 (UDP)、域名系统 (DNS) 等。\nIP 负责将数据包路由到正确的目的地,而 TCP 和 UDP 则确保数据包可靠且高效地传输。 DNS 用于将域名转换为 IP 地址,HTTP 用于在客户端和服务器之间传输数据。\n使用标准化协议的主要好处之一是它们允许来自不同制造商和供应商的设备和系统彼此无缝通信。例如,一家公司开发的 Web 浏览器可以与另一家公司开发的 Web 服务器通信,只要它们都遵守 HTTP 协议。\n作为开发人员,了解互联网通信中使用的各种协议以及它们如何协同工作以实现通过互联网传输数据和信息非常重要。\n了解 IP 地址和域名 # IP 地址和域名都是使用互联网时需要理解的重要概念。\nIP 地址是分配给网络上每个设备的唯一标识符。它用于将数据路由到正确的目的地,确保信息发送到预期的收件人。 IP 地址通常表示为由句点分隔的一系列四个数字,例如“192.168.1.1”。\n另一方面,域名是人类可读的名称,用于识别网站和其他互联网资源。它们通常由两个或多个部分组成,并用句点分隔。例如,“google.com”是一个域名。使用域名系统 (DNS) 将域名转换为 IP 地址。\nDNS 是互联网基础设施的重要组成部分,负责将域名转换为 IP 地址。当您在 Web 浏览器中输入域名时,您的计算机会向 DNS 服务器发送 DNS 查询,该服务器会返回相应的 IP 地址。然后,您的计算机使用该 IP 地址连接到您请求的网站或其他资源。\nHTTP 和 HTTPS 简介 # HTTP(超文本传输​​协议)和 HTTPS(HTTP 安全)是基于互联网的应用程序和服务中最常用的两种协议。\nHTTP 是用于在客户端(例如网络浏览器)和服务器(例如网站)之间传输数据的协议。当您访问网站时,您的网络浏览器会向服务器发送 HTTP 请求,询问您请求的网页或其他资源。然后,服务器将包含请求数据的 HTTP 响应发送回客户端。\nHTTPS 是 HTTP 的更安全版本,它使用 SSL/TLS(安全套接字层/传输层安全性)加密技术对客户端和服务器之间传输的数据进行加密。这提供了额外的安全层,有助于保护敏感信息,例如登录凭据、支付信息和其他个人数据。\n当您访问使用 HTTPS 的网站时,您的网络浏览器将在地址栏中显示一个挂锁图标,表明连接是安全的。您还可能会在网站地址开头看到字母“https”,而不是“http”。\n使用 TCP/IP 构建应用程序 # TCP/IP(传输控制协议/互联网协议)是大多数基于互联网的应用程序和服务使用的底层通信协议。它在不同设备上运行的应用程序之间提供可靠、有序且经过错误检查的数据传输。\n使用 TCP/IP 构建应用程序时,需要理解几个关键概念:\n端口:端口用于识别设备上运行的应用程序或服务。每个应用程序或服务都分配有一个唯一的端口号,允许将数据发送到正确的目的地。 套接字:套接字是 IP 地址和端口号的组合,代表通信的特定端点。套接字用于在设备之间建立连接并在应用程序之间传输数据。 连接:当两个设备想要相互通信时,在两个套接字之间建立连接。在连接建立过程中,设备会协商各种参数,例如最大段大小和窗口大小,这些参数决定如何通过连接传输数据。 数据传输:一旦建立连接,就可以在每个设备上运行的应用程序之间传输数据。数据通常分段传输,每个段包含序列号和其他元数据以确保可靠传输。 使用 TCP/IP 构建应用程序时,您需要确保应用程序设计为可使用适当的端口、套接字和连接。您还需要熟悉 TCP/IP 常用的各种协议和标准,例如 HTTP、FTP(文件传输协议)和 SMTP(简单邮件传输协议)。了解这些概念和协议对于构建有效、可扩展且安全的基于互联网的应用程序和服务至关重要。\n使用 SSL/TLS 保护互联网通信 # 正如我们之前讨论的,SSL/TLS 是一种用于加密通过互联网传输的数据的协议。它通常用于为 Web 浏览器、电子邮件客户端和文件传输程序等应用程序提供安全连接。\n使用 SSL/TLS 保护互联网通信时,需要了解一些关键概念:\n证书:SSL/TLS 证书用于在客户端和服务器之间建立信任。它们包含有关服务器身份的信息,并由受信任的第三方(证书颁发机构)签名以验证其真实性。\n握手:在 SSL/TLS 握手过程中,客户端和服务器交换信息以协商安全连接的加密算法和其他参数。\n加密:建立安全连接后,数据将使用约定的算法进行加密,并可以在客户端和服务器之间安全传输。\n在构建基于 Internet 的应用程序和服务时,了解 SSL/TLS 的工作原理并确保您的应用程序设计为在传输登录凭据、支付信息和其他个人数据等敏感数据时使用 SSL/TLS 非常重要。您还需要确保为您的服务器获取并维护有效的 SSL/TLS 证书,并遵循配置和保护 SSL/TLS 连接的最佳实践。通过这样做,您可以帮助保护用户数据并确保应用程序通过互联网进行通信的完整性和机密性。\n未来:新兴趋势和技术 # 互联网在不断发展,新技术和趋势不断涌现。作为开发人员,为了构建创新且有效的应用程序和服务,了解最新的发展非常重要。\n以下是塑造互联网未来的一些新兴趋势和技术:\n5G:5G是最新一代移动网络技术,与前几代相比,速度更快、延迟更低、容量更大。预计它将实现新的用例和应用,例如自动驾驶车辆和远程手术。\n物联网(IoT):物联网是指连接到互联网并可以交换数据的物理设备、车辆、家用电器和其他物体的网络。随着物联网的不断发展,预计它将彻底改变医疗保健、运输和制造等行业。\n人工智能 (AI):机器学习和自然语言处理等人工智能技术已被用于支持从语音助手到欺诈检测等广泛的应用程序和服务。随着人工智能的不断发展,预计它将催生新的用例并改变医疗保健、金融和教育等行业。\n区块链:区块链是一种分布式账本技术,可实现安全、去中心化的交易。它被用来为从加密货币到供应链管理的广泛应用提供支持。\n边缘计算:边缘计算是指在网络边缘而不是在集中式数据中心处理和存储数据。预计它将支持新的用例和应用程序,例如实时分析和低延迟应用程序。\n通过及时了解这些以及其他新兴趋势和技术,您可以确保您的应用程序和服务能够利用最新功能并为用户提供最佳体验。\n结论 # 这就是本文的结尾。我们已经涵盖了很多内容,所以让我们花点时间回顾一下我们所学到的内容:\n互联网是一个由互连计算机组成的全球网络,使用一组标准的通信协议来交换数据。 互联网的工作原理是使用标准化协议(例如 IP 和 TCP)将设备和计算机系统连接在一起。 互联网的核心是一个由互连路由器组成的全球网络,用于引导不同设备和系统之间的流量。 您需要熟悉的基本概念和术语包括数据包、路由器、IP 地址、域名、DNS、HTTP、HTTPS 和 SSL/TLS。 协议在通过互联网进行通信和数据交换方面发挥着至关重要的作用,允许来自不同制造商和供应商的设备和系统无缝通信。 我希望您觉得这篇文章有用。如果您有任何问题或意见,请随时在下面留言。谢谢阅读!\n» 网络通信\n"},{"id":23,"href":"/cs/networking/","title":"Networking","section":"Cs","content":" 🏠 首页 / 计算机科学 / 网络通信\n网络通信 # I/O 模型 # 《UNIX 网络编程》中总结了 5 种 I/O 模型,包括同步和异步 I/O:\n阻塞 I/O (Blocking I/O) 非阻塞 I/O (Nonblocking I/O) I/O 多路复用 (I/O multiplexing) 信号驱动 I/O (Signal driven I/O) 异步 I/O (Asynchronous I/O) 操作系统上的 I/O 是用户空间和内核空间的数据交互。\n« 互联网如何运作?\n» 虚拟内存\n"},{"id":24,"href":"/cs/virtual-memory/","title":"Virtual Memory","section":"Cs","content":" 🏠 首页 / 计算机科学 / 虚拟内存\n虚拟内存 # 为了更加有效的管理内存并且降低内存出错的概率。\n计算机存储器 # 速度快 容量大 价格便宜 类型,自上向下分别是寄存器,高速缓存,主存(RAM),磁盘。成本与访问速度负相关。\n寄存器的容量:32 位:32x32 bit吗,64 位:64x64 bit\n1 个字节(Bytes)等于 8 bit,因此 1kb 是 8x1024 bit\n主存(RAM)与 CPU 直接交换数据的内部存储器。\n物理内存 虚拟内存 虚拟内存核心原理 # 为每个程序设置一段\u0026quot;连续\u0026quot;的虚拟地址空间,把这个地址空间分割成多个具有连续地址范围的页 (Page),并把这些页和物理内存做映射,在程序运行期间动态映射到物理内存。当程序引用到一段在物理内存的地址空间时,由硬件立刻执行必要的映射;而当程序引用到一段不在物理内存中的地址空间时,由操作系统负责将缺失的部分装入物理内存并重新执行失败的指令。\n内存交换(swap) # 在进程运行期间只分配映射当前使用到的内存,暂时不使用的数据则写回磁盘作为副本保存,需要用的时候再读入内存,动态地在磁盘和内存之间交换数据。\n参考 # 虚拟内存精粹 « 网络通信\n"},{"id":25,"href":"/dapr/","title":"Dapr","section":"","content":" 🏠 首页 / Dapr\nDapr # Dapr 0-1\n"},{"id":26,"href":"/dapr/dapr/","title":"Dapr","section":"Dapr","content":" 🏠 首页 / Dapr / Dapr 0-1\nDapr 0-1 # 介绍 # Dapr(Distributed Application Runtime),提供分布式应用运行所需要的环境。\nSidecar架构。\n目的:\n快速落地微服务,将业务和基础设施分离,专注于业务开发,降低微服务的复杂性。\n运行环境:\n服务发现 负载均衡 故障转移 熔断限流 缓存 异步通信 日志组件 链路监控 \u0026hellip; 核心功能:\nService Invocation(服务调用) State Management(状态管理) Publish and Subscribe(消息发布订阅) Resource bingdings and triggers(资源绑定,事件触发) Actors(单线程模型)分布式锁 Observability(遥测)ELK,链路监控,告警 Secrets(安全)IdentityServer4 安装 # 依赖 # Docker: https://docs.docker.com/install/ 注意:windows平台,Docker必须运行Linux Containers模式\n安装cli # https://github.com/dapr/cli\n以在linux中安装dapr为例:\nwget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O-|/bin/bash dapr init --runtime-version Service Invocation # 解决微服务之间通信的问题。\n"},{"id":27,"href":"/design-pattern/","title":"Design Pattern","section":"","content":" 🏠 首页 / 设计模式\n设计模式 # CI/CD\n"},{"id":28,"href":"/design-pattern/cicd/","title":"Cicd","section":"Design Pattern","content":" 🏠 首页 / 设计模式 / CI/CD\nCI/CD # Concepts # Pipeline # PipelineStage # Fields:\nName Type Desc ID string PipelineID string PrevPipelineStageID string Status uint8 Pending, Runing, Success, Failed, Abort Name string PipelineStageTask # Fields:\nName Type Desc ID string PipelineStageID string PrevPipelineStageTaskID string Image string Container image task running on. DindRequired boolean Docker in docker? DindImage string dependency DindRequired default: docker:18-dind. Scripts string CacheDirs string EnvironmentVariables string Status string EnvironmentVariables string EnvironmentVariables string "},{"id":29,"href":"/devops/","title":"Devops","section":"","content":" 🏠 首页 / DevOps\nDevOps # Agile\nAnsible\n蓝绿部署、滚动部署和灰度部署\n混沌工程原则 (PRINCIPLES OF CHAOS ENGINEERING)\n商业画布\n使用grafana监控5xx服务\n使用Grafana监控service\nGrafana\nJaeger\nnginx\n"},{"id":30,"href":"/devops/agile/","title":"Agile","section":"Devops","content":" 🏠 首页 / DevOps / Agile\nAgile # 敏捷 交付产品可以看作饭店上菜 顾客点了十个菜 后厨把十个菜做完,最后十个菜一起上桌 ——不敏捷 后厨做完一个菜就上一个菜 ——敏捷\n敏捷的优点:\n做一盘上一盘,顾客早早就能吃上了,优先横扫饥饿;尽早给用户体验上产品; 做一盘上一盘,每个菜都是新出锅,顾客能吃上一口热的;对比十盘菜一起上,可能先炒的菜已经凉了,凉的菜换做成产品的话,可能就是已经过时的功能了,不符合需求了 做一盘上一盘, 如果前面的菜咸了,可以反馈给饭店,后面的菜做淡点;对比十盘菜一起上,顾客就无法从中间反馈意见了,做一个用户插不上意见的产品,严重的后果可能是用户已经不感兴趣了。 » Ansible\n"},{"id":31,"href":"/devops/ansible/","title":"Ansible","section":"Devops","content":" 🏠 首页 / DevOps / Ansible\nAnsible # 介绍 # 一款轻量级的自动化运维工具,只需要一台主机安装Ansible,便可以管理其他可连通的Linux服务器。\n开源\npython\n特点 # 自动化引擎,实现管理配置,应用部署,服务编排及其他各种服务器管理需求; 使用简单,具有客户端的特点; 基于ssh实现配置管理; 依赖python; 功能强大,支持云服务操作。 安装 # ​ 由于Ansible使用Python开发,所以可以直接使用pip安装\npip install ansible ​ 也可以使用yum或apt-get安装\nyum install ansible -y apt-get install ansible -y ansible --version ​ 只需要在Control Node上安装即可。\n主要概念 # Control node:\n安装了Ansible的主机都可以称之为Control node,反过来说,Ansible安装在Contriol Node上;\n一般为linux机器(注:目前也已经对windows做了支持)。\nManaged nodes:\n待管理的网络设备或者服务器;\nlinux机器(安装了python)。\nInventory:\nModules:\nTasks:\nAnsible配置 # ​ 配置文件位置:/etc/ansible/ansible.cfg,为ini格式文件。\n配置示例 # ​ inventory:主机清单配置文件位置,在使用Ansible命令时,也开始-i \u0026lt;path\u0026gt;指定;\n​ host_key_checking:当know_hosts中不存在的主机(即尚未访问过的主机,是否需要输入密钥);\n​ become_user:sudo用户;\n主机清单(Inventory) # ​ 在一个ini或yaml文件中管理Managed nodes清单,默认安装ansible后,在/etc/ansible/hosts文件中管理(后文统一称之为主机清单文件),一般是将maneged nodes的ip address或host name等信息存储到主机清单文件中。\nFormats # ​ 主机清单文件格式兼容两种:\nini,因为配置方便简单,一般常选。\nmail.example.com [webservers] foo.example.com bar.example.com yaml\nall: hosts: mail.example.com: children: webservers: hosts: foo.example.com: bar.example.com: Hosts # ​ 配置需要管理的主机host name或ip address。\nGroup # ​ 如果想管理某一批主机,可以将这多个host配置到统一个group下。方便对这组主机的批量管理。\n[webserver] webserver01.com webserver02.com [dbserver] dbserver01.com dbserver02.com Group变量 # 作用在group上的变量。\n[webserver] webserver01.com [webserver:vars] ansible_ssh_user=ubuntu Nested group # ​ 一个组包含其他组。\n[allserver:children] webserver dbserver Inventory内置参数 # 参数名 参数说明 ansible_ssh_host 定义主机的ssh地址 ansible_ssh_port 定义主机的ssh端口 ansible_ssh_user 定义主机的ssh认证用户 ansible_ssh_pass 定义主机的ssh认证密码 ansible_sudo 定义主机的sudo用户 ansible_sudo_pass 定义主机的sudo密码 ansible_sudo_exe 定义主机的sudo路径 ansible_connection 定义主机连接方式;与主机的连接类型.比如:local,ssh或者paramiko;Ansible 1.2以前默认使用paramiko。1.2以后的版本默认使用‘smart’,‘smart’方式会根据是否支持ControlPersist,来判断ssh方式是否可行 ansible_ssh_private_key_file 定义主机私钥文件 ansible_shell_type 定义主机shell类型 ansible_python_interpreter 定义主机python解释器路径 [webserver] webserver01.com webserver02.com [webserver:vars] ansible_ssh_private_key_file=~/singapore.pem 模块(Modules) # ​ Ansible功能的单元,一个Ansible功能对应有一个Module。\n常用模块 # file # 远程服务器上的文件进行操作。创建删除文件,修改文件权限等。\npath:执行文件、目录的路径 recurse:递归设置文件属性,只对目录有效 group:定义文件、目录的属组 mode:定义文件、目录的权限 owner:定义文件、目录的所有者 src:要被链接的源文件路径,只应用与state为link的情况 dest:被链接到的路径,只应用于state为link的情况 force:在两种情况下会强制创建软链接,一种是源文件不存在但之后会建立的情况;一种是目标软链接已经存在,需要先取消之前的软链接,然后创建新的软链接,默认值 no。 ansible servers -m file -a \u0026#39;path=~/temp state=directory\u0026#39; # 创建目录 ansible servers -m file -a \u0026#39;path=~/temp/test1.txt state=touch\u0026#39; # 创建文件 copy # 将Control Node上的目录或文件拷贝至Managed Nodes。\nsrc:指定要复制到远程主机的文件或目录。如果路径以 / 结尾,则只复制目录里的内容,如果没有以 / 结尾,则复制包含目录在内的整个内容 dest:文件复制的目的地,必须是一个绝对路径,如果源文件是一个目录,那么dest指向的也必须是一个目录 force:默认取值为yes,表示目标主机包含此文件,但内容不同时,覆盖。 backup:默认取值为no,如果为yes,在覆盖前将原文件进行备份 directory_mode:递归设定目录权限,默认为系统默认权限 others:所有file模块里的选项都可以在这里使用 touch test2.txt ansible servers -m copy -a \u0026#39;src=test2.txt dest=~/temp/test2.txt\u0026#39; # 拷贝文件到服务器 command # ​ 直接要求主机清单执行命令。\nansible servers -m command -a \u0026#34;touch ~/temp/test3.txt\u0026#34; ansible servers -m command -a \u0026#34;sudo apt update\u0026#34; ansible servers -m command -a \u0026#34;sudo apt install nginx -y\u0026#34; ansible servers -m command -a \u0026#34;sudo apt autoremove nginx -y\u0026#34; 不支持shell变量,如$NAME, \u0026lt;,\u0026gt;,\u0026amp;,| 等\nshell # ​ 可以先编辑shell脚本,然后要求主机清单执行shell脚本里的命令。\n- name: Execute the command in remote shell; stdout goes to the specified file on the remote. shell: somescript.sh \u0026gt;\u0026gt; somelog.txt - name: Change the working directory to somedir/ before executing the command. shell: somescript.sh \u0026gt;\u0026gt; somelog.txt args: chdir: somedir/ 注意:shell脚本必须有可执行的权限\n- name: \u0026#39;chmod shell\u0026#39; command: chmod 777 somedir/somescript.sh apt # ​ ubuntu/debian 包、应用管理。\nupdate_cache : yes/no,默认no,操作之前是否 apt-get update\nstate :\nabsent : 移除 latest :最新 present: 目前稳定的,默认 autoremove :yes/no,默认no,移除依赖\nautoclean :yes/no,默认no,移除缓存\nansible servers -m apt -a \u0026#34;name=nginx update_cache=yes\u0026#34; -b # append to playbook - name: Install nginx apt: name: nginx state: latest update_cache: yes ansible servers -m apt -a \u0026#34;name=nginx state=absent autoremove=yes autoclean=yes\u0026#34; -b yum # ​ CentOs管理程序包。\nservice # ​ 管理服务的模块,用来启动、停止、重启服务。\nenabled :yes/no,默认no.是否开机启动。\nname :服务名\nstate :\nreloaded restarted started stopped sleep :如果是restarted,需要等待多长秒后再重启 如:10,等待10秒后重启\nansible servers -m service -a \u0026#34;name=nginx state=started\u0026#34; # playbook - name: Start service nginx, if not started service: name: nginx state: started ansible servers -m service -a \u0026#34;name=nginx state=stoped\u0026#34; unarchive # ​ 解压缩。\ngit # Managed需要已经安装git。\n执行git相关操作。\n任务(Tasks) # ​ Ansible执行的单元,一般放在Playbook中,多个Task一起执行,使用方便,可复用。\n​ 定义方式:\n-name: names module: action --- - name: ansible-playbook demo hosts: servers tasks: - name: create temp dir file: path: ~/temp state: directory - name: create test1.txt file: path: ~/temp/test1.txt state: touch - name: copy test2.txt copy: src: test2.txt dest: ~/temp/test2.txt - name: copy test.sh copy: src: test.sh dest: ~/temp/test.sh - name: \u0026#39;chmod shell\u0026#39; command: chmod 777 ~/temp/test.sh - name: exec shell shell: ~/temp/test.sh args: executable: /bin/bash Ansible用法 # ansible命令 # ansible \u0026lt;hosts\u0026gt; -m \u0026lt;module_name\u0026gt; -a \u0026lt;arguments\u0026gt; -i \u0026lt;hosts_path\u0026gt; hosts:\nall 或 *,所有hosts文件中配置的主机 ip address host name group name 多个host或group可用:分隔 支持正则匹配:如配置了一个webserver的主机,可用ansible ~web* -m ping匹配到 module_name:例如ping,command,apt的任务模块\narguments: 执行module的参数,例如 ansible -m command -a \u0026quot;touch hello.txt\u0026quot;\nhosts_path:主机清单文件位置,默认/etc/ansible/etc,也可以自己指定\nAnsible命令参数 # [-h] # 帮助 [--version] # Ansible版本 [-v] # verbost [-b] # sudo运行,可以以sudo权限运行 [--become-method BECOME_METHOD] [--become-user BECOME_USER] [-K] # 提示输入sudo密码,当不是NOPASSWD模式时使用 [-i INVENTORY] # 指定hosts文件路径,默认default=/etc/ansible/hosts [--list-hosts] # 打印有主机列表 [-o] # 压缩输出,摘要输出 [-t TREE] [-k] # 提示输入ssh登录密码。当使用密码验证的时候用 [--private-key PRIVATE_KEY_FILE] # ssh连接的私钥文件位置 [-u REMOTE_USER] # ssh连接的用户名,默认用root,ansible.cfg中可以配置 [-c CONNECTION] # 连接类型(default=smart),例如还有local [-T TIMEOUT] # 超时限制 [--ask-vault-pass | --vault-password-file VAULT_PASSWORD_FILES] [-f FORKS] # fork多少个进程并发处理,默认为5个 [-M MODULE_PATH] # 模块的执行文件位置,一般用于扩展模块 [--playbook-dir BASEDIR] [-a MODULE_ARGS] # 模块的参数 [-m MODULE_NAME] # 要执行的模块,默认为command Anisble playbook # ​ 预先定义好所有需要执行的操作,形成一个脚本文件,执行该脚本文件即可,方便管理和复用。\nansible-playbook -i \u0026lt;hosts_path\u0026gt; \u0026lt;playbook_path\u0026gt; hosts_path:主机清单文件位置\nplaybook_path: playbook yaml文件位置\n-C:加在ansible-playbook命令后,用于验证palybook文件是否有误\n--- - name: Install prometheus on ubuntu hosts: prometheus become: yes tasks: - name: \u0026#34;Step 1: Import prometheus public GPG Key\u0026#34; apt_key: url: https://s3-eu-west-1.amazonaws.com/deb.robustperception.io/41EFC99D.gpg state: present - name: \u0026#34;Step 2: Reload local package database\u0026#34; apt: update_cache: yes - name: \u0026#34;Step 3: Install prometheus\u0026#34; apt: name: prometheus update_cache: yes - name: \u0026#34;Step 4: Install prometheus-node-exporter\u0026#34; apt: name: prometheus-node-exporter update_cache: yes - name: \u0026#34;Step 5: Install prometheus-pushgateway\u0026#34; apt: name: prometheus-pushgateway update_cache: yes - name: \u0026#34;Step 6: Install prometheus-alertmanager\u0026#34; apt: name: prometheus-alertmanager update_cache: yes - name: \u0026#34;Step 7: Start prometheus service\u0026#34; service: name: prometheus enabled: yes state: started playbook 参数 # name :标识plaoybook文件执行内容\nhosts: inventory\nbecome: yes/no,默认no,是否使用sudo执行\ntasks : 预先定义好的ansible命令,managed node将从上到下执行\nhandles: 使用notify触发\n# 修改nginx配置后,设置重启 - name: write the nginx config file template: src=/somepath/nginx.j2 dest=/data/nginx/conf/nginx.conf notify: - restart nginx handlers: - name: enable nginx service: name=nginx state=restarted when: 当满足条件时才执行,多条件可以使用and、or\ntasks: - name: \u0026#34;shut down Debian flavored systems\u0026#34; command: /sbin/shutdown -t now when: ansible_os_family == \u0026#34;Debian\u0026#34; 常用用法**:\n# --list-hosts(简写为--list) ansible webserver --list-hosts Ansible原理 # ansible命令\nansible命令所使用的模块,参数\n演示 # ansible.cfg host_key_checking = False remote_tmp = /tmp/.ansible-${USER}/tmp hosts主机清单 [ubuntu:vars] ansible_ssh_private_key_file=~/singapore.pem [ubuntu] 172.30.2.252 ansible_ssh_user=ubuntu [mysql:vars] ansible_private_key_file=~/dev-mysql.pem [mysql] 172.31.22.159 ansible_ssh_user=ubuntu [servers:children] ubuntu mysql ansible操作 文件操作\n# 创建目录 ansible servers -m file -a \u0026#39;path=~/temp state=directory mode=0755\u0026#39; # 创建文件 ansible servers -m file -a \u0026#39;path=~/temp/test1.txt state=touch\u0026#39; # Control Node分发文件至Managed Nodes ansible servers -m copy -a \u0026#39;src=index.html dest=~/temp/test2.txt\u0026#39; 踩坑记录 # 问题1\n连接aws的linux实例时,需要在/etc/ansible/hosts定义的参数\n[ubuntu:vars] ansible_private_key_file=/home/ubuntu/singapore.pem [ubuntu] 192.168.0.1 ansible_ssh_user=ubuntu 问题2\n使用docker创建可以ssh连接的ubuntu容器,无法使用root连接,但是使用其他user时,在ansible local -m ping时也会出现以下问题。\nAuthentication or permission failure. In some cases, you may have been able to authenticate and did not have permissions on the remote directory. Consider changing the remote temp path in ansible.cfg to a path rooted in \u0026#34;/tmp\u0026#34;.... 其实上面的信息已经有了一些提示,修改/etc/ansible/ansible.cfg的remote_tmp配置\nremote_tmp = /tmp/.ansible-${USER}/tmp 问题3\n如果在使用pem文件连接服务器时遇到Permissions 0664 for '/home/ubuntu/dev-mysql.pem' are too open.\\r\\nIt is required that your private key files are NOT accessible by others.问题。\n修改文件权限即可\nsudo chmod 400 /path/to/file.pem 问题4\nFailed to lock apt for exclusive operation\n添加\nbecome: yes 其他 # 预编译 # python的版本兼容问题 # ansible v2.7及更高版本默认优先支持python3,如果对特定的host支持单独的python版本,可以通过设置ansible_python_interpreter inventory参数设置。\n[webservers] webserver1.com ansible_python_interpreter=/usr/bin/python2.4 工具对比 # 参考 # Ansible入门\nAnsible Docs\n« Agile\n» 蓝绿部署、滚动部署和灰度部署\n"},{"id":32,"href":"/devops/bule-green-rollback-gray/","title":"Bule Green Rollback Gray","section":"Devops","content":" 🏠 首页 / DevOps / 蓝绿部署、滚动部署和灰度部署\n蓝绿部署、滚动部署和灰度部署 # 直接举例说明:\n现环境中运行着3个V1版本的实例,计划更新到V2版本。\n蓝绿部署 # 直接使用新的服务资源部署3个V2版本实例(仍然保留3个V1版本),然后将请求流量全部转到V2版本。\n优点:\n无需中断服务;\n有回旋的余地,如果V2版本有bug的话,可以很快速的重新回到V1版本。\n缺点:\n资源占用较大,在发布过程中需要用到6个实例的服务资源。\n滚动部署 # 现停掉一个V1版本的实例,待其停止后,部署一个V2版本的实例,V2实例部署成功之后,再停掉一个V1实例,往复,直至全部替换为V2版本实例。\n优点:\n无需中断服务;\n部署新版本时无需增加服务资源,节省成本。\n缺点:\nV2版本有bug的话,不能及时回滚。\n灰度部署 # 也叫金丝雀部署,停掉一个V1版本的实例,部署一个V2版本的实例,将部分用户请求流量的转到V2版本,如果没有问题,再逐步替换V1版本。A/B测试就是一种灰度发布。\n优点:\n无需中断服务;\n同样无需增加服务器,能较为平稳的过渡到新版本,并且当有bug时也能做到快速回滚。\n« Ansible\n» 混沌工程原则 (PRINCIPLES OF CHAOS ENGINEERING)\n"},{"id":33,"href":"/devops/chaos-engineering/","title":"Chaos Engineering","section":"Devops","content":" 🏠 首页 / DevOps / 混沌工程原则 (PRINCIPLES OF CHAOS ENGINEERING)\n混沌工程原则 (PRINCIPLES OF CHAOS ENGINEERING) # http://principlesofchaos.org/ 简体中文版\n混沌工程是在分布式系统上进行实验的学科, 目的是建立对系统抵御生产环境中失控条件的能力以及信心。:\n大规模分布式软件系统的发展正在改变软件工程。作为一个行业,我们很快采用了提高开发灵活性和部署速度的实践。紧随着这些优点的一个迫切问题是:我们对投入生产的复杂系统有多少信心?\n即使分布式系统中的所有单个服务都正常运行, 这些服务之间的交互也会导致不可预知的结果。 这些不可预知的结果, 由影响生产环境的罕见且破坏性的事件复合而成,令这些分布式系统存在内在的混沌。\n我们需要在异常行为出现之前,在整个系统内找出这些弱点。这些弱点包括以下形式:\n当服务不可用时的不正确回滚设置; 不当的超时设置导致的重试风暴; 由于下游依赖的流量过载导致的服务中断; 单点故障时的级联失败等。 我们必须主动的发现这些重要的弱点,在这些弱点通过生产环境暴露给我们的用户之前。我们需要一种方法来管理这些系统固有的混沌, 通过增加的灵活性和速率以提升我们对生产环境部署的信心, 尽管系统的复杂性是由这些部署所导致的。\n我们采用基于经验和系统的方法解决了分布式系统在规模增长时引发的问题, 并以此建立对系统抵御这些事件的能力和信心。通过在受控实验中观察分布式系统的行为来了解它的特性,我们称之为混沌工程。\n混沌工程实践 # 为了具体地解决分布式系统在规模上的不确定性,可以把混沌工程看作是为了揭示系统弱点而进行的实验。这些实验遵循四个步骤:\n首先,用系统在正常行为下的一些可测量的输出来定义“稳定状态”。 其次,假设这个在控制组和实验组都会继续保持稳定状态。 然后,在实验组中引入反映真实世界事件的变量,如服务器崩溃、硬盘故障、网络连接断开等。 最后,通过控制组和实验组之间的状态差异来反驳稳定状态的假说。 破坏稳态的难度越大,我们对系统行为的信心就越强。如果发现了一个弱点,那么我们就有了一个改进目标。避免在系统规模化之后被放大。\n高级原则 # 以下原则描述了应用混沌工程的理想方式,这些原则基于上述实验过程。对这些原则的匹配程度能够增强我们在大规模分布式系统的信心。\n建立一个围绕稳定状态行为的假说 # 要关注系统的可测量输出, 而不是系统的属性。对这些输出在短时间内的度量构成了系统稳定状态的一个代理。 整个系统的吞吐量、错误率、延迟百分点等都可能是表示稳态行为的指标。 通过在实验中的系统性行为模式上的关注, 混沌工程验证了系统是否正常工作, 而不是试图验证它是如何工作的。\n多样化真实世界的事件 # 混沌变量反映了现实世界中的事件。 我们可以通过潜在影响或估计频率排定这些事件的优先级。考虑与硬件故障类似的事件, 如服务器宕机、软件故障 (如错误响应) 和非故障事件 (如流量激增或伸缩事件)。 任何能够破坏稳态的事件都是混沌实验中的一个潜在变量。\n在生产环境中运行实验 # 系统的行为会依据环境和流量模式都会有所不同。 由于资源使用率变化的随时可能发生, 因此通过采集实际流量是捕获请求路径的唯一可靠方法。 为了保证系统执行方式的真实性与当前部署系统的相关性, 混沌工程强烈推荐直接采用生产环境流量进行实验。\n持续自动化运行实验 # 手动运行实验是劳动密集型的, 最终是不可持续的。所以我们要把实验自动化并持续运行,混沌工程要在系统中构建自动化的编排和分析。\n最小化爆炸半径 # 在生产中进行试验可能会造成不必要的客户投诉。虽然对一些短期负面影响必须有一个补偿, 但混沌工程师的责任和义务是确保这些后续影响最小化且被考虑到。\n混沌工程是一个强大的实践, 它已经在世界上一些规模最大的业务系统上改变了软件是如何设计和工程化的。 相较于其他方法解决了速度和灵活性, 混沌工程专门处理这些分布式系统中的系统不确定性。 混沌工程的原则为我们大规模的创新和给予客户他们应得的高质量的体验提供了信心。\n欢迎加入 混沌工程社区的 Google 讨论组和我们一起讨论这些原则的应用。\n« 蓝绿部署、滚动部署和灰度部署\n» 商业画布\n"},{"id":34,"href":"/devops/commercial-canvas/","title":"Commercial Canvas","section":"Devops","content":" 🏠 首页 / DevOps / 商业画布\n商业画布 # 用来描述商业模式、可视化商业模式、评估商业模式以及改变商业模式的通用语言。\n商业模式:通过商业产品创造价值,传递价值,获取价值的一种原理。\n九个模块 # CS客户细分(Customer Segments):企业或机构所服务的一个或多个客户分类群体。\nVP价值主张(Value Propositions):通过价值主张来解决客户难题和满足客户需求。\nCH渠道通路(Channels):通过沟通、分销和销售渠道向客户传递价值主张。\nCR客户关系(Customer Relationships):在每一个客户细分市场建立和维护客户关系。\nR$收入来源(Revenue Streams):收入来源产生于成功提供给客户的价值主张。\nKR核心资源(Key Resoures):核心资源是提供和交付先前描述要素所必备的重要资产。\nKA关键业务(Key Activities):通过执行一些关键业务活动,运转商业模式。\nKP重要合作(Key Partnership):有些业务要外包,而另外一些资源需要从企业外部获得。\nC$成本结构(Cost Structure):商业模式上述要素所引发的成本构成。\n基本认知 # 客户细分 # 客户是商业模式的核心。哪些是重要客户\n« 混沌工程原则 (PRINCIPLES OF CHAOS ENGINEERING)\n» 使用grafana监控5xx服务\n"},{"id":35,"href":"/devops/grafana-monite-service-with-5xx/","title":"Grafana Monite Service With 5xx","section":"Devops","content":" 🏠 首页 / DevOps / 使用grafana监控5xx服务\n使用grafana监控5xx服务 # 1. Grafana信息 # grafana服务: https://devops.example.com/grafana\n如果要注册账号请联系devops组。\n2. Grafana监控预览 # grafana已经配置了对service.hompartners.com域名下的service访问状态返回5xx的监控,可以查看对应的grafana面板 https://devops.example.com/grafana/d/Q_zv-HrWz/cst-service-status?orgId=1\n该监控面板中可以查看如userapi、emailapi等服务是否正常,当面板的网格视图中出现红点,说明访问对应的服务返回了5xx状态,即服务端异常。开发人员等可以根据该视图及时发现服务异常情况。\n3. Grafana添加监控5xx服务 # 如果项继续添加Grafana面板来监控更多的服务,请参照以下教程。\nStep 1 复制模板视图:\n选中并进入xxx service http_status_5xx template面板,按操作如下复制xxx.xxx.com http_status_5xx视图\n(可通过该链接访问: https://devops.example.com/grafana/d/XNnusprWz/xxx-service-http_status_5xx-template?orgId=1)\nStep 2 创建新面板:\n按如下操作创建新面板并粘贴视图。\n随后会在页面呈现一个视图,这时可以先编辑面板信息,并新命名,选择面板分类,并保存面板信息。\nStep 3 定制xxx.xxx.com http_status_5xx视图:\n保存完成之后,点击左上角的回退箭头图标:\u0026lt;\u0026ndash;,回到视图页面,按如下操作编辑视图。\n修改查询sql语句,域名修改为要监控的域名或服务名,比如你想监控www.example.com域名下所有服务,那么你可以定制sql如下:\nSELECT \u0026#34;service_code\u0026#34; FROM \u0026#34;service_status\u0026#34; WHERE (\u0026#34;health_code\u0026#34; = 500 AND \u0026#34;domain_name\u0026#34; = \u0026#39;www.example.com\u0026#39;) AND $timeFilter GROUP BY \u0026#34;service_name\u0026#34; ,当然你可能只想监控某个域名下的其中一个服务,如你想监控www.example.com域名下operationplatgform服务,那么你可以定制sql如下:\nSELECT \u0026#34;service_code\u0026#34; FROM \u0026#34;service_status\u0026#34; WHERE (\u0026#34;health_code\u0026#34; = 500 AND \u0026#34;domain_name\u0026#34; = \u0026#39;www.example.com\u0026#39; AND \u0026#34;service_name\u0026#34; = \u0026#39;operationplatgform\u0026#39;) 另外这里的health_code做了格式化,分为200和500,我们默认监控对我们有用的500状态\n修改视图名称,如果有CloudWatch日志连接的需要,可以定制Panel links。\n视图修改完成后,右上角保存面板。\nStep 4 定制xxx service info视图:\n按照上述操作,将xxx.service.info视图复制,然后粘贴在新的面板中。\n并且编辑粘贴的视图,修改query。\n4. Grafana添加服务信息 # Step 1 复制模板视图:\n选中并进入xxx service http_status_5xx template面板,按操作如下复制xxx service ingo视图\n(可通过该链接访问: https://devops.example.com/grafana/d/XNnusprWz/xxx-service-http_status_5xx-template?orgId=1)\n之后在视图页面,可以通过伸缩视图页面,使展示更合理;通过调整时间段查看想要观察的时间段内的数据。\n如果在定制过程中存在问题,也可以联系DevOps组。\n« 商业画布\n» 使用Grafana监控service\n"},{"id":36,"href":"/devops/grafana-monite-service/","title":"Grafana Monite Service","section":"Devops","content":" 🏠 首页 / DevOps / 使用Grafana监控service\n使用Grafana监控service # 监控live上的应用服务,如果服务http状态为5xx,则反应到grafana图表中,DevOps和开发人员都能及时从图表中获取信息,及时确认和排查问题.\nService Http状态数据来源 # 使用程序定时轮询获取aws elb的日志数据,将日志数据以时序形式存储在influxdb,目前数据结构如下:\ntag keys:\n# TagKeyName Remark 1 domain_name 2 service_name 默认取pathbase,如果pathbase为空,取domain field keys:\n# FieldKeyName Remark 1 domain_name 2 elb_status_code 数字类型,200;500 3 health_code 数字类型,200;500 4 request_url 请求路径 5 service_code 一个域名下的多个service,按序从1自增,作为grafana图表的y轴数据 创建Dashboard # =\u0026gt; Add Query\n目前数据源已经配置完成,选择Influxdb_Elb_Logs作为QUuery DataSource,并且开始配置query\n查询语句可以参考:\nSELECT mean(\u0026#34;service_code\u0026#34;) FROM \u0026#34;service_status\u0026#34; WHERE (\u0026#34;domain_name\u0026#34; = \u0026#39;service.example.com\u0026#39; AND \u0026#34;health_code\u0026#34; = 500) AND $timeFilter GROUP BY time($__interval), \u0026#34;service_name\u0026#34; 根据自身需求修改query即可。\n« 使用grafana监控5xx服务\n» Grafana\n"},{"id":37,"href":"/devops/grafana/","title":"Grafana","section":"Devops","content":" 🏠 首页 / DevOps / Grafana\nGrafana # 官方文档: https://grafana.com/docs/grafana/latest/\n简介 # 特性 # 可视化:通过图表展示指标信息,直观,便于分析\n报警:指标数据超出阈值\n统一:多种数据源可以应用到同一个Dashboard中\n多平台支持:windows、linux、docker、mac\n丰富的插件扩展\n丰富的模板支持\n使用Grafana监控Jenkins # 监控指标包括:jenkins发布状态,jenkins的发布时长等.\n前提条件 # 已安装jenkins\n已安装prometheus\n已安装grafana\nJenkins安装插件 # 登入Jenkins =\u0026gt; Manage Jenkins =\u0026gt; Manage Plugins =\u0026gt; Available页签 搜索Prometheus插件,安装即可.\n此节可以参考: https://medium.com/@eng.mohamed.m.saeed/monitoring-jenkins-with-grafana-and-prometheus-a7e037cbb376\n« 使用Grafana监控service\n» Jaeger\n"},{"id":38,"href":"/devops/jeager/","title":"Jeager","section":"Devops","content":" 🏠 首页 / DevOps / Jaeger\nJaeger # 前言 # 微服务之间的调用关系错综复杂,当你在京东下单时,应用背后的服务调用链可能超你想象。调用链的追踪是微服务绕不过去的技术栈,\n简介 # 关于 # Jaeger,受Dapper和OpenZipkin启发,由Uber开源的一个分布式跟踪系统,用于基于微服务分布式系统的监控和排错,包括:\n分布式上下文传递 分布式事务监控 问题根由分析 服务依赖分析 性能、延迟优化 功能 # 兼容OpenTracing数据模型和工具库 对每个服务、端点使用一致的抽样概率 支持多样的后端数据库:Cassandra,Elasticsearch,Memory 追踪数据拓扑图形展示 基础概念 # Span:\n跨度,是跨服务的一次调用。包含名称,开始时间和截止时间,Span之间可以并列,也可以嵌套。\nTrace:\n是一次完成的分布式调用链,包含多个Span\n技术规格 # 后端Go语言实现 前端React/Javascript 支持的数据库:Cassandra3.4+,Elasticsearch5.x+,Kafka\u0026hellip; 组件介绍 # jaeger-client:\njaeger客户端,可以使用多种主流语言实现OpenTracing协议,将调用链数据收集到agent。\njaeger-agent:\njaeger的代理程序,将收集到的client调用链数据上报到collector。\njaeger-collector:\njaeger调用链数据收集器,对收集到的调用链数据进行校验,处理,存储到后端数据库。\njaeger-query:\njaeger调用链数据查询服务,有独立UI。\nOpenTracing # 分布式的追踪系统其实不止Jaeger一种,但是它们的核心原理都大相径庭,都是从入侵到代码中埋点,然后像追踪系统上报数据信息,最终我们在追踪系统得到数据,从而实现追踪分析。\n为了兼容统一各追踪系统API,OpenTracing规范诞生了,它与平台无关,与厂商无关。有了它的存在,你可以方便的切换你想使用的追踪系统。\n安装 # Docker # docker run -d --name jaeger \\ -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \\ -p 5775:5775/udp \\ -p 6831:6831/udp \\ -p 6832:6832/udp \\ -p 5778:5778 \\ -p 16686:16686 \\ -p 14268:14268 \\ -p 14250:14250 \\ -p 9411:9411 \\ jaegertracing/all-in-one:1.20 Kubernetes # 开发环境:\nkubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-kubernetes/master/all-in-one/jaeger-all-in-one-template.yml 可能需要将文件下载下来,修改Deployment版本。\n生产环境:\n├── install-jaeger.sh ├── jaeger-agent.yaml ├── jaeger-cassandra.yaml ├── jaeger-collector.yaml ├── jaeger-configmap.yaml ├── jaeger-persistent.yaml ├── jaeger-query.yaml └── uninstall-jaeger.sh\n示例 # DotNet Demo:\ndotnet add package Jaeger --version 0.4.2 dotnet add package OpenTracing.Contrib.NetCore --version 0.6.2 public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddOpenTracing(); var httpOption = new HttpHandlerDiagnosticOptions(); httpOption.IgnorePatterns.Add(req =\u0026gt; req.RequestUri.AbsolutePath.Contains(\u0026#34;/api/traces\u0026#34;)); services.AddSingleton(Options.Create(httpOption)); services.AddSingleton\u0026lt;ITracer\u0026gt;(serviceProvider =\u0026gt; { string serviceName = serviceProvider.GetRequiredService\u0026lt;IWebHostEnvironment\u0026gt;().ApplicationName; ILoggerFactory loggerFactory = serviceProvider.GetRequiredService\u0026lt;ILoggerFactory\u0026gt;(); Configuration.SenderConfiguration senderConfiguration = new Configuration.SenderConfiguration(loggerFactory) .WithSender(new UdpSender(\u0026#34;jaeger-agent\u0026#34;, 6831, 0)); var tracer = new Tracer.Builder(serviceName) .WithSampler(new ConstSampler(true)) .WithReporter(new RemoteReporter.Builder().WithSender(senderConfiguration.GetSender()).Build()) .Build(); GlobalTracer.Register(tracer); return tracer; }); } Golang Demo:\ngo get github.com/uber/jaeger-client-go go get github.com/opentracing/opentracing-go func main() { tracer, closer := initJaegerTracer(\u0026#34;jaeger-api-go\u0026#34;, \u0026#34;jaeger-agent.example.dev:6831\u0026#34;) defer closer.Close() opentracing.InitGlobalTracer(tracer) http.HandleFunc(\u0026#34;/api/values\u0026#34;, TraceHandler(valuesHandler)) http.ListenAndServe(\u0026#34;:8082\u0026#34;, nil) } func initJaegerTracer(serviceName, jaegerAgentAddr string) (opentracing.Tracer, io.Closer) { cfg := config.Configuration{ Sampler: \u0026amp;config.SamplerConfig{ Type: jaeger.SamplerTypeConst, Param: 1, }, Reporter: \u0026amp;config.ReporterConfig{ LogSpans: true, LocalAgentHostPort: jaegerAgentAddr, }, } tracer, closer, err := cfg.New(serviceName, config.Logger(jaeger.StdLogger)) if err != nil { log.Printf(\u0026#34;ERROR: Could not initialize jaeger tracer: %s\u0026#34;, err.Error()) } return tracer, closer } func valuesHandler(w http.ResponseWriter, r *http.Request) { time.Sleep(2 * time.Second) fmt.Fprintf(w, \u0026#34;hello from jaeger-go.\u0026#34;) } func TraceHandler(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { spanName := r.URL.Path spanCtx, _ := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header)) span := opentracing.GlobalTracer().StartSpan(spanName, opentracing.ChildOf(spanCtx)) span.SetTag(string(ext.Component), spanName) defer span.Finish() handler(w, r) } } package jaeger_tracer import ( \u0026#34;github.com/opentracing/opentracing-go/ext\u0026#34; \u0026#34;io\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;github.com/opentracing/opentracing-go\u0026#34; \u0026#34;github.com/uber/jaeger-client-go\u0026#34; \u0026#34;github.com/uber/jaeger-client-go/config\u0026#34; ) type TraceHandler struct { OriginalHandler http.Handler } func (handler TraceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { //spanName := runtime.FuncForPC(reflect.ValueOf(handler.OriginalHandler).Pointer()).Name() spanName := r.URL.Path spanCtx, _ := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header)) span := opentracing.GlobalTracer().StartSpan(spanName,opentracing.ChildOf(spanCtx)) span.SetTag(string(ext.Component), spanName) defer span.Finish() handler.OriginalHandler.ServeHTTP(w, r) } func InitJaegerTracer(serviceName, jaegerAgentAddr string) (opentracing.Tracer, io.Closer) { cfg := config.Configuration{ ServiceName: serviceName, Sampler: \u0026amp;config.SamplerConfig{ Type: jaeger.SamplerTypeConst, Param: 1, }, Reporter: \u0026amp;config.ReporterConfig{ LogSpans: true, LocalAgentHostPort: jaegerAgentAddr, }, } tracer, closer, err := cfg.NewTracer(config.Logger(jaeger.StdLogger)) if err != nil { log.Printf(\u0026#34;ERROR: Could not initialize jaeger tracer: %s\u0026#34;, err.Error()) } return tracer, closer } func TracerHandler(handler http.Handler) http.Handler { return jaeger_tracer.TraceHandler{ OriginalHandler: handler, } } 使用 # « Grafana\n» nginx\n"},{"id":39,"href":"/devops/nginx/","title":"Nginx","section":"Devops","content":" 🏠 首页 / DevOps / nginx\nnginx # nginx简介 # 高性能的反向代理工具,负载均衡器;\nnginx配置 # 全局配置 # event配置 # http配置 # 配置反向代理 # 正向代理:\n在国内是\n配置location:\nlocaltion [ = | ~ | ~* | ^~] uri { } =:用于不包含正则表达式的url前,要求请求字符串与uri严格匹配; ~:用于表示uri包含正则表达式,并且区分大小写; ~*:用于表示uri包含正则表达式,并且不区分大小写; ^~:用于不包含正则表达式的uri前,要求nginx服务器找到表示ui和请求字符串匹配度最高的location后,立即使用此location处理请求 配置负载均衡 # 将负载分摊到不同的服务单元,保证服务的快速响应,高可用。\nupstream myserver { server 192.168.0.1:8081; server 192.168.0.2:8082; } server { listen 80; server_name 192.168.0.1; location / { proxy_pass http://myserver; root html; index index.html index.htm; } } 均衡策略:\n轮询策略:\nnginx默认使用的均衡策略,按请求时间先后顺序逐一分配到后端服务器列表,如果某后端服务器down掉了,则自动剔除服务器列表。\n权重策略:\n后端服务器配置weight值,默认值为1,该值配置越大,则均衡到该服务器上的请求越多。\nupstream myserver { server 192.168.0.1:8081 weight=1; server 192.168.0.2:8082 weight=2; } IP-Hash策略:\n根据请求客户端的IP 的hash值给其分配固定的某个服务器,可以解决session问题。\nupstream myserver { ip_hash; server 192.168.0.1:8081; server 192.168.0.2:8082; } Fair策略:\n根据后端服务器的响应时间分配,响应时间短的优先分配。\nupstream myserver { server 192.168.0.1:8081; server 192.168.0.2:8082; fair; } 配置动静分离 # 静态服务器用于响应html、css、js、图片等静态资源的访问,动态服务器用于响应业务处理等。\nlocation /www/ { root /data/; index index.html index.htm; } location /image/ { root /data/; autoindex on; # 列出image目录下的静态文件 } Nginx常用命令 # 重启 # 适用于修改了配置文件之后,重新加载\nnginx -s reload 检查配置文件格式是否正确 # # 检查默认配置文件 /etc/nginx/nginx.conf nginx -t # 检查指定配置文件 nginx -t -c /etc/nginx/conf.d/default.conf « Jaeger\n"},{"id":40,"href":"/docker/","title":"Docker","section":"","content":" 🏠 首页 / Docker\nDocker # container-diff 工具的使用\nDocker in Docker\ndocker buildx\nDocker 常用命令\nDocker Compose 实践\nDocker 容器中安装 PFX 证书\nDocker 主机容器互拷贝文件\n使用 docker manifest 命令构建多架构镜像\n理解 docker run \u0026ndash;link\nDocker 可视化工具 Kitematic\nDockerfile\nLinux 容器\n非 root 账号获取 docker 权限\nsome-apps.md\n"},{"id":41,"href":"/docker/container-diff/","title":"Container Diff","section":"Docker","content":" 🏠 首页 / Docker / container-diff 工具的使用\ncontainer-diff 工具的使用 # 简介 # container-diff 是 google 开源的一款用于分析和比较 Docker 镜像的工具,它可以从多个维度分析一个或者比较两个容器镜像:\n镜像构建历史 镜像文件系统 镜像大小 软件包管理 项目地址: https://github.com/GoogleContainerTools/container-diff\n安装 # macOS # curl -LO https://storage.googleapis.com/container-diff/latest/container-diff-darwin-amd64 \u0026amp;\u0026amp; chmod +x container-diff-darwin-amd64 \u0026amp;\u0026amp; sudo mv container-diff-darwin-amd64 /usr/local/bin/container-diff Linux # curl -LO https://storage.googleapis.com/container-diff/latest/container-diff-linux-amd64 \u0026amp;\u0026amp; chmod +x container-diff-linux-amd64 \u0026amp;\u0026amp; sudo mv container-diff-linux-amd64 /usr/local/bin/container-diff # or curl -LO https://storage.googleapis.com/container-diff/latest/container-diff-linux-amd64 \u0026amp;\u0026amp; chmod +x container-diff-linux-amd64 \u0026amp;\u0026amp; mkdir -p $HOME/bin \u0026amp;\u0026amp; export PATH=$PATH:$HOME/bin \u0026amp;\u0026amp; mv container-diff-linux-amd64 $HOME/bin/container-diff Windows # 下载地址: https://storage.googleapis.com/container-diff/latest/container-diff-windows-amd64.exe\n下载 exe 文件重命名为 container-diff.exe,添加到系统环境变量 PATH 中。\n使用 # 分析单个 Docker 镜像\ncontainer-diff analyze \u0026lt;image-name\u0026gt; 对比两个 Docker 镜像\ncontainer-diff diff \u0026lt;image1-name\u0026gt; \u0026lt;image2-name\u0026gt; 如果不指定 type,默认分析/对比的是镜像大小,即 --type=size\n可以通过指定 type,分析/对比特定维度\ncontainer-diff analyze \u0026lt;image-name\u0026gt; --type=\u0026lt;type-name\u0026gt; container-diff diff \u0026lt;image1-name\u0026gt; \u0026lt;image2-name\u0026gt; --type=\u0026lt;type-name\u0026gt; type 类型支持如下:\nhistory:镜像构建历史 file:镜像文件 size:镜像大小 rpm:rpm 包管理器 pip:pip 包管理器 apt:apt 包管理器 node:node 包管理器 通过设置多组 type,可以一次性分析/对比多个维度,例如:\ncontainer-diff analyze nginx --type=history --type=size 通过设置 --type=file 和 --filename=/path/file,可以比较比较两个 docker 镜像中某目录或文件的区别,例如:\ncontainer-diff diff nginx:v1 nginx:v2 --type=file --filename=/etc/ 通过设置 -j,可以使用 json 格式输出结果。\n通过设置 -w \u0026lt;file-path\u0026gt;,可以将结果输入到文件。\n更多命令参数可以通过 -h 解锁。\n» Docker in Docker\n"},{"id":42,"href":"/docker/dind/","title":"Dind","section":"Docker","content":" 🏠 首页 / Docker / Docker in Docker\nDocker in Docker # Docker-in-Docker 的意思是在 Docker 容器中使用 docker,就像和在宿主机上使用 docker 一样,你可以理解为套娃。\n场景:\n如果你的 Jenkins 是使用 Docker 容器的方式运行的,如果你想使用 Jenkins 的 Docker 插件来为 Jenkins Job 提供运行容器,这时候你就需要用到 Docker-in-Docker;\n一般这个技术使用在应用的程序集成中 CI/CD。\n1. 挂载主机 /var/run/docker.sock # Docker 容器:\ndocker run -v /var/run/docker.sock:/var/run/docker.sock --name docker-in-docker -it docker 在运行起来的容器中使用docker:\n$ docker run -v /var/run/docker.sock:/var/run/docker.sock --name docker-in-docker -it docker / # docker run hello-world Hello from Docker! This message shows that your installation appears to be working correctly. ... 可以看到,在该容器中可以像在宿主机一样运行 docker 容器。\n但是,使用 docker ps -a 命令可以查看到宿主机的运行容器,这说明容器的权限是很大的,存在一定的安全隐患:\nKubernetes Pod:\n准备 docker-in-docker.yaml 文件如下:\napiVersion: v1 kind: Pod metadata: name: docker-in-docker spec: containers: - name: docker image: docker command: [\u0026#34;/bin/sh\u0026#34;, \u0026#34;-c\u0026#34;] args: - sleep 300s; volumeMounts: - name: docker-sock mountPath: /var/run/docker.sock volumes: - name: docker-sock hostPath: path: /var/run/docker.sock kubectl apply -f docker-in-docker.yaml kubectl exec -it docker-in-docker -c docker /bin/sh 同样,这种方式也能获取到宿主机的容器。\n2. 使用 Docker-Dind # Docker 容器:\ndocker run --privileged -d --name docker-in-docker docker:dind docker exec -it docker-in-docker /bin/sh 与上面方式不同的是,这种方式运行起来的 docker-in-docker 无法看到宿主机上的容器。\nKubernetes Pod:\n准备 docker-in-docker.yaml 文件如下:\napiVersion: v1 kind: Pod metadata: name: docker-in-docker spec: containers: - name: docker image: docker:18 imagePullPolicy: Always env: - name: DOCKER_HOST value: tcp://localhost:2375 command: [\u0026#34;/bin/sh\u0026#34;, \u0026#34;-c\u0026#34;] args: - sleep 300s; - name: docker-dind image: docker:18-dind securityContext: privileged: true restartPolicy: OnFailure 注意:\n需要两个容器,第一容器由 docker 镜像运行,第二容器由 docker:dind 镜像运行;\n第一容器需要设置环境变量:DOCKER_HOST=tcp://localhost:2375;\n第二容器使用特权模式运行。\nkubectl apply -f docker-in-docker.yaml kubectl exec -it docker-in-docker -c docker /bin/sh 3. 结束语 # 前面提到了,由于第一种方式挂载主机 /var/run/docker.sock,在容器视角依然能获取到宿主机的容器,也能运行或删除容器,存在一定的安全隐患,更推荐使用第二种方式,Over!\n« container-diff 工具的使用\n» docker buildx\n"},{"id":43,"href":"/docker/docker-buildx/","title":"Docker Buildx","section":"Docker","content":" 🏠 首页 / Docker / docker buildx\ndocker buildx # $ docker buildx Usage: docker buildx [OPTIONS] COMMAND Extended build capabilities with BuildKit Options: --builder string Override the configured builder instance Management Commands: imagetools Commands to work on images in registry Commands: bake Build from a file build Start a build create Create a new builder instance du Disk usage inspect Inspect current builder instance ls List builder instances prune Remove build cache rm Remove a builder instance stop Stop builder instance use Set the current builder instance version Show buildx version information Run \u0026#39;docker buildx COMMAND --help\u0026#39; for more information on a command. 本篇介绍如何使用 docker buildx 命令实现交叉编译不同系统架构下的 Docker 镜像。\n安装 # 如果根据 docker 官网的安装手册安装 docker,会默认安装 docker buildx。\n参照文档: Docker Buildx | Docker Documentation\n手动安装:\n下载地址: https://github.com/docker/buildx/releases/latest\n手动下载对应版本的二进制文件,重命名并拷贝到目标目录,例如:\nwget https://github.com/docker/buildx/releases/download/v0.8.1/buildx-v0.8.1.linux-amd64 mv buildx-v0.8.1.linux-amd64 $HOME/.docker/cli-plugins/docker-buildx QEMU:\ndocker run --privileged --rm tonistiigi/binfmt --install all 将buildx设置成默认的镜像编译器 # 因为 docker buildx build 命令后续的大部分参数与 docker build 完全一致,所以可以通过设置命令别名的方式,将 buildx 作为默认的镜像编译器。\n你只需要执行以下命令即可:\ndocker buildx install 如果需要取消这个命令别名,执行以下命令:\ndocker buildx uninstall 使用 # 开始一个新的构建,执行命令 docker buildx build .。\n创建实例\ndocker buildx create --name demo-builder # 创建并使用 docker buildx create --use --name demo-builder 使用实例\ndocker buildx use demo-builder 交叉编译\ndocker buildx build . -t demo:latest 删除实例\ndocker buildx rm demo-builder « Docker in Docker\n» Docker 常用命令\n"},{"id":44,"href":"/docker/docker-commands/","title":"Docker Commands","section":"Docker","content":" 🏠 首页 / Docker / Docker 常用命令\nDocker 常用命令 # 启动容器命令 # 默认需要sudo权限执行\nsudo docker run -d -p 80:80 --name nginx nginx \u0026ndash;name:容器命名\n-d:在后台启动\n-p:\u0026lt;host端口\u0026gt;:\u0026lt;容器端口\u0026gt;\n\u0026ndash;rm:容器退出即删除\n-it:i-与容器交互,t-终端\n以root权限进入容器 # sudo docker exec -it -u root nginx bash 让容器一直睡眠 # 使用 curlimages/curl 镜像,并让其一直睡眠。\ndocker run -d --name sleep curlimages/curl sleep infinity 操作镜像命令 # 查看镜像 # sudo docker images 删除镜像 # sudo docker rmi \u0026lt;image\u0026gt; # or sudo docker image rm \u0026lt;image\u0026gt; 删除所有镜像 # sudo docker rmi $(docker images -q) 清除未使用镜像 # sudo docker image prune # or sudo docker rmi $(sudo docker images | grep \u0026#34;^\u0026lt;none\u0026gt;\u0026#34; | awk \u0026#34;{print $3}\u0026#34;) 模糊清除镜像 # docker rmi $(docker images | grep \u0026#39;query\u0026#39; | awk \u0026#39;{print $3}\u0026#39;) 操作容器命令 # 查看已经退出的容器 # sudo docker ps -a | grep Exited 清理已经退出的容器 # sudo docker rm $(sudo docker ps -qf status=exited) # or sudo docker rm `sudo docker ps -a | grep Exited | awk \u0026#39;{print $1}\u0026#39;` 清除所有容器 # 使用 -f 参数才能清除所有容器,不使用则只会清理已经退出的容器\nsudo docker rm $(sudo docker ps -a -q) -f 清除孤立容器 # sudo docker container prune 强制删除容器 # 如果某个 Pod 突然不可用,那么运行在该节点上的 Pod 可能会一直处于 Terminating 的状态,无法移除。这时候如果想强制将该 Pod 从 etcd 数据库中删除,可以使用以下命令:\nkubectl delete po \u0026lt;pod-name\u0026gt; -n \u0026lt;namespace\u0026gt; --force --grace-period=0 grace-period 表示过渡存活期,默认 30s,在删除 Pod 之前允许 Pod 慢慢终止其上的容器进程,从而优雅退出,0 表示立即终止 Pod。\n« docker buildx\n» Docker Compose 实践\n"},{"id":45,"href":"/docker/docker-compose-practice/","title":"Docker Compose Practice","section":"Docker","content":" 🏠 首页 / Docker / Docker Compose 实践\nDocker Compose 实践 # 安装 # 如果你安装了 Docker Desktop,那么它已经帮你自动安装了 Docker Compose 插件。否则,需要额外安装插件。\n使用一下命令安装或升级 Docker Compose(linux):\nUbuntu,Debian: sudo apt update sudo apt install docker-compose-plugin 基于 RPM 发行版: sudo yum update sudo yum install docker-compose-plugin 验证安装版本:\ndocker-compose version 常用命令 # 运行\ndocker-compose up 查看运行\ndocker-compose ps 停止\ndocker-compose stop 启动\u0026amp;重启\ndocker-compose start docker-compose restart 退出\ndocker-compose down 使用 docker-compose -h 查看更多命令及参数。\n实践 # 使用 Docker Compose 运行一个简单的 golang web 程序。\n程序初始化 mkdir docker-compose-go-demo cd docker-compose-go-demo go mod init docker-compose-go-demo 创建 main.go 文件,并写入程序代码 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;time\u0026#34; ) func greet(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, \u0026#34;Hello Docker Compose! %s\u0026#34;, time.Now()) } func main() { http.HandleFunc(\u0026#34;/\u0026#34;, greet) http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil) } 创建 Dockerfile 文件,并编写内容 FROM golang:alpine WORKDIR /app COPY . . EXPOSE 8080 ENTRYPOINT [ \u0026#34;go\u0026#34;,\u0026#34;run\u0026#34;,\u0026#34;main.go\u0026#34; ] 创建 docker-comppose.yml 文件,并编写内容 version: \u0026#34;3.9\u0026#34; services: web: build: . # image: docker-compose-go-demo_web:v1 # image: docker-compose-go-demo_web:v2 ports: - \u0026#34;8080:8080\u0026#34; 启动服务 docker-compose up -d 场景:\nweb 服务业务代码修改了,希望不停机更新服务: docker-compose up -d --build 包含多个服务,例如中间件,但只想重新编译其中业务服务,如 web: docker-compose up -d --no-deps --build web 如果 docker-compose.yml 直接使用的镜像,那么直接更新,再次 docker-compose up -d 即可。\n« Docker 常用命令\n» Docker 容器中安装 PFX 证书\n"},{"id":46,"href":"/docker/docker-container-install-pfx-cert/","title":"Docker Container Install Pfx Cert","section":"Docker","content":" 🏠 首页 / Docker / Docker 容器中安装 PFX 证书\nDocker 容器中安装 PFX 证书 # 如果正在开发 .NetCore 项目,并且你的项目需要使用到 PFX 证书。此时你需要将你的项目发布到 Docker 容器中,那么你就需要在你的 Docker 容器中安装 PFX 证书了。\n代码中编写 # 使用 X509Store Api 编写你的程序\nusing (var certificate = new X509Certificate2(pfxFileBytes, pfxPassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet)) using (var store = new X509Store(storeName, storeLocation, OpenFlags.ReadWrite)) { store.Add(certificate); store.Close(); } Dockerfile 中编写 # 使用 dotnet-certificate-tool 工具安装 pfx 证书。\n首先获取到 Pfx 文件的 Thumbprint,这在 dotnet-certificate-tool 命令中作为参数被使用。\n使用 Powershell Get-PfxCertificate 函数获取 Thumbprint Get-PfxCertificate -FilePath C:\\Pfx\\Hello-to-World.pfx 执行以上命令,会要求输入 pfx 证书密码,输入密码后,你应该可以得到 Thumbprint 了,输出格式大致如下:\nThumbprint Subject ---------- ------- EED5DCA70697648CDFGD7FB1EFDDD683579B436E CN=Hello-to-World, OU=IT, O=Hello 在 Dockerfile 内容大致如下\nFROM mcr.microsoft.com/dotnet/core/sdk:3.1 WORKDIR /app COPY Hello-to-World.pfx ./ ENV PATH \u0026#34;$PATH:/root/.dotnet/tools\u0026#34; RUN dotnet tool install dotnet-certificate-tool -g \u0026amp;\u0026amp; certificate-tool add -f ./Hello-to-World.pfx -p Password123 -t EED5DCA70697648CDFGD7FB1EFDDD683579B436E ENTRYPOINT [\u0026#34;dotnet\u0026#34;, \u0026#34;PfxTest.dll\u0026#34;] « Docker Compose 实践\n» Docker 主机容器互拷贝文件\n"},{"id":47,"href":"/docker/docker-copy-between-host-container/","title":"Docker Copy Between Host Container","section":"Docker","content":" 🏠 首页 / Docker / Docker 主机容器互拷贝文件\nDocker 主机容器互拷贝文件 # 命令:docker cp\n1. 将 Docker 容器内文件拷贝到 Host # 获取 docker 容器的 Container ID 或Name\nsudo docker ps 使用以下命令从容器内拷出文件\nsudo docker cp [CONTAINER ID/NAME]:[CONTAINER_PATH] [HOST_PATH] 例如我需要将容器内 /app/appsettings.json 文件拷贝到宿主机的 ~/temp/ 目录 (该目录必须存在) 下\nsudo docker cp b3e608e28f21:/app/appsettings.json ~/temp/appsettings.json # 不指定文件名亦可,默认使用原文件名 sudo docker cp b3e608e28f21:/app/appsettings.json ~/temp/ 2. 将 Host 文件拷贝至 Docker 容器 # 同样,需要先获取容器的 Container ID 或 Name;\n使用以下命令将文件拷贝至容器内\nsudo docker cp [HOST_PATH] [CONTAINER ID/NAME]:[CONTAINER_PATH] 例如我需要将宿主机的 ~/temp/hello.txt 文件拷贝至容器内 /app/ 目录 (该目录必须存在) 下\nsudo docker cp ~/temp/hello.txt b3e608e28f21:/app/hello.txt # 不指定文件名亦可,默认使用原文件名 sudo docker cp ~/temp/hello.txt b3e608e28f21:/app/ « Docker 容器中安装 PFX 证书\n» 使用 docker manifest 命令构建多架构镜像\n"},{"id":48,"href":"/docker/docker-manifest-build-cross-arch-image/","title":"Docker Manifest Build Cross Arch Image","section":"Docker","content":" 🏠 首页 / Docker / 使用 docker manifest 命令构建多架构镜像\n使用 docker manifest 命令构建多架构镜像 # # 创建 docker manifest create poneding/myimage:v1 poneding/myimage-amd64:v1 poneding/myimage-arm64:v1 # 注解 docker manifest annotate poneding/myimage:v1 poneding/myimage-amd64:v1 --arch amd64 docker manifest annotate poneding/myimage:v1 poneding/asmyimageh-arm64:v1 --arch arm64 # 检查 docker manifest inspect poneding/myimage:v1 # 推送 docker manifest push poneding/myimage:v1 在 x86 机器上构建 arm64 镜像\ndocker run --rm --privileged multiarch/qemu-user-static --reset --persistent yes « Docker 主机容器互拷贝文件\n» 理解 docker run \u0026ndash;link\n"},{"id":49,"href":"/docker/docker-run-link/","title":"Docker Run Link","section":"Docker","content":" 🏠 首页 / Docker / 理解 docker run \u0026ndash;link\n理解 docker run \u0026ndash;link # 使用方式 # # 前提已经存在一个 container2 在运行 docker run img1 --name container1 --link container2 作用 # container1 连接 container2,达到:\n与 container2 直接通信 获取 container2 的环境变量 « 使用 docker manifest 命令构建多架构镜像\n» Docker 可视化工具 Kitematic\n"},{"id":50,"href":"/docker/docker-visiable-tool-kitematic/","title":"Docker Visiable Tool Kitematic","section":"Docker","content":" 🏠 首页 / Docker / Docker 可视化工具 Kitematic\nDocker 可视化工具 Kitematic # 使用 Kitematic,以可视化的方式管理 docker 镜像,容器等。\n安装 Kitematic # 在 ubuntu(desktop)中安装 kitematic 作为示例,其他平台安装下载地址: https://github.com/docker/kitematic/releases\n# download wget https://github.com/docker/kitematic/releases/download/v0.17.11/Kitematic-0.17.11-Ubuntu.zip unzip Kitematic-0.17.11-Ubuntu.zip # install sudo dpkg -i Kitematic-0.17.11_amd64.deb 用户组管理 # ubuntu 已经安装了 docker 了,当我们安装完 Kitematic 之后,第一次打开会遇到\n将当前用户加入到 docker 组:\nsudo usermod -aG docker $USER # 重启 docker sudo systemctl restart docker sudo chmod a+rw /var/run/docker.sock 完成上面操作后,重启主机,应该就可以使用 Kitamatic 了。\n使用 Kitematic # 第一次启动 Kitematic,需要登录 docker 账号,登录完成后,界面如下。\n主界面会列出一些热门的镜像,比如 redis,jenkins。可以通过切换右上角菜单选项,查看我的 Docker 账号中的镜像列表,以及我当前主机中已经拉取的镜像列表。\n点击镜像的 Create 按钮,可以直接以默认方式启动容器,比如我选择 redis 镜像,点击 Create 按钮,之后会在我的左侧 Containers 列表中出现一个 redis 容器,这个过程中包含拉取镜像,启动容器。\n容器运行后,可以在容器界面对容器进行容器配置,例如环境变量配置、端口映射配置、卷映射配置等;\n也可以在容器界面对容器进行容器管理,例如容器停止、重启、和删除。\n这个工具功能还在不断完善,但是使用体验还算不错,推荐给大家,更多使用细节可以自己慢慢挖掘。\n« 理解 docker run \u0026ndash;link\n» Dockerfile\n"},{"id":51,"href":"/docker/dockerfile/","title":"Dockerfile","section":"Docker","content":" 🏠 首页 / Docker / Dockerfile\nDockerfile # 官方文档参考: https://docs.docker.com/engine/reference/builder/\nDockerfile Linter: https://hadolint.github.io/hadolint/\nUsage # docker build [work-dir] -t [image-tag] -f [dockerfile-path] --build-arg [arg-key]=[arg-value] 指令 # Dockerfile reference | Docker Documentation\nFROM # ARG # 由docker build命令传的参数。\nARG在multi-stage的作用范围 # 如果ARG放置在第一个FROM之前,那么作用范围是全局的;如果ARG放在FROM之后,那么只对FROM的stage作用。\nARG USERNAME FROM alpine RUN echo hello, ${USERNAME} FROM alpine RUN echo hi, ${USERNAME} CMD # CMD 指令的目的是为一个可执行容器提供初始运行命令或运行参数。\nCMD 指令有三种形式:\n可执行命令 + 命令参数列表,推荐使用 CMD [\u0026#34;executable\u0026#34;,\u0026#34;param1\u0026#34;,\u0026#34;param2\u0026#34;] 命令参数列表,作为 ENTRYPOINT 的参数 CMD [\u0026#34;param1\u0026#34;,\u0026#34;param2\u0026#34;] Shell 形式,字符串形式的命令 CMD command param1 param2 单个 build stage 只允许存在一个 CMD 指令,如果存在多个 CMD 指令,只有最后一个 CMD 指令生效。\nENTRYPOINT # ENTRYPOINT 指令用于定义容器启动时被调用的可执行程序。\nENTRYPOINT 指令有两种形式,以运行 node 程序示例:\nexec 形式,推荐使用 ENTRYPOINT [\u0026#34;node\u0026#34;,\u0026#34;app.js\u0026#34;] Shell 形式 ENTRYPOINT node app.js 这两种形式的区别在于 shell 会在容器中运行 /bin/sh -c node app.js,而 exec 是直接运行 node app.js 命令,因此采用 exec 形式是更为合适的。\nQ\u0026amp;A # 1. Dockerfile 中 ARG 无法被 CMD 使用? # 可能你需要修改你的CMD:\nFROM alpine ARG USERNAME ENV USERNAME ${USERNAME} RUN echo ${USERNAME} # CMD [\u0026#34;echo\u0026#34;,\u0026#34;${USERNAME}\u0026#34;] # 会原样输出 ${USERNAME} CMD [\u0026#34;/bin/sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;echo ${USERNAME}\u0026#34;] # 输出 dp # 或者 # CMD echo ${USERNAME} # 输出 dp docker build . -t echo-user --build-arg USERNAME=dp docker run echo-user « Docker 可视化工具 Kitematic\n» Linux 容器\n"},{"id":52,"href":"/docker/linux-container/","title":"Linux Container","section":"Docker","content":" 🏠 首页 / Docker / Linux 容器\nLinux 容器 # 容器是轻量级的虚拟化技术。\n资源隔离和限制\n容器镜像 # 联合文件系统 # 允许文件存放在不同的层级上,但是最终可以通过统一的视图查看到这些层级的所有文件。\ncgroup # namespace # mount:文件系统隔离 uts:hostname domain pid:1号进程 network user ipc:进程间通信 cgroup « Dockerfile\n» 非 root 账号获取 docker 权限\n"},{"id":53,"href":"/docker/non-root-account-get-docker-permission/","title":"Non Root Account Get Docker Permission","section":"Docker","content":" 🏠 首页 / Docker / 非 root 账号获取 docker 权限\n非 root 账号获取 docker 权限 # 默认 docker 的命令是需要 sudo 权限的,如果你觉得麻烦,想直接在当前用户下执行 docker 权限,你可以尝试使用下面这个解决方案。\n拢共分两步:\n第一步,将当前用户添加到 docker 组\nsudo usermod -aG docker $USER 第二步,授权\nsudo chmod a+rw /var/run/docker.sock 快去试试吧。\n« Linux 容器\n» some-apps.md\n"},{"id":54,"href":"/docker/some-apps/","title":"Some Apps","section":"Docker","content":" 🏠 首页 / Docker / some-apps.md\nDocker 应用\nCloudreve # 项目地址: https://github.com/cloudreve/Cloudreve\ndocker run -d --name cloudreve \\ -p 5212:5212 \\ --mount type=bind,source=/root/apps/cloudreve/conf.ini,target=/cloudreve/conf.ini \\ --mount type=bind,source=/root/apps/cloudreve/cloudreve.db,target=/cloudreve/cloudreve.db \\ -v /root/apps/cloudreve/uploads:/cloudreve/uploads \\ -v /root/apps/cloudreve/avatar:/cloudreve/avatar \\ cloudreve/cloudreve:latest Etcd # docker run -d --name etcd \\ -p 12379:2379 \\ -p 12380:2380 \\ -e ALLOW_NONE_AUTHENTICATION=yes \\ -e ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 \\ -e ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379,http://0.0.0.0:2379 \\ -v /root/apps/etcd/data:/var/run/etcd \\ quay.io/coreos/etcd:v3.5.6 Minio # docker run -d --name minio \\ -p 9000:9000 \\ -p 9001:9001 \\ -e MINIO_ROOT_USER=minio \\ -e MINIO_ROOT_PASSWORD=\u0026#39;pd1n9@1024\u0026#39; \\ -v /root/apps/minio/data:/data \\ quay.io/minio/minio:latest server /data --console-address \u0026#34;:9001\u0026#34; MySQL # docker run -d --name mysql \\ -v /root/apps/mysql/data:/var/lib/mysql \\ -e MYSQL_ROOT_PASSWORD=\u0026#39;pd1n9@1024\u0026#39; \\ -p 3306:3306 \\ mysql:8.0 Postgres # docker run -d --name postgres \\ -p 5432:5432 \\ -e POSTGRES_USER=root \\ -e POSTGRES_PASSWORD=pd1n9@1024 \\ -v /root/apps/postgres/data:/var/lib/postgresql/data \\ postgre RabbitMQ # docker run -d --name rabbitmq \\ -p 15672:15672 \\ -p 5672:5672 \\ --hostname rabbitmq \\ -e RABBITMQ_DEFAULT_USER=pding \\ -e RABBITMQ_DEFAULT_PASS=P0n3_D1n9 \\ registry.cn-hangzhou.aliyuncs.com/pding/rabbitmq:3-management Redis # docker run -d --name redis \\ -v /root/apps/redis/data:/data \\ -p 6379:6379 \\ redis:7.0 redis-server --requirepass \u0026#34;pd1n9@1024\u0026#34; Transfer # docker run -d --name transfer \\ -p 7080:8080 \\ dutchcoders/transfer.sh:latest --provider local --basedir /tmp/ « 非 root 账号获取 docker 权限\n"},{"id":55,"href":"/ebpf/","title":"Ebpf","section":"","content":" 🏠 首页 / EBPF\nEBPF # eBPF\n"},{"id":56,"href":"/ebpf/ebpf/","title":"Ebpf","section":"Ebpf","content":" 🏠 首页 / EBPF / eBPF\neBPF # 简介 # eBPF (extended Berkeley Packet Filter) 是一项革命性的技术,起源于 Linux 内核,它可以在特权上下文中(如操作系统内核)运行沙盒程序。它用于安全有效地扩展内核的功能,而无需通过更改内核源代码或加载内核模块的方式来实现。\n从历史上看,由于内核具有监督和控制整个系统的特权,操作系统一直是实现可观测性、安全性和网络功能的理想场所。同时,由于操作系统内核的核心地位和对稳定性和安全性的高要求,操作系统内核很难快速迭代发展。因此在传统意义上,与在操作系统本身之外实现的功能相比,操作系统级别的创新速度要慢一些。\neBPF 从根本上改变了这个方式。通过允许在操作系统中运行沙盒程序的方式,应用程序开发人员可以运行 eBPF 程序,以便在运行时向操作系统添加额外的功能。然后在 JIT 编译器和验证引擎的帮助下,操作系统确保它像本地编译的程序一样具备安全性和执行效率。这引发了一股基于 eBPF 的项目热潮,它们涵盖了广泛的用例,包括下一代网络实现、可观测性和安全功能等领域。\n如今,eBPF 被广泛用于驱动各种用例:在现代数据中心和云原生环境中提供高性能网络和负载均衡,以低开销提取细粒度的安全可观测性数据,帮助应用程序开发人员跟踪应用程序,为性能故障排查、预防性的安全策略执行(包括应用层和容器运行时)提供洞察,等等。可能性是无限的,eBPF 开启的创新才刚刚开始。\n钩子 # eBPF 程序是事件驱动的,当内核或应用程序通过某个钩子点时运行。预定义的钩子包括系统调用、函数入口/退出、内核跟踪点、网络事件等。\n如果预定义的钩子不能满足特定需求,则可以创建内核探针(kprobe)或用户探针(uprobe),以便在内核或用户应用程序的几乎任何位置附加 eBPF 程序。\n编写程序 # 在很多情况下,eBPF 不是直接使用,而是通过像 Cilium、 bcc 或 bpftrace 这样的项目间接使用,这些项目提供了 eBPF 之上的抽象,不需要直接编写程序,而是提供了指定基于意图的来定义实现的能力,然后用 eBPF 实现。\n如果不存在更高层次的抽象,则需要直接编写程序。Linux 内核期望 eBPF 程序以字节码的形式加载。虽然直接编写字节码当然是可能的,但更常见的开发实践是利用像 LLVM 这样的编译器套件将伪 c 代码编译成 eBPF 字节码。\n安全性 # 由于 eBPF 允许我们在内核中运行任意代码,需要有一种机制来确保它的安全运行,不会使用户的机器崩溃,也不会损害他们的数据。这个机制就是 eBPF 验证器。\n验证器对 eBPF 程序进行分析,以确保无论输入什么,它都会在一定数量的指令内安全地终止。\n云原生领域 # 屏弃 SideCar 模式,将eBPF 加载到内核,跟随事件触发。\n项目 # Cilium:基于 eBPF 的数据平面的网络,可观测性、安全性的解决方案。 Pixie:基于 eBPF 实现的 Kubernetes 可观测性解决方案。 Hubble Calico 参考 # https://ebpf.io/ "},{"id":57,"href":"/front-end/","title":"Front End","section":"","content":" 🏠 首页 / 前端技术\n前端技术 # 搭建博客站点\nPinia 入门\nVitePress\n认识Vue3\n"},{"id":58,"href":"/front-end/build-blog-site/","title":"Build Blog Site","section":"Front End","content":" 🏠 首页 / 前端技术 / 搭建博客站点\n搭建博客站点 # 1. Hugo 搭建博客 # Hugo 是一个用 Go 语言编写的静态网站生成器。Hugo 的速度非常快,因为它是一个独立的二进制文件,不需要任何运行时依赖。Hugo 的主要特点是速度快、易于安装、易于使用、易于定制。\n1.1 安装 Hugo # 参考: https://gohugo.io/installation\n1.2 创建博客 # hugo new site blog --format yaml cd blog git init 1.3 选择主题 # 使用 hugo-book 主题。\ngit submodule add https://github.com/alex-shpak/hugo-book themes/hugo-book 2. 定制 # 2.1 配置 hugo.yaml # # hugo server --minify --themesDir ../.. --baseURL=http://0.0.0.0:1313/theme/hugo-book/ baseURL: https://blog.poneding.com/ title: 秋河落叶 theme: hugo-book pluralizeListTitles: false defaultContentLanguage: cn # Book configuration disablePathToLower: true enableGitInfo: true # Needed for mermaid/katex shortcodes markup: tableOfContents: startLevel: 2 endLevel: 3 # ordered: true highlight: noClasses: false # style: monokai menu: after: - name: \u0026#34;🔗 GitHub\u0026#34; url: \u0026#34;https://github.com/poneding\u0026#34; weight: 10 params: BookTheme: \u0026#34;auto\u0026#34; BookToC: true BookFavicon: logo.png BookLogo: logo.png BookMenuBundle: /menu BookSection: \u0026#34;none\u0026#34; BookRepo: https://github.com/poneding/blog BookCommitPath: commit BookEditPath: edit/master BookDateFormat: \u0026#34;2006/01/02\u0026#34; BookSearch: true BookComments: true BookPortableLinks: true BookServiceWorker: true BookTranslatedOnly: false 要注意的几个配置点:\nparams.BookSection: 本身指定一个 content 下的文档目录,我们这里设置一个不存在的目录,是为了不在左侧菜单栏展示我们的 N 多的目录树; markup.highlight.noClasses: 本身用来确认是否不使用自定义的 CSS 样式,我们这里设置为 false,因为我们需要使用自定义的 chorma 的代码高亮样式,跟随浏览器或系统自动切换代码高亮主题; 2.2 定制左侧菜单栏 # 创建 content/menu/index.md 文件,并添加如下内容:\nmkdir -p content/menu vim content/menu/index.md 菜单配置内容如下:\n--- headless: true --- - [**🏠 首页**](/) --- - **📌 置顶** - [Golang 编程](/go) - [Kubernetes](/kubernetes) - [Rust 编程](/rust) - [Git](/git) --- - **🔗 外链** 2.2 配置 giscus 评论 # 拷贝 hugo-book 的 layouts/_default/baseof.html 文件到 layouts/_default/baseof.html,命令操作如下:\nmkdir -p layouts/_default cp themes/hugo-book/layouts/_default/baseof.html layouts/_default/baseof.html 通过配置 giscus 获取 js 脚本代码,参考: https://giscus.app\n获取到的 js 脚本代码,在 layouts/_default/baseof.html 文件找到 {{- partial \u0026quot;docs/comments\u0026quot; . -}} 所在行,在其下一行添加 js 脚本代码,最终代码如下:\n... \u0026lt;div class=\u0026#34;book-comments\u0026#34;\u0026gt; {{- partial \u0026#34;docs/comments\u0026#34; . -}} \u0026lt;!-- start giscus --\u0026gt; \u0026lt;script src=\u0026#34;https://giscus.app/client.js\u0026#34; data-repo=\u0026#34;poneding/blog\u0026#34; data-repo-id=\u0026#34;R_kgDOMITIHg\u0026#34; data-category=\u0026#34;General\u0026#34; data-category-id=\u0026#34;DIC_kwDOMITIHs4CgB4x\u0026#34; data-mapping=\u0026#34;url\u0026#34; data-strict=\u0026#34;0\u0026#34; data-reactions-enabled=\u0026#34;1\u0026#34; data-emit-metadata=\u0026#34;0\u0026#34; data-input-position=\u0026#34;top\u0026#34; data-theme=\u0026#34;preferred_color_scheme\u0026#34; data-lang=\u0026#34;zh-CN\u0026#34; data-loading=\u0026#34;lazy\u0026#34; crossorigin=\u0026#34;anonymous\u0026#34; async\u0026gt; \u0026lt;/script\u0026gt; \u0026lt;!-- end giscus --\u0026gt; \u0026lt;/div\u0026gt; ... 2.3 代码主题自动切换 # 生成代码高亮样式文件,命令操作如下:\nmkdir -p static/css # light echo \u0026#34;@media (prefers-color-scheme: light) {\u0026#34; \u0026gt; static/css/syntax.css hugo gen chromastyles --style=monokailight \u0026gt;\u0026gt; static/css/syntax.css echo \u0026#34;}\u0026#34; \u0026gt;\u0026gt; static/css/syntax.css # dark echo \u0026#34;@media (prefers-color-scheme: dark) {\u0026#34; \u0026gt;\u0026gt; static/css/syntax.css hugo gen chromastyles --style=monokai \u0026gt;\u0026gt; static/css/syntax.css echo \u0026#34;}\u0026#34; \u0026gt;\u0026gt; static/css/syntax.css 拷贝 hugo-book 的 layouts/partials/docs/html-head.html 文件到 layouts/partials/docs/html-head.html,命令操作如下:\nmkdir -p layouts/partials/docs cp themes/hugo-book/layouts/partials/docs/html-head.html layouts/partials/docs/html-head.html # 引入样式文件 echo \u0026#39;\u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;/css/syntax.css\u0026#34;\u0026gt;\u0026#39; \u0026gt;\u0026gt; layouts/partials/docs/html-head.html 2.4 Logo # 将 logo.png 图片放到 static 目录下。\n3. 部署 # 3.1 自定义域名 # 我们已经在 hugo.yaml 中配置了 baseURL: https://blog.poneding.com/,我们还要创建一个 CNAME 文件,内容为 blog.poneding.com,然后将该文件放到 static 目录下。\necho \u0026#34;blog.poneding.com\u0026#34; \u0026gt; static/CNAME 3.2 使用 GitHub Actions 自动部署 # 前提:\n在 GitHub 上创建一个新的仓库,例如:poneding/blog; 配置 GitHub 仓库的 Settings -\u0026gt; Secrets:GH_TOKEN,值为 GitHub 个人访问令牌; mkdir -p .github/workflows vim .github/workflows/deploy.yml deploy.yml 文件内容如下:\nname: Deploy on: push: branches: - master # Set a branch to deploy workflow_dispatch: schedule: # Runs everyday at 8:00 AM - cron: \u0026#34;0 0 * * *\u0026#34; pull_request: jobs: deploy: runs-on: ubuntu-22.04 concurrency: group: ${{ github.workflow }}-${{ github.ref }} steps: - uses: actions/checkout@v4 with: submodules: true # Fetch Hugo themes (true OR recursive) fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod - name: Setup Hugo uses: peaceiris/actions-hugo@v3 with: hugo-version: \u0026#39;0.127.0\u0026#39; # extended: true - name: Build run: hugo --minify - name: Deploy uses: peaceiris/actions-gh-pages@v3 if: github.ref == \u0026#39;refs/heads/master\u0026#39; with: github_token: ${{ secrets.GH_TOKEN }} publish_dir: ./public 3.3 代码提交触发部署 # git add . git commit -m \u0026#34;init repo\u0026#34; git remote add origin git@github.com:poneding/blog.git git push -u origin master 3.4 GitHub Pages 配置 # 在 GitHub 仓库的 Settings -\u0026gt; Pages 中配置 Source 为 Deploy from a branch, Branch 为 gh-pages 分支,root 为 /。\n4. SEO 配置 # 参考:\nGoogle 百度 Bing » Pinia 入门\n"},{"id":59,"href":"/front-end/pinia/","title":"Pinia","section":"Front End","content":" 🏠 首页 / 前端技术 / Pinia 入门\nPinia 入门 # 什么是pinia # Pinia 是 Vue 的专属状态管理库,可以实现跨组件或页面共享状态,是 vuex 状态管理工具的替代品,和 Vuex相比,具备以下优势\n提供更加简单的API (去掉了 mutation ) 提供符合组合式API风格的API (和 Vue3 新语法统一) 去掉了modules的概念,每一个store都是一个独立的模块 搭配 TypeScript 一起使用提供可靠的类型推断 创建空Vue项目并安装Pinia # 1. 创建空Vue项目 # npm init vue@latest 2. 安装Pinia并注册 # npm i pinia import { createPinia } from \u0026#39;pinia\u0026#39; const app = createApp(App) // 以插件的形式注册 app.use(createPinia()) app.use(router) app.mount(\u0026#39;#app\u0026#39;) 实现counter # 核心步骤:\n定义store 组件使用store 1- 定义store\nimport { defineStore } from \u0026#39;pinia\u0026#39; import { ref } from \u0026#39;vue\u0026#39; export const useCounterStore = defineStore(\u0026#39;counter\u0026#39;, ()=\u0026gt;{ // 数据 (state) const count = ref(0) // 修改数据的方法 (action) const increment = ()=\u0026gt;{ count.value++ } // 以对象形式返回 return { count, increment } }) 2- 组件使用store\n\u0026lt;script setup\u0026gt; // 1. 导入use方法 import { useCounterStore } from \u0026#39;@/stores/counter\u0026#39; // 2. 执行方法得到store store里有数据和方法 const counterStore = useCounterStore() \u0026lt;/script\u0026gt; \u0026lt;template\u0026gt; \u0026lt;button @click=\u0026#34;counterStore.increment\u0026#34;\u0026gt; {{ counterStore.count }} \u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt; 实现getters # getters直接使用计算属性即可实现\n// 数据(state) const count = ref(0) // getter (computed) const doubleCount = computed(() =\u0026gt; count.value * 2) 异步action # 思想:action函数既支持同步也支持异步,和在组件中发送网络请求写法保持一致 步骤:\nstore中定义action 组件中触发action 1- store中定义action\nconst API_URL = \u0026#39;http://geek.itheima.net/v1_0/channels\u0026#39; export const useCounterStore = defineStore(\u0026#39;counter\u0026#39;, ()=\u0026gt;{ // 数据 const list = ref([]) // 异步action const loadList = async ()=\u0026gt;{ const res = await axios.get(API_URL) list.value = res.data.data.channels } return { list, loadList } }) 2- 组件中调用action\n\u0026lt;script setup\u0026gt; import { useCounterStore } from \u0026#39;@/stores/counter\u0026#39; const counterStore = useCounterStore() // 调用异步action counterStore.loadList() \u0026lt;/script\u0026gt; \u0026lt;template\u0026gt; \u0026lt;ul\u0026gt; \u0026lt;li v-for=\u0026#34;item in counterStore.list\u0026#34; :key=\u0026#34;item.id\u0026#34;\u0026gt;{{ item.name }}\u0026lt;/li\u0026gt; \u0026lt;/ul\u0026gt; \u0026lt;/template\u0026gt; storeToRefs保持响应式解构 # 直接基于store进行解构赋值,响应式数据(state和getter)会丢失响应式特性,使用storeToRefs辅助保持响应式\n\u0026lt;script setup\u0026gt; import { storeToRefs } from \u0026#39;pinia\u0026#39; import { useCounterStore } from \u0026#39;@/stores/counter\u0026#39; const counterStore = useCounterStore() // 使用它storeToRefs包裹之后解构保持响应式 const { count } = storeToRefs(counterStore) const { increment } = counterStore \u0026lt;/script\u0026gt; \u0026lt;template\u0026gt; \u0026lt;button @click=\u0026#34;increment\u0026#34;\u0026gt; {{ count }} \u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt; « 搭建博客站点\n» VitePress\n"},{"id":60,"href":"/front-end/vitepress/","title":"Vitepress","section":"Front End","content":" 🏠 首页 / 前端技术 / VitePress\nVitePress # 搭建项目 # mkdir vitepress-demo npm add -D vitepress npx vitepress init 运行 # npm run docs:dev # 或者直接调用 VitePress npx vitepress dev docs 打包 # npm run docs:build GitHub Action # .github/workflows/deploy.yaml\n# 构建 VitePress 站点并将其部署到 GitHub Pages 的示例工作流程 # name: Deploy VitePress site to Pages on: # 在针对 `main` 分支的推送上运行。如果你 # 使用 `master` 分支作为默认分支,请将其更改为 `master` push: branches: [master] # 允许你从 Actions 选项卡手动运行此工作流程 workflow_dispatch: # 设置 GITHUB_TOKEN 的权限,以允许部署到 GitHub Pages permissions: contents: read pages: write id-token: write # 只允许同时进行一次部署,跳过正在运行和最新队列之间的运行队列 # 但是,不要取消正在进行的运行,因为我们希望允许这些生产部署完成 concurrency: group: pages cancel-in-progress: false jobs: # 构建工作 build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 # 如果未启用 lastUpdated,则不需要 # - uses: pnpm/action-setup@v3 # 如果使用 pnpm,请取消注释 # - uses: oven-sh/setup-bun@v1 # 如果使用 Bun,请取消注释 - name: Setup Node uses: actions/setup-node@v4 with: node-version: 20 cache: npm # 或 pnpm / yarn - name: Setup Pages uses: actions/configure-pages@v4 - name: Install dependencies run: npm ci # 或 pnpm install / yarn install / bun install - name: Build with VitePress run: npm run docs:build # 或 pnpm docs:build / yarn docs:build / bun run docs:build - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: path: .vitepress/dist # 部署工作 deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} needs: build runs-on: ubuntu-latest name: Deploy steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 需要该 GitHub 仓库开启 Action 并且配置 GITHUB_TOKEN。\n« Pinia 入门\n» 认识Vue3\n"},{"id":61,"href":"/front-end/vue3/","title":"Vue3","section":"Front End","content":" 🏠 首页 / 前端技术 / 认识Vue3\n认识Vue3 # 1. Vue3组合式API体验 # 通过 Counter 案例 体验Vue3新引入的组合式API\n\u0026lt;script\u0026gt; export default { data(){ return { count:0 } }, methods:{ addCount(){ this.count++ } } } \u0026lt;/script\u0026gt; \u0026lt;script setup\u0026gt; import { ref } from \u0026#39;vue\u0026#39; const count = ref(0) const addCount = ()=\u0026gt; count.value++ \u0026lt;/script\u0026gt; 特点:\n代码量变少 分散式维护变成集中式维护 2. Vue3更多的优势 # 使用create-vue搭建Vue3项目 # 1. 认识create-vue # create-vue是Vue官方新的脚手架工具,底层切换到了 vite (下一代前端工具链),为开发提供极速响应\n2. 使用create-vue创建项目 # 前置条件 - 已安装16.0或更高版本的Node.js\n执行如下命令,这一指令将会安装并执行 create-vue\nnpm init vue@latest 熟悉项目和关键文件 # 组合式API - setup选项 # 1. setup选项的写法和执行时机 # 写法\n\u0026lt;script\u0026gt; export default { setup(){ }, beforeCreate(){ } } \u0026lt;/script\u0026gt; 执行时机\n在beforeCreate钩子之前执行\n2. setup中写代码的特点 # 在setup函数中写的数据和方法需要在末尾以对象的方式return,才能给模版使用\n\u0026lt;script\u0026gt; export default { setup(){ const message = \u0026#39;this is message\u0026#39; const logMessage = ()=\u0026gt;{ console.log(message) } // 必须return才可以 return { message, logMessage } } } \u0026lt;/script\u0026gt; 3. \u0026lt;script setup\u0026gt; 语法糖 # script标签添加 setup标记,不需要再写导出语句,默认会添加导出语句\n\u0026lt;script setup\u0026gt; const message = \u0026#39;this is message\u0026#39; const logMessage = ()=\u0026gt;{ console.log(message) } \u0026lt;/script\u0026gt; 组合式API - reactive和ref函数 # 1. reactive # 接受对象类型数据的参数传入并返回一个响应式的对象\n\u0026lt;script setup\u0026gt; // 导入 import { reactive } from \u0026#39;vue\u0026#39; // 执行函数 传入参数 变量接收 const state = reactive({ msg:\u0026#39;this is msg\u0026#39; }) const setSate = ()=\u0026gt;{ // 修改数据更新视图 state.msg = \u0026#39;this is new msg\u0026#39; } \u0026lt;/script\u0026gt; \u0026lt;template\u0026gt; {{ state.msg }} \u0026lt;button @click=\u0026#34;setState\u0026#34;\u0026gt;change msg\u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt; 2. ref # 接收简单类型或者对象类型的数据传入并返回一个响应式的对象\n\u0026lt;script setup\u0026gt; // 导入 import { ref } from \u0026#39;vue\u0026#39; // 执行函数 传入参数 变量接收 const count = ref(0) const setCount = ()=\u0026gt;{ // 修改数据更新视图必须加上.value count.value++ } \u0026lt;/script\u0026gt; \u0026lt;template\u0026gt; \u0026lt;button @click=\u0026#34;setCount\u0026#34;\u0026gt;{{count}}\u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt; 3. reactive 对比 ref # 都是用来生成响应式数据 不同点 reactive不能处理简单类型的数据 ref参数类型支持更好,但是必须通过.value做访问修改 ref函数内部的实现依赖于reactive函数 在实际工作中的推荐 推荐使用ref函数,减少记忆负担,小兔鲜项目都使用ref 组合式API - computed # 计算属性基本思想和Vue2保持一致,组合式API下的计算属性只是修改了API写法\n\u0026lt;script setup\u0026gt; // 导入 import {ref, computed } from \u0026#39;vue\u0026#39; // 原始数据 const count = ref(0) // 计算属性 const doubleCount = computed(()=\u0026gt;count.value * 2) // 原始数据 const list = ref([1,2,3,4,5,6,7,8]) // 计算属性list const filterList = computed(item=\u0026gt;item \u0026gt; 2) \u0026lt;/script\u0026gt; 组合式API - watch # 侦听一个或者多个数据的变化,数据变化时执行回调函数,俩个额外参数 immediate控制立刻执行,deep开启深度侦听\n1. 侦听单个数据 # \u0026lt;script setup\u0026gt; // 1. 导入watch import { ref, watch } from \u0026#39;vue\u0026#39; const count = ref(0) // 2. 调用watch 侦听变化 watch(count, (newValue, oldValue)=\u0026gt;{ console.log(`count发生了变化,老值为${oldValue},新值为${newValue}`) }) \u0026lt;/script\u0026gt; 2. 侦听多个数据 # 侦听多个数据,第一个参数可以改写成数组的写法\n\u0026lt;script setup\u0026gt; // 1. 导入watch import { ref, watch } from \u0026#39;vue\u0026#39; const count = ref(0) const name = ref(\u0026#39;cp\u0026#39;) // 2. 调用watch 侦听变化 watch([count, name], ([newCount, newName],[oldCount,oldName])=\u0026gt;{ console.log(`count或者name变化了,[newCount, newName],[oldCount,oldName]) }) \u0026lt;/script\u0026gt; 3. immediate # 在侦听器创建时立即出发回调,响应式数据变化之后继续执行回调\n\u0026lt;script setup\u0026gt; // 1. 导入watch import { ref, watch } from \u0026#39;vue\u0026#39; const count = ref(0) // 2. 调用watch 侦听变化 watch(count, (newValue, oldValue)=\u0026gt;{ console.log(`count发生了变化,老值为${oldValue},新值为${newValue}`) },{ immediate: true }) \u0026lt;/script\u0026gt; 4. deep # 通过watch监听的ref对象默认是浅层侦听的,直接修改嵌套的对象属性不会触发回调执行,需要开启deep\n\u0026lt;script setup\u0026gt; // 1. 导入watch import { ref, watch } from \u0026#39;vue\u0026#39; const state = ref({ count: 0 }) // 2. 监听对象state watch(state, ()=\u0026gt;{ console.log(\u0026#39;数据变化了\u0026#39;) }) const changeStateByCount = ()=\u0026gt;{ // 直接修改不会引发回调执行 state.value.count++ } \u0026lt;/script\u0026gt; \u0026lt;script setup\u0026gt; // 1. 导入watch import { ref, watch } from \u0026#39;vue\u0026#39; const state = ref({ count: 0 }) // 2. 监听对象state 并开启deep watch(state, ()=\u0026gt;{ console.log(\u0026#39;数据变化了\u0026#39;) },{deep:true}) const changeStateByCount = ()=\u0026gt;{ // 此时修改可以触发回调 state.value.count++ } \u0026lt;/script\u0026gt; 组合式API - 生命周期函数 # 1. 选项式对比组合式 # 2. 生命周期函数基本使用 # 导入生命周期函数 执行生命周期函数,传入回调 \u0026lt;scirpt setup\u0026gt; import { onMounted } from \u0026#39;vue\u0026#39; onMounted(()=\u0026gt;{ // 自定义逻辑 }) \u0026lt;/script\u0026gt; 3. 执行多次 # 生命周期函数执行多次的时候,会按照顺序依次执行\n\u0026lt;scirpt setup\u0026gt; import { onMounted } from \u0026#39;vue\u0026#39; onMounted(()=\u0026gt;{ // 自定义逻辑 }) onMounted(()=\u0026gt;{ // 自定义逻辑 }) \u0026lt;/script\u0026gt; 组合式API - 父子通信 # 1. 父传子 # 基本思想\n父组件中给子组件绑定属性 子组件内部通过props选项接收数据 2. 子传父 # 基本思想\n父组件中给子组件标签通过@绑定事件 子组件内部通过 emit 方法触发事件 组合式API - 模版引用 # 概念:通过 ref标识 获取真实的 dom对象或者组件实例对象\n1. 基本使用 # 实现步骤:\n调用ref函数生成一个ref对象 通过ref标识绑定ref对象到标签 2. defineExpose # 默认情况下在 \u0026lt;script setup\u0026gt; 语法糖下组件内部的属性和方法是不开放给父组件访问的,可以通过defineExpose编译宏指定哪些属性和方法容许访问 说明:指定testMessage属性可以被访问到\n组合式API - provide和inject # 1. 作用和场景 # 顶层组件向任意的底层组件传递数据和方法,实现跨层组件通信\n2. 跨层传递普通数据 # 实现步骤\n顶层组件通过 provide 函数提供数据 底层组件通过 inject 函数提供数据 3. 跨层传递响应式数据 # 在调用provide函数时,第二个参数设置为ref对象\n4. 跨层传递方法 # 顶层组件可以向底层组件传递方法,底层组件调用方法修改顶层组件的数据\n综合案例 # 1. 项目地址 # git clone http://git.itcast.cn/heimaqianduan/vue3-basic-project.git 2. 项目说明 # 模版已经配置好了案例必须的安装包 案例用到的接口在 README.MD文件 中 案例项目有俩个分支,main主分支为开发分支,complete分支为完成版分支供开发完参考 « VitePress\n"},{"id":62,"href":"/git/","title":"Git","section":"","content":" 🏠 首页 / Git\nGit # Git 常用\n使用 git-secret 保护仓库敏感数据\nGithub Action 使用最佳实践\n使用 GitHub 托管 helm-chart 仓库\nGitHub 托管 helm-chart 仓库\nGitHub\nGitlab 添加 K8s 集群\nGitlab 跨版本升级\n多 GitHub 账号管理\n搭建最简单的 git 仓库服务\n"},{"id":63,"href":"/git/common-usage/","title":"Common Usage","section":"Git","content":" 🏠 首页 / Git / Git 常用\nGit 常用 # 本篇主要介绍 Git 的常用命令,包括 Git 的基本配置、创建仓库、添加文件、提交文件、查看状态、查看提交历史、撤销修改、删除文件、分支管理、远程仓库等。\nGit 基本配置 # 配置 SSH 密钥 # 提交代码到远程仓库时,需要使用 SSH 密钥进行身份验证,因此需要先配置 SSH 密钥。\n# 生成 SSH 密钥 ssh-keygen -t rsa -C poneding@gmail.com # 查看 SSH 密钥,复制到 GitHub/GitLab 等 SSH Keys 中 cat ~/.ssh/id_rsa.pub 添加 .ssh/config 文件,配置 SSH 密钥的别名,方便管理多个 SSH 密钥。\nvim ~/.ssh/config 以 GitHub 为例,配置如下:\n# GitHub Host github.com HostName github.com IdentityFile ~/.ssh/id_rsa 配置用户名和邮箱 # 提交代码时,需要配置用户名和邮箱。\ngit config --global user.name \u0026#34;poneding\u0026#34; git config --global user.email poneding@gmail.com # 只配置当前仓库的用户名和邮箱 git config user.name \u0026#34;poneding\u0026#34; git config user.email poneding@gmail.com 配置别名 # 通过配置别名,可以简化 Git 命令的输入,例如,将 git status 简化为 git st 获取 Git 仓库的状态。\ngit config --global alias.st status git config --global alias.ci commit git config --global alias.co checkout git config --global alias.br branch git config --global alias.ck checkout 配置默认分支 # 最新版本的 Git 默认分支为 main,如果需要修改为 master,可以通过如下命令进行修改。\ngit config --global init.defaultBranch master 配置 rabase # 当你运行 git pull 时,Git 会从远程仓库拉取最新的更改并将它们合并到你的本地分支。如果设置了 pull.rebase 为 true,Git 会在拉取远程更改后,不使用普通的 merge 操作,而是使用 rebase 将你本地的提交放在远程更改的后面。这样可以有效避免分支合并时产生大量的无用的 merge commit,有助于保持一个更线性、整洁的提交历史。\ngit config --global pull.rebase true Git 仓库 # 初始化仓库 # # 在当前目录初始化仓库 git init # 初始化并指定初始化分支 git init -b dev # 在指定目录创建仓库 git init mydir 克隆远程仓库 # # SSH 方式 git clone git@github.com/poneding/archives.git # HTTPS 方式 git clone https://github.com/kubernetes/kubernetes.git 提交本地更改到远程仓库 # # 拉取远程仓库的最新更改 git pull # 添加所有更改到暂存区 git add . # 提交更改 git commit -m \u0026#34;this commit\u0026#34; # 推送到远程仓库 git push # 首次推送到远程仓库 git push -u origin master # 推送所有本地分支到远程仓库 git push --all # 推送所有标签到远程仓库 git push --tags 添加远程仓库 # # 查看远程仓库 git remote -v # 添加远程仓库 git remote add origin git@github.com/poneding/archives.git # 修改远程仓库地址 git remote set-url origin git@github.com/poneding/archives-new.git # 删除远程仓库 git remote rm origin 暂存更改 # 添加文件 # # 将更改添加到暂存区 git stash # 查看暂存区的更改 git stash list # 取出暂存区的更改 git stash pop 提交修正 # 多次提交的内容其实可以合并成一次提交,这样可以保持提交历史的整洁。 想要修改提交的信息,例如提交信息描述不准确、拼写错误等。 # 修改最近一次提交的信息 git commit --amend -m \u0026#34;new commit message\u0026#34; 提交规范 # 规范化的 commit 提交有一些约定俗称的格式,下面快速枚举一些常见格式:\n# 功能类 git commit -m \u0026#34;feat: xxx\u0026#34; # 修复类 git commit -m \u0026#34;fix: xxx\u0026#34; # 杂事 git commit -m \u0026#34;chore: xxx\u0026#34; 撤销更改 # 还未 git add # 这里其实涉及到了两种更改:\n文件新增:使用 git clean -f 撤销更改 文件修改/删除:使用 git restore .(新版,推荐)或 git checkout .(旧版) 撤销更改 git clean -f \u0026amp;\u0026amp; git restore . 已经 git add,还未 git commit # # 取消所有暂存的更改 git reset git reset . git reset HEAD . # 撤销单个文件的暂存更改 git reset HEAD README.md 已经 git commit # # 获取最近一次提交的 commit_id git log # 撤销最近一次提交 git reset [commit_id] Git 分支 # 查看分支 # # 查看本地分支 git branch # 查看远程分支 git branch -r # 查看所有分支 git branch -a 创建分支 # # 创建新分支 git branch dev # 创建并切换到新分支 git checkout -b dev # 创建孤立分支,新分支没有任何提交历史,但会保留之前分支的所有文件 git checkout --orphan dev 同步远程分支 # 远程分支分两种:\n本地远程分支,可以通过 git branch -r 查看到; 远程仓库分支,可能是其他人创建的,可以通过 git fetch 同步到本地远程分支。 # 远程仓库有了新的分支,将远程分支同步到本地 git fetch # 将远程分支同步到本地 git checkout -b dev origin/dev 合并分支 # # 将当前分支 dev 的更改合并到 master 分支 git checkout master git merge dev git push 删除分支 # # 删除本地分支 git branch -d dev # 删除远程分支 git push origin -d dev 场景:远程仓库已经删除了分支,本地仓库还存在该分支,需要删除本地分支。\n# 1. 查看远程分支和本地分支的对应关系: git remote show origin # 2. 删除远程已经删除过的分支: git remote prune origin Git 标签 # 查看标签 # git tag 创建标签 # # 创建标签(轻量标签,只是某个提交的引用) git tag v1.0.0 # 创建标签(附注标签) git tag -a v1.0.0 -m \u0026#34;release v1.0.0\u0026#34; # 推送标签到远程仓库 git push origin v1.0.0 删除标签 # # 删除本地标签 git tag -d v1.0.0 # 删除远程标签 git push origin :refs/tags/v1.0.0 # 本地批量删除标签 git tag | xargs git tag -d # 本地删除 v1.1.0 开头的标签 git tag | grep \u0026#34;v1.1.0.\\d$\u0026#34; | xargs git tag -d # 批量删除远程标签 git show-ref --tag | awk \u0026#39;/(.*)(\\s+)(.*)$/ {print \u0026#34;:\u0026#34; $2}\u0026#39; | xargs git push origin Git Remote # 查看远程信息 # git remote -v git remote show 新增远程 # git remote add origin git@github.com:poneding/demo.git 更新远程地址 # git remote set-url origin git@github.com:poneding/demo.git 删除远程 # git remote remove origin 多远程 # git remote add origin git@github.com:poneding/demo.git git remote add origin2 git@gitlab.com:poneding/demo.git git push origin master git push origin2 master # 所有分支、tag 等 # git push --all origin # 所有分支 git push --mirror origin # git push --all origin2 # 所有分支 git push --mirror origin2 注意,后续提交到远程时:\n# 默认提交到 origin 远程 git push # 提交到 origin2 远程 git pull origin2 master git push origin2 master Git 信息 # 提交哈希值 # # 完整的提交哈希值 git rev-parse HEAD # 简短的提交哈希值 (7 位) git rev-parse --short HEAD 删除远程仓库提交历史 # rm -rf .git git init git add . git commit -m \u0026#34;first commit\u0026#34; git remote add origin git@github.com/poneding/archives.git # 强制推送到远程仓库,覆盖远程仓库的提交历史 git push -u --force origin master 排错 # Q1:Permissions 0664 for \u0026lsquo;~/.ssh/id_rsa\u0026rsquo; are too open # ssh 私钥文件权限过高,需要修改为 600。\nchmod 600 ~/.ssh/id_rsa Q1:证书颁发者未被识别 # 问题描述:Peer’s Certificate issuer is not recognized.\n解决方法:\n# 仓库级别,在仓库目录下执行 git config http.\u0026#34;sslVerify\u0026#34; false # 全局级别 git config --global http.\u0026#34;sslVerify\u0026#34; false » 使用 git-secret 保护仓库敏感数据\n"},{"id":64,"href":"/git/git-secret/","title":"Git Secret","section":"Git","content":" 🏠 首页 / Git / 使用 git-secret 保护仓库敏感数据\n使用 git-secret 保护仓库敏感数据 # 如何保护 git 仓库中的敏感数据,例如数据库连接字符串,账号密码等?\n首先,最好先将仓库设置成私有仓库!然后,\n第一种方式:带有敏感数据的文件加入到. gitignore,不提交到仓库中; 第二种方式:敏感数据库文件加密后再提交到仓库中,这个就是今天要说的 git-secret。 这两种方式都有优缺点:\n第一种方式,较为靠谱,敏感文件在 git 仓库之外,根本上避免仓库敏感数据的泄露,但是敏感文件不受版本控制了,开发人员需要在其他频道同步敏感文件的更新,而且使用到自动部署时需要另外去拉取敏感数据,最好是有自己的敏感数据配置中心统一管理;\n第二种方式,使用 git-secret 加密敏感文件,这样敏感文件被仓库 ignore 掉,转而提交加密后的文件,但是敏感文件如果更新了开发人员要记得再次加密。\ngit-secret 简介 # git-secret 是一个在 git 仓库中加密文件的工具,将敏感文件加密,得到加密文件,将文件保存到仓库中,这样敏感文件也是版本控制,你可以获取到该文件的所有提交记录。\n使用 gpg 和所有信任用户的公钥加密文件,每个信任用户可以使用个人密钥解密文件,如果用户离开团队,将删除用户的公钥即可,他也就不能再解密文件了。\ngit-secret 使用 # 假设我现在有一个仓库 git-secret-demo,仓库下有一个包含敏感信息的文件 secret.json:\n我现在想做的是使用 git-secret 将 secret.json 文件加密。\n首先得安装 gpg 工具 # Debian \u0026amp; Ubuntu sudo apt install gnupg -y ​ # Macos brew install gnupg 本地创建 gpg RSA 密钥对 gpg --gen-key 在创建时需要输入自己的用户名和邮箱,并且需要输入你的加密密码。\n导出你的公钥文件,你可以拿到这个公钥文件交给 git 仓库管理员,让他把你加入到仓库的信任用户列表:\ngpg --armor --export ding.peng@email.cn \u0026gt; dp-public-key.gpg 导入公钥文件,git 仓库管理员将公钥文件导入本地环境,之后可以将本地环境中的公钥加入到 git-secret 信任用户列表:\ngpg --import other-pulic-key.gpg 查看本地密钥对列表\ngpg --list-key 安装 git-secret # Debian \u0026amp; Ubuntu sudo apt install git-secret -y ​ # Macos brew install git-secret 使用 git secret init 初始化,在仓库根目录下执行 git secret init 默认会在仓库下生成 .gitsecret 目录和 .gitignore 文件。\n将用户加入到 git-secret 信任用户列表 git secret tell ding.peng@email.cn 添加加密文件到 git-secret git secret add -i secret.json 这里 -i 自动将要加密的文件添加到 .gitignore\n此时使用如下命令可以在目录下看到生成的 secret.json.secret 的加密后文件\ngit secret hide 到这时,你的加密文件不会提交到仓库中,从 git 仓库新克隆下来的时候是没有加密文件的,你需要解密文件才能得到文件,例如我现在删除加密文件,然后再通过解密加密过的文件重新得到敏感文件,使用命令:\ngit secret reveal # 第一次使用该命令需要输入信任用户的密码 从以上截图,你可以看到敏感文件 secret.json 失而复得的整个过程。\ngit-secret 常用命令 # 除了上面使用过的命令,你可能还会用到以下命令:\n**git secret usage:**列出 git secret 可用命令。 **git secret whoknows:**查看当前 git 仓库允许访问私密文件的用户邮箱列表。 git secret add:添加加密文件。 **git secret remove:**移除加密文件。 **git secret list:**列出加密文件。 **git secret changes:**查看加密文件的更改记录。 **git secret clean:**清除所有加密文件。 **git secret removeperson:**移除信任用户。 « Git 常用\n» Github Action 使用最佳实践\n"},{"id":65,"href":"/git/github-action-best-practice/","title":"Github Action Best Practice","section":"Git","content":" 🏠 首页 / Git / Github Action 使用最佳实践\nGithub Action 使用最佳实践 # Commit 构建 beta 版本镜像 # 仓库根目录下创建 .github/workflows/commit-cicd.yml 文件,用于提交代码触发 github action。\nbeta 版本的镜像 tag 命名规则:{vx.x.x}-beta-{COMMIT_ID},例如:v1.0.0-beta-f37cfa2\nname: commit-cicd ​ env: BASE_VERSION: v1.0.0 ​ on: push: branches: [main] workflow_dispatch: ​ jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 ​ - name: Set ENV run: | echo \u0026#34;VERSION=${BASE_VERSION}-beta-${GITHUB_SHA::7}\u0026#34; \u0026gt;\u0026gt; $GITHUB_ENV ​ - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 ​ - name: Login to docker hub uses: docker/login-action@v2 with: username: poneding password: ${{ secrets.DOCKER_PASSWORD }} ​ - name: Build and push id: docker_build uses: docker/build-push-action@v3 with: platforms: linux/amd64,linux/arm64 file: Dockerfile.multiarch push: true tags: poneding/demo:${VERSION} - name: Image digest run: echo ${{ steps.docker_build.outputs.digest }} ​ - name: Deploy demo id: deploy uses: actions-hub/kubectl@master env: KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }} with: args: set image deployment/demo demo=poneding/demo:${VERSION} Release 构建 stable 版本镜像 # 仓库根目录下创建 .github/workflows/release-cicd.yml文件,用于 Release 发布触发 github action。\nstable 版本镜像 tag 命名规则:vx.x.x-stable,例如:v1.0.0-stable\nname: release-cicd ​ on: release: types: [created] ​ jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 ​ - name: Set ENV run: | echo \u0026#34;VERSION=${GITHUB_REF#refs/*/}\u0026#34; \u0026gt;\u0026gt; $GITHUB_ENV ​ - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Login to docker hub uses: docker/login-action@v2 with: username: poneding password: ${{ secrets.DOCKER_PASSWORD }} ​ - name: Build and push id: docker_build uses: docker/build-push-action@v3 env: VERSION: ${GITHUB_REF#refs/*/} with: platforms: linux/amd64,linux/arm64 file: Dockerfile.multiarch push: true tags: poneding/demo:latest,poneding/demo:${VERSION} - name: Image digest run: echo ${{ steps.docker_build.outputs.digest }} ​ - name: Deploy demo id: deploy uses: actions-hub/kubectl@master env: KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }} with: args: set image deployment/demo demo=poneding/demo:${VERSION} 镜像 tag 版本号来自于 git 中创建的 tag,所以如果要发布稳定版本,需要打一个命名为 vx.x.x-stable 的 tag,然后 Release 时,选择该 tag 即可。\n« 使用 git-secret 保护仓库敏感数据\n» 使用 GitHub 托管 helm-chart 仓库\n"},{"id":66,"href":"/git/github-host-helm-chart/","title":"Github Host Helm Chart","section":"Git","content":" 🏠 首页 / Git / 使用 GitHub 托管 helm-chart 仓库\n使用 GitHub 托管 helm-chart 仓库 # helm 官方文档:\nHelm | Chart Releaser Action to Automate GitHub Page Charts 创建 GitHub 仓库,例如:helm-charts,克隆到本地。 git clone git@github.com:[gh_id]/helm-charts.git cd helm-charts 创建干净的 gh-pages 分支。 git checkout --orphan gh-pages git rm -rf . vim README.md # helm-charts ## Usage [Helm](https://helm.sh) must be installed to use the charts. Please refer to Helm\u0026#39;s [documentation](https://helm.sh/docs) to get started. Once Helm has been set up correctly, add the repo as follows: ```bash helm repo add mycharts https://[gh_id].github.io/helm-charts ``` If you had already added this repo earlier, run `helm repo update` to retrieve the latest versions of the packages. You can then run`helm search repo mycharts` to see the charts. To install the mycharts chart: ```bash helm install myapp mycharts/myapp ``` To uninstall the chart: ```bash helm uninstall myapp ``` git add . git commit -am \u0026#34;add README.md\u0026#34; git push -u origin gh-pages 仓库启用 GitHub Pages 功能,选择 gh-pages 分支。\ncharts 开发。\ngit checkout master mkdir charts cd charts helm create myapp cd .. 使用 GitHub Action 搭配 chart-releaser 功能,为我们自动发布 charts 版本。 mkdir -p .github/workflows vim .github/workflows/release-chart.yaml name: Release Charts on: push: branches: - main jobs: release: # depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions # see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token permissions: contents: write runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 with: fetch-depth: 0 - name: Configure Git run: | git config user.name \u0026#34;$GITHUB_ACTOR\u0026#34; git config user.email \u0026#34;$GITHUB_ACTOR@users.noreply.github.com\u0026#34; - name: Install Helm uses: azure/setup-helm@v3 - name: Run chart-releaser uses: helm/chart-releaser-action@v1.5.0 env: CR_TOKEN: \u0026#34;${{ secrets.GITHUB_TOKEN }}\u0026#34; 需要提前将 GitHub Token 生成出来,并配置到仓库的 Secrets 中。\n提交代码即可第一次发版。\n之后对 charts 改动后,修改 Chart.yaml 中的 version 字段,helm-releaser 会检测版本改动,并自动发版。\n参考 # chart-releaser-action « Github Action 使用最佳实践\n» GitHub 托管 helm-chart 仓库\n"},{"id":67,"href":"/git/github-hosting-helm-reop/","title":"Github Hosting Helm Reop","section":"Git","content":" 🏠 首页 / Git / GitHub 托管 helm-chart 仓库\nGitHub 托管 helm-chart 仓库 # 创建 GitHub 仓库 # 创建 GitHub helm charts 仓库,例如:helm-charts,克隆到本地。\ngit clone git@github.com:poneding/helm-charts.git cd helm-charts 创建 gh-pages 孤立分支 # git checkout --orphan gh-pages git rm -rf . vim README.md 编写 README.md 文件,例如:\n# helm-charts ## Usage [Helm](https://helm.sh) must be installed to use the charts. Please refer to Helm\u0026#39;s [documentation](https://helm.sh/docs) to get started. Once Helm has been set up correctly, add the repo as follows: ```bash helm repo add poneding https://poneding.github.io/helm-charts ``` If you had already added this repo earlier, run `helm repo update` to retrieve the latest versions of the packages. You can then run`helm search repo mycharts` to see the charts. To install the mycharts chart: ```bash helm install myapp poneding/myapp ``` To uninstall the chart: ```bash helm uninstall myapp ``` 提交代码:\ngit add . git commit -am \u0026#34;Add README.md\u0026#34; git push -u origin gh-pages 启用 GitHub Pages # 仓库启用 GitHub Pages 功能,选择 gh-pages 分支。\nCharts 开发 # git checkout master mkdir charts cd charts helm create myapp cd .. 配置 GitHub Action # 使用 GitHub Action 搭配 chart-releaser 功能,为我们自动发布 charts 版本。\nmkdir -p .github/workflows vim .github/workflows/release-chart.yaml name: Release Charts on: push: branches: - master jobs: release: # depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions # see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token permissions: contents: write runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 with: fetch-depth: 0 - name: Configure Git run: | git config user.name \u0026#34;$GITHUB_ACTOR\u0026#34; git config user.email \u0026#34;$GITHUB_ACTOR@users.noreply.github.com\u0026#34; - name: Install Helm uses: azure/setup-helm@v3 - name: Run chart-releaser uses: helm/chart-releaser-action@v1.5.0 env: CR_TOKEN: \u0026#34;${{ secrets.GITHUB_TOKEN }}\u0026#34; 需要提前将 GitHub Token 生成出来,并配置到仓库的 Secrets 中。\n提交代码即可第一次发版。\n之后对 charts 改动后,修改 Chart.yaml 中的 version 字段,helm-releaser 会检测版本改动,并自动发版。\n参考 # Helm | Chart Releaser Action to Automate GitHub Page Charts helm/chart-releaser-action « 使用 GitHub 托管 helm-chart 仓库\n» GitHub\n"},{"id":68,"href":"/git/github/","title":"Github","section":"Git","content":" 🏠 首页 / Git / GitHub\nGitHub # GitHub 托管 helm chart 仓库 # GitHub 托管 helm chart 仓库\n获取仓库最新 Release 的版本 # 方法一:\ncurl -s https://api.github.com/repos/ketches/registry-proxy/releases/latest | jq -r .tag_name 方法二:\nbasename $(curl -s -w %{redirect_url} https://github.com/ketches/registry-proxy/releases/latest) « GitHub 托管 helm-chart 仓库\n» Gitlab 添加 K8s 集群\n"},{"id":69,"href":"/git/gitlab-intergrate-k8s/","title":"Gitlab Intergrate K8s","section":"Git","content":" 🏠 首页 / Git / Gitlab 添加 K8s 集群\nGitlab 添加 K8s 集群 # 本文介绍如何在 Gitlab 项目中添加 K8s 集群,以便使用 K8s 集群部署 gitlab-runner 帮我们运行 gitlab 的 CI/CD。\n参考官方文档: https://docs.gitlab.com/ee/user/project/clusters/add_remove_clusters.html#add-existing-cluster\n操作步骤 # 找到添加位置:\n登入 gitlab 后,进入自己的项目主页,菜单栏 Operations =\u0026gt; Kubernetes =\u0026gt; Add Kubernetes cluster,选择页签 Add existing cluster。\n我们只需要获取响应的值填录到该表单即可。Kubernetes cluster name 集群名称随意填,Project namespace 可不填。\n获取 API URL:\n运行以下命令得到输出值:\nkubectl cluster-info | grep \u0026#39;Kubernetes master\u0026#39; | awk \u0026#39;/http/ {print $NF}\u0026#39; 获取 CA Certificate:\n运行以下命令得到输出值:\nkubectl get secret $(kubectl get secret | grep default-token | awk \u0026#39;{print $1}\u0026#39;) -o jsonpath=\u0026#34;{[\u0026#39;data\u0026#39;][\u0026#39;ca\\.crt\u0026#39;]}\u0026#34; | base64 --decode 获取 Token:\n创建文件 gitlab-sa.yaml:\nvim gitlab-sa.yaml 文件写入如下内容:\napiVersion: v1 kind: ServiceAccount metadata: name: gitlab-sa namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: gitlab-sa roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: gitlab-sa namespace: kube-system 运行以下命令得到输出值:\nkubectl apply -f gitlab-sa.yaml kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep gitlab-sa | awk \u0026#39;{print $1}\u0026#39;) 添加完成之后,可以在集群中安装你想用的插件了,例如 gitlab-runner。\n踩坑记录 # Gitlab 与 Kubernetes 版本兼容问题\n在 Gitlab 中添加 Kubernetes 集群,可能存在两者版本不兼容的问题,这会导致 gitlab 调用 K8s 集群的 API 失败,可能是因为 K8s 不同版本的 api 更新的缘故。尽量使用最新版本的 Gitlab,他会支持更多版本的 K8s API。\n可以通过文档查看 Gitlab 支持的 K8s 版本: https://docs.gitlab.com/ee/user/project/clusters/index.html\n如果在添加集群遇到 is blocked: Requests to the local network are not allowed 问题,需要设置\nSettings =\u0026gt; Network =\u0026gt; Outbound requests,将选项 Allow requests to the local network from web hooks and services 勾选即可。\n« GitHub\n» Gitlab 跨版本升级\n"},{"id":70,"href":"/git/gitlab-upgrade-cross-version/","title":"Gitlab Upgrade Cross Version","section":"Git","content":" 🏠 首页 / Git / Gitlab 跨版本升级\nGitlab 跨版本升级 # 本文记录 Gitlab 跨版本升级的具体操作过程。\n按照官方的说法,gitlab 允许小版本直接升级,大版本需要阶段升级。\n跨版本升级示例:11.0.x -\u0026gt; 11.11.x -\u0026gt; 12.0.x -\u0026gt; 12.10.x -\u0026gt; 13.0.x。\n官方推荐的升级路线文档: https://docs.gitlab.com/ee/policy/maintenance.html#upgrade-recommendations\n目的 # 实现 gitlab 版本:11.2.3 到 13.0.0 版本的升级,我选择的升级路线是:11.2.3 =\u0026gt; 11.11.8 =\u0026gt; 12.0.12 =\u0026gt; 12.10.6 =\u0026gt; 13.0.0 =\u0026gt; 13.1.2\n我当前创建 gitlab 容器的脚本如下:\nsudo docker run --detach \\ --hostname gitlab.example.com \\ --publish 8443:443 --publish 8080:80 --publish 8022:22 \\ --name gitlab \\ --restart always \\ --volume /home/ubuntu/Apps/gitlab/etc/gitlab:/etc/gitlab \\ --volume /home/ubuntu/Apps/gitlab/var/log/gitlab/logs:/var/log/gitlab \\ --volume /home/ubuntu/Apps/gitlab/var/opt/gitlab:/var/opt/gitlab \\ gitlab/gitlab-ce:11.2.3-ce.0 我当前的 gitlab 容器已经将 /etc/gitlab,/var/log/gitlab,/var/opt/gitlab 挂载到了宿主机上。\n操作步骤 # 进入 gitlab 容器,停止 gitlab 服务,然后退出容器:\nsudo docker exec -it gitlab /bin/bash # 进入容器后执行 gitlab-ctl stop 退出容器后,删除 gitlab 容器:\nsudo docker rm gitlab -f 使用新版本的脚本运行 gitlab 容器,这里只修改 gitlab 的镜像版本就可以了:\nsudo docker run --detach \\ --hostname gitlab.example.com \\ --publish 8443:443 --publish 8080:80 --publish 8022:22 \\ --name gitlab \\ --restart always \\ --volume /home/ubuntu/Apps/gitlab/etc/gitlab:/etc/gitlab \\ --volume /home/ubuntu/Apps/gitlab/var/log/gitlab/logs:/var/log/gitlab \\ --volume /home/ubuntu/Apps/gitlab/var/opt/gitlab:/var/opt/gitlab \\ gitlab/gitlab-ce:11.2.3-ce.0 启动容器后,查看容器运行状态:\nsudo docker ps | grep gitlab 等到容器状态为 Up xxx (healthy) 后,进入容器,停掉 gitlab 服务。。。\n后面就是重复工作了,直到运行最新版本的gitlab容器。\nGitlab 备份 # 在升级之前,最好先对 gitlab 数据做一个全量备份,避免升级失败造成的不可逆影响。\n具体的还原操作如下:\n如果你和我一样使用 gitlab 容器,首先进入容器,然后执行备份命令:\ngitlab-rake gitlab:backup:create 以上命令执行完后,会在容器的 /var/opt/gitlab/backups 目录下创建文件名类似 1592276197_2020_06_16_11.2.3_gitlab_backup.tar 的备份文件。\nGitlab 还原 # 如果你想利用 gitlab 的备份文件还原,那么你运行还原操作的 gitlab 必须和备份时使用的 gitlab 版本一致,否则可能会出现还原失败的问题。\n具体的还原操作如下:\n如果你和我一样使用 gitlab 容器,首先将 gitlab 的备份文件拷贝到 /var/opt/gitlab/backups/ 目录下,然后进入 gitlab 容器,执行还原命令:\ngitlab-rake gitlab:backup:restore BACKUP=1592276197_2020_06_16_11.2.3 BACKUP 命令参数指定值为 /var/opt/gitlab/backups/ 目录下的备份文件,但是无需携带 _gitlab_backup.tar 后缀。\n« Gitlab 添加 K8s 集群\n» 多 GitHub 账号管理\n"},{"id":71,"href":"/git/multi-github-account-management/","title":"Multi Github Account Management","section":"Git","content":" 🏠 首页 / Git / 多 GitHub 账号管理\n多 GitHub 账号管理 # 实际开发工作中,你有可能多个 GitHub 账号:个人开发账号,工作开发账号。\n在仓库代码管理的过程中你需要重复的使用 git config user.* 来切换代码提交账号,很是麻烦。以下方案可以帮你解决你的烦恼。\n请确保你的 git 版本最低为 2.13\n~/.gitconfig\n[user] name = poneding email = poneding@gmail.com [includeIf \u0026#34;gitdir:~/src/workspace/\u0026#34;] path = ~/src/workspace/.gitconfig [url \u0026#34;git@github-workspace\u0026#34;] insteadOf = git@github.com [pull] rebase = false [init] defaultBranch = master [core] excludesfile = ~/.gitignore_global ~/src/workspace/.gitconfig\n[user] name = dingpeng24001 email = dingpeng24001@talkweb.com.cn [url \u0026#34;git@github-workspace\u0026#34;] insteadOf = git@github.com [pull] rebase = false [init] defaultBranch = master [core] excludesfile = ~/.gitignore_global ~/.ssh/config\nHost github.com HostName github.com IdentityFile ~/.ssh/id_rsa ​ Host github-workspace HostName github.com IdentityFile ~/.ssh/id_rsa_dingpeng24001 ~/.ssh/id_rsa 是个人账号 github ssh key\n~/.ssh/id_rsa_dingpeng24001 是工作账号 github ssh key\n« Gitlab 跨版本升级\n» 搭建最简单的 git 仓库服务\n"},{"id":72,"href":"/git/simplest-git-server/","title":"Simplest Git Server","section":"Git","content":" 🏠 首页 / Git / 搭建最简单的 git 仓库服务\n搭建最简单的 git 仓库服务 # 远端 # 创建仓库服务目录:\ngit init --bare git-server-demo.git 其实也可以直接在终端创建,但是你首先要可以能够通过 ssh 的方式连接远端,例如远端 IP 是 192.168.10.24\nssh root@192.168.10.24 git init --bare git-server-demo.git 执行完命令之后,将在远端目标目录下生成 git-server-demo 目录,子目录结构如下:\ntree git-server-demo.git git-server-demo.git ├── branches ├── config ├── description ├── HEAD ├── hooks │ ├── applypatch-msg.sample │ ├── commit-msg.sample │ ├── fsmonitor-watchman.sample │ ├── post-update.sample │ ├── pre-applypatch.sample │ ├── pre-commit.sample │ ├── pre-merge-commit.sample │ ├── prepare-commit-msg.sample │ ├── pre-push.sample │ ├── pre-rebase.sample │ ├── pre-receive.sample │ ├── push-to-checkout.sample │ └── update.sample ├── info │ └── exclude ├── objects │ ├── info │ └── pack └── refs ├── heads └── tags 终端 # 必要条件:可以 ssh 的方式登录远端服务器账号。\n尝试克隆代码:\ngit clone root@192.168.10.24:git-server-demo.git 剩余就是常规 git 步骤了。 # « 多 GitHub 账号管理\n"},{"id":73,"href":"/go/","title":"Go","section":"","content":" 🏠 首页 / Golang 编程\nGolang 编程 # Go 开发环境配置\nGolang 函数可选参数模式\nGolang 密钥对、数字签名和证书管理\nGolang 不同平台架构编译\nGolang 生成证书\ngo:linkname 指令\nGolang 列表转树\nGolang 实现双向认证\nGolang 发布类库 - 1\nGolang 发布类库 - 2\nGo 程序 SOLID 设计原则\nGolang 标准库\ntesting\nGolang\ngopkg-errors.md\nGoreleaser\nMac M1 交叉编译 CGO\npprof\n使用 Go 生成 OpenSSH 兼容的 RSA 密钥对\n"},{"id":74,"href":"/go/dev-env-config/","title":"Dev Env Config","section":"Go","content":" 🏠 首页 / Golang 编程 / Go 开发环境配置\nGo 开发环境配置 # cobra-cli # 安装:\ngo install github.com/spf13/cobra-cli@latest 自动补全:\ncobra-cli completion zsh \u0026gt; .zfunc/_cobra-cli 在 .zshrc 文件中添加内容(如果已添加,则忽略):\nfpath+=~/.zfunc autoload -Uz compinit \u0026amp;\u0026amp; compinit » Golang 函数可选参数模式\n"},{"id":75,"href":"/go/function-optional-pattern/","title":"Function Optional Pattern","section":"Go","content":" 🏠 首页 / Golang 编程 / Golang 函数可选参数模式\nGolang 函数可选参数模式 # 函数可选参数模式 # type Server struct { Addr string Timeout time.Duration } type Option func(*Server) func newServer(addr string, options ...Option) (*Server, error) { s := \u0026amp;Server{ Addr: addr, } for _, opt := range options { opt(s) } // ... return s, nil } func WithTimeout(timeout time.Duration) Option { return func(s *Server) { s.Timeout = timeout } } 通用函数可选参数模式 # type BasicService struct { redisClient string } type ServiceOption func(*BasicService) func WithRedisClient(redisClient string) ServiceOption { return func(s *BasicService) { s.redisClient = redisClient } } type UserService struct { *BasicService } type OrderService struct { *BasicService } func newUserService(opts ...ServiceOption) *UserService { us := \u0026amp;UserService{BasicService: new(BasicService)} for _, opt := range opts { opt(us.BasicService) } return us } func newOrderService(opts ...ServiceOption) *OrderService { os := \u0026amp;OrderService{BasicService: new(BasicService)} for _, opt := range opts { opt(os.BasicService) } return os } func TestNewService(t *testing.T) { us := newUserService(WithRedisClient(\u0026#34;redis\u0026#34;)) t.Log(us.redisClient) os := newOrderService(WithRedisClient(\u0026#34;redis\u0026#34;)) t.Log(os.redisClient) } « Go 开发环境配置\n» Golang 密钥对、数字签名和证书管理\n"},{"id":76,"href":"/go/go-cert-management/","title":"Go Cert Management","section":"Go","content":" 🏠 首页 / Golang 编程 / Golang 密钥对、数字签名和证书管理\nGolang 密钥对、数字签名和证书管理 # Golang 实现密钥对生成 相当于使用 openssl 生成私钥和公钥:\nopenssl genrsa -out pri.key 2048 openssl rsa -in pri.key -pubout -out pub.key package main import ( \u0026#34;crypto/rand\u0026#34; \u0026#34;crypto/rsa\u0026#34; ) func GenerateKeyPair() (*rsa.PrivateKey, *rsa.PublicKey, error) { prikey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return nil, nil, err } return prikey, \u0026amp;prikey.PublicKey, nil } 实现加密和解密 加密解密:公钥加密,私钥解密\npackage main import ( \u0026#34;crypto/rand\u0026#34; \u0026#34;crypto/rsa\u0026#34; ) func Encrypt(data []byte, publicKey *rsa.PublicKey) ([]byte, error) { return rsa.EncryptPKCS1v15(rand.Reader, publicKey, data) } func Decrypt(data []byte, privateKey *rsa.PrivateKey) ([]byte, error) { return rsa.DecryptPKCS1v15(rand.Reader, privateKey, data) } 实现数字签名 数字签名:私钥签名,公钥验证\npackage main import ( \u0026#34;crypto\u0026#34; \u0026#34;crypto/rand\u0026#34; \u0026#34;crypto/rsa\u0026#34; \u0026#34;crypto/sha256\u0026#34; ) func Sign(data []byte, privateKey *rsa.PrivateKey) ([]byte, error) { hash := sha256.New() hash.Write(data) return rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hash.Sum(nil)) } func Verify(data []byte, sig []byte, publicKey *rsa.PublicKey) error { hash := sha256.New() hash.Write(data) return rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hash.Sum(nil), sig) } « Golang 函数可选参数模式\n» Golang 不同平台架构编译\n"},{"id":77,"href":"/go/go-cross-complie/","title":"Go Cross Complie","section":"Go","content":" 🏠 首页 / Golang 编程 / Golang 不同平台架构编译\nGolang 不同平台架构编译 # 在 MacOS 平台编译成 Windows、Linux 可执行文件:\nCGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build main.go CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go 在 Windows 平台编译成 Linux、MacOS 可执行文件:\n$env:GOOS = \u0026#34;linux\u0026#34;;$env:CGO_ENABLED = \u0026#34;0\u0026#34;;$env:GOARCH = \u0026#34;amd64\u0026#34;;go build carbon/carbon.go $env:GOOS = \u0026#34;linux\u0026#34;;$env:CGO_ENABLED = \u0026#34;0\u0026#34;;$env:GOARCH = \u0026#34;arm64\u0026#34;;go build carbon/carbon.go $env:GOOS = \u0026#34;darwin\u0026#34;;$env:CGO_ENABLED = \u0026#34;0\u0026#34;;$env:GOARCH = \u0026#34;amd64\u0026#34;;go build carbon/carbon.go $env:GOOS = \u0026#34;darwin\u0026#34;;$env:CGO_ENABLED = \u0026#34;0\u0026#34;;$env:GOARCH = \u0026#34;arm64\u0026#34;;go build carbon/carbon.go 在 Linux 平台编译成 Windows、MacOS 可执行文件:\nCGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build main.go CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build main.go « Golang 密钥对、数字签名和证书管理\n» Golang 生成证书\n"},{"id":78,"href":"/go/go-gen-cert/","title":"Go Gen Cert","section":"Go","content":" 🏠 首页 / Golang 编程 / Golang 生成证书\nGolang 生成证书 # 代码实现 # package certutil import ( \u0026#34;bytes\u0026#34; \u0026#34;crypto/rand\u0026#34; \u0026#34;crypto/rsa\u0026#34; \u0026#34;crypto/x509\u0026#34; \u0026#34;crypto/x509/pkix\u0026#34; \u0026#34;encoding/pem\u0026#34; \u0026#34;math/big\u0026#34; \u0026#34;net\u0026#34; \u0026#34;time\u0026#34; ) // CA ca type CA struct { caInfo *x509.Certificate caPrivKey *rsa.PrivateKey caPem, caKeyPem []byte } // GetCAPem get ca pem bytes func (c *CA) GetCAPem() ([]byte, error) { if c.caPem == nil { // create the CA caBytes, err := x509.CreateCertificate(rand.Reader, c.caInfo, c.caInfo, \u0026amp;c.caPrivKey.PublicKey, c.caPrivKey) if err != nil { return nil, err } // pem encode caPEM := new(bytes.Buffer) _ = pem.Encode(caPEM, \u0026amp;pem.Block{ Type: \u0026#34;CERTIFICATE\u0026#34;, Bytes: caBytes, }) c.caPem = caPEM.Bytes() } return c.caPem, nil } // GetCAKeyPem get ca key pem func (c *CA) GetCAKeyPem() ([]byte, error) { if c.caKeyPem == nil { caPrivKeyPEM := new(bytes.Buffer) _ = pem.Encode(caPrivKeyPEM, \u0026amp;pem.Block{ Type: \u0026#34;RSA PRIVATE KEY\u0026#34;, Bytes: x509.MarshalPKCS1PrivateKey(c.caPrivKey), }) c.caKeyPem = caPrivKeyPEM.Bytes() } return c.caKeyPem, nil } // CreateCert make Certificate func (c *CA) CreateCert(ips []string, domains ...string) (certPem, certKey []byte, err error) { var ipAddresses []net.IP for _, ip := range ips { if i := net.ParseIP(ip); i != nil { ipAddresses = append(ipAddresses, i) } } // set up our server certificate cert := \u0026amp;x509.Certificate{ SerialNumber: big.NewInt(2019), Subject: pkix.Name{ Organization: []string{\u0026#34;poneding.com\u0026#34;}, Country: []string{\u0026#34;CN\u0026#34;}, Province: []string{\u0026#34;Beijing\u0026#34;}, Locality: []string{\u0026#34;Beijing\u0026#34;}, StreetAddress: []string{\u0026#34;Beijing\u0026#34;}, PostalCode: []string{\u0026#34;000000\u0026#34;}, }, DNSNames: domains, IPAddresses: ipAddresses, NotBefore: time.Now(), NotAfter: time.Now().AddDate(99, 0, 0), SubjectKeyId: []byte{1, 2, 3, 4, 6}, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, KeyUsage: x509.KeyUsageDigitalSignature, } certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { return nil, nil, err } certBytes, err := x509.CreateCertificate(rand.Reader, cert, c.caInfo, \u0026amp;certPrivKey.PublicKey, c.caPrivKey) if err != nil { return nil, nil, err } certPEM := new(bytes.Buffer) _ = pem.Encode(certPEM, \u0026amp;pem.Block{ Type: \u0026#34;CERTIFICATE\u0026#34;, Bytes: certBytes, }) certPrivKeyPEM := new(bytes.Buffer) _ = pem.Encode(certPrivKeyPEM, \u0026amp;pem.Block{ Type: \u0026#34;RSA PRIVATE KEY\u0026#34;, Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), }) return certPEM.Bytes(), certPrivKeyPEM.Bytes(), nil } // CreateCA create ca info func CreateCA() (*CA, error) { // set up our CA certificate ca := \u0026amp;x509.Certificate{ SerialNumber: big.NewInt(2019), Subject: pkix.Name{ Organization: []string{\u0026#34;poneding.com\u0026#34;}, Country: []string{\u0026#34;CN\u0026#34;}, Province: []string{\u0026#34;Beijing\u0026#34;}, Locality: []string{\u0026#34;Beijing\u0026#34;}, StreetAddress: []string{\u0026#34;Beijing\u0026#34;}, PostalCode: []string{\u0026#34;000000\u0026#34;}, }, NotBefore: time.Now(), NotAfter: time.Now().AddDate(99, 0, 0), IsCA: true, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, BasicConstraintsValid: true, } // create our private and public key caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { return nil, err } return \u0026amp;CA{ caInfo: ca, caPrivKey: caPrivKey, }, nil } // ParseCA parse caPem func ParseCA(caPem, caKeyPem []byte) (*CA, error) { p := \u0026amp;pem.Block{} p, caPem = pem.Decode(caPem) ca, err := x509.ParseCertificate(p.Bytes) if err != nil { return nil, err } p2 := \u0026amp;pem.Block{} p2, caKeyPem = pem.Decode(caKeyPem) caKey, err := x509.ParsePKCS1PrivateKey(p2.Bytes) if err != nil { return nil, err } return \u0026amp;CA{ caInfo: ca, caPrivKey: caKey, caPem: caPem, caKeyPem: caKeyPem, }, nil } // DomainSign create cert func DomainSign(ips []string, domains ...string) ([]byte, []byte, []byte, error) { ca, err := CreateCA() if err != nil { return nil, nil, nil, err } caPem, err := ca.GetCAPem() if err != nil { return nil, nil, nil, err } certPem, certKey, err := ca.CreateCert(ips, domains...) if err != nil { return nil, nil, nil, err } return caPem, certPem, certKey, nil } « Golang 不同平台架构编译\n» go:linkname 指令\n"},{"id":79,"href":"/go/go-linkname/","title":"Go Linkname","section":"Go","content":" 🏠 首页 / Golang 编程 / go:linkname 指令\ngo:linkname 指令 # 背景 # 阅读 Golang 源码时,发现在标准库 time.Sleep 方法没有没有方法体。如下:\n// Sleep pauses the current goroutine for at least the duration d. // A negative or zero duration causes Sleep to return immediately. func Sleep(d Duration) 当我们直接在代码中写一个空方法 func Foo(),编译时会报错:missing function body。所以标准库使用了什么魔法来实现空方法的呢? 进一步研究,得知 time.Sleep 运行时实际调用了 runtime.timeSleep方法,如下:\n// timeSleep puts the current goroutine to sleep for at least ns nanoseconds. // //go:linkname timeSleep time.Sleep func timeSleep(ns int64) { if ns \u0026lt;= 0 { return } gp := getg() t := gp.timer if t == nil { t = new(timer) gp.timer = t } t.f = goroutineReady t.arg = gp t.nextwhen = nanotime() + ns if t.nextwhen \u0026lt; 0 { // check for overflow. t.nextwhen = maxWhen } gopark(resetForSleep, unsafe.Pointer(t), waitReasonSleep, traceBlockSleep, 1) } 那么这是如何实现的呢?细心一点的话你就会发现在 runtime.timeSleep 的注释上发现 //go:linkname 指令,按我们就需要花点时间研究一下这个玩意儿了。\n介绍 # go:linkname指令的规范如下:\n//go:linkname localname importpath.name 这个指令实际是在告诉 Golang 编译器,将本地的变量或方法(localname)链接到导入的变量或方法(importpath.name)。 由于该指令破坏了类型系统和包的模块化原则,只有在引入 unsafe 包的前提下才能使用这个指令。 好了,现在我们知其所以然了,我们尝试来实现一个“空方法”吧!\n示例 # 创建一个项目:\nmake golinkname-demo cd golinkname-demo go mod init golinkname-demo touch main.go 编写 main.go 代码:\npackage main import ( \u0026#34;fmt\u0026#34; _ \u0026#34;unsafe\u0026#34; ) func main() { Foo() } //go:linkname Foo main.myFoo func Foo() func myFoo() { fmt.Println(\u0026#34;myFoo called\u0026#34;) } 运行:\n$ go run main.go myFoo called 完成!\n拓展 # 使用 go:linkname 来引用第三方包中私有的变量和方法:\npackage mypkg import ( _ \u0026#34;unsafe\u0026#34; _ \u0026#34;github.com/xxx/xxx/internal\u0026#34; ) //go:linkname a github.com/xxx/xxx/internal.a var a int //go:linkname Foo github.com/xxx/xxx/internal.foo func Foo() « Golang 生成证书\n» Golang 列表转树\n"},{"id":80,"href":"/go/go-list-to-tree/","title":"Go List to Tree","section":"Go","content":" 🏠 首页 / Golang 编程 / Golang 列表转树\nGolang 列表转树 # 场景介绍 # 从数据库获取到了菜单列表数据,这些菜单数据通过字段 ParentID 表示父子层级关系,现在需要将菜单列表数据转成树状的实例对象。\n数据库取出的初始数据:\nraw := []Menu{ {Name: \u0026#34;一级菜单 1\u0026#34;, ID: 1, PID: 0}, {Name: \u0026#34;一级菜单 2\u0026#34;, ID: 2, PID: 0}, {Name: \u0026#34;一级菜单 3\u0026#34;, ID: 3, PID: 0}, {Name: \u0026#34;二级菜单 1-1\u0026#34;, ID: 11, PID: 1}, {Name: \u0026#34;二级菜单 1-2\u0026#34;, ID: 12, PID: 1}, {Name: \u0026#34;二级菜单 1-3\u0026#34;, ID: 13, PID: 1}, {Name: \u0026#34;二级菜单 2-1\u0026#34;, ID: 21, PID: 2}, {Name: \u0026#34;二级菜单 2-2\u0026#34;, ID: 22, PID: 2}, {Name: \u0026#34;二级菜单 2-3\u0026#34;, ID: 23, PID: 2}, } 需要得到的目标数据:\n{ \u0026#34;name\u0026#34;:\u0026#34;根菜单\u0026#34;, \u0026#34;id\u0026#34;:0, \u0026#34;pid\u0026#34;:0, \u0026#34;SubMenus\u0026#34;:[ { \u0026#34;name\u0026#34;:\u0026#34;一级菜单 1\u0026#34;, \u0026#34;id\u0026#34;:1, \u0026#34;pid\u0026#34;:0, \u0026#34;SubMenus\u0026#34;:[ { \u0026#34;name\u0026#34;:\u0026#34;二级菜单 1-1\u0026#34;, \u0026#34;id\u0026#34;:11, \u0026#34;pid\u0026#34;:1 }, { \u0026#34;name\u0026#34;:\u0026#34;二级菜单 1-2\u0026#34;, \u0026#34;id\u0026#34;:12, \u0026#34;pid\u0026#34;:1 }, { \u0026#34;name\u0026#34;:\u0026#34;二级菜单 1-3\u0026#34;, \u0026#34;id\u0026#34;:13, \u0026#34;pid\u0026#34;:1 } ] }, { \u0026#34;name\u0026#34;:\u0026#34;一级菜单 2\u0026#34;, \u0026#34;id\u0026#34;:2, \u0026#34;pid\u0026#34;:0, \u0026#34;SubMenus\u0026#34;:[ { \u0026#34;name\u0026#34;:\u0026#34;二级菜单 2-1\u0026#34;, \u0026#34;id\u0026#34;:21, \u0026#34;pid\u0026#34;:2 }, { \u0026#34;name\u0026#34;:\u0026#34;二级菜单 2-2\u0026#34;, \u0026#34;id\u0026#34;:22, \u0026#34;pid\u0026#34;:2 }, { \u0026#34;name\u0026#34;:\u0026#34;二级菜单 2-3\u0026#34;, \u0026#34;id\u0026#34;:23, \u0026#34;pid\u0026#34;:2 } ] }, { \u0026#34;name\u0026#34;:\u0026#34;一级菜单 3\u0026#34;, \u0026#34;id\u0026#34;:3, \u0026#34;pid\u0026#34;:0 } ] } 代码实现 # package main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; ) func main() { // 数据库里存储的菜单 rawMenus := []Menu{ {Name: \u0026#34;一级菜单 1\u0026#34;, ID: 1, PID: 0}, {Name: \u0026#34;一级菜单 2\u0026#34;, ID: 2, PID: 0}, {Name: \u0026#34;一级菜单 3\u0026#34;, ID: 3, PID: 0}, {Name: \u0026#34;二级菜单 1-1\u0026#34;, ID: 11, PID: 1}, {Name: \u0026#34;二级菜单 1-2\u0026#34;, ID: 12, PID: 1}, {Name: \u0026#34;二级菜单 1-3\u0026#34;, ID: 13, PID: 1}, {Name: \u0026#34;二级菜单 2-1\u0026#34;, ID: 21, PID: 2}, {Name: \u0026#34;二级菜单 2-2\u0026#34;, ID: 22, PID: 2}, {Name: \u0026#34;二级菜单 2-3\u0026#34;, ID: 23, PID: 2}, } menu := \u0026amp;Menu{ Name: \u0026#34;根菜单\u0026#34;, PID: 0, } for _, rm := range rawMenus { menu.setSubMenus(rm) } // 打印结果 b, _ := json.Marshal(menu) fmt.Println(string(b)) } type Menu struct { Name string `json:\u0026#34;name\u0026#34;` ID int `json:\u0026#34;id\u0026#34;` PID int `json:\u0026#34;pid\u0026#34;` SubMenus []Menu `json:\u0026#34;,omitempty\u0026#34;` } func (m *Menu) setSubMenus(menu Menu) bool { if menu.PID == m.ID { m.SubMenus = append(m.SubMenus, menu) return true } for i := range m.SubMenus { if m.SubMenus[i].setSubMenus(menu) { return true } } return false } 注意事项 # Golang for 遍历使用 for _, item := range slice 时,item 是一份遍历元素的复制,而使用 for i := range slice 时,slice[i] 则是遍历元素本身,使用时需要注意切片扩容带来的地址变化问题。\n« go:linkname 指令\n» Golang 实现双向认证\n"},{"id":81,"href":"/go/go-mtls/","title":"Go Mtls","section":"Go","content":" 🏠 首页 / Golang 编程 / Golang 实现双向认证\nGolang 实现双向认证 # TLS # 传输层安全协议(TLS),在互联网上,通常是由服务器单向的向客户端提供证书,以证明其身份。\nmTLS # 双向 TLS 认证,是指在客户端和服务器之间使用双行加密通道,mTLS 是云原生应用中常用的通信安全协议。\n使用双向TLS连接的主要目的是当服务器应该只接受来自有限的允许的客户端的 TLS 连接时。例如,一个组织希望将服务器的 TLS 连接限制为只来自该组织的合法合作伙伴或客户。显然,为客户端添加IP白名单不是一个好的安全实践,因为IP可能被欺骗。\n为了简化 mTLS 握手的过程,我们这样简单梳理:\n客户端发送访问服务器上受保护信息的请求; 服务器向客户端提供公钥证书; 客户端通过使用 CA 的公钥来验证服务器公钥证书的数字签名,以验证服务器的证书; 如果步骤 3 成功,客户机将其客户端公钥证书发送到服务器; 服务器使用步骤 3 中相同的方法验证客户机的证书; 如果成功,服务器将对受保护信息的访问权授予客户机。 代码实现 # 需要实现客户端验证服务端的公钥证书,服务端验证客户端的公钥证书。\n生成证书 # echo \u0026#39;清理并生成目录\u0026#39; OUT=./certs DAYS=365 RSALEN=2048 CN=poneding rm -rf ${OUT}/* mkdir ${OUT} \u0026gt;\u0026gt; /dev/null 2\u0026gt;\u0026amp;1 cd ${OUT} echo \u0026#39;生成CA的私钥\u0026#39; openssl genrsa -out ca.key ${RSALEN} \u0026gt;\u0026gt; /dev/null 2\u0026gt;\u0026amp;1 echo \u0026#39;生成CA的签名证书\u0026#39; openssl req -new \\ -x509 \\ -key ca.key \\ -subj \u0026#34;/CN=${CN}\u0026#34; \\ -out ca.crt echo \u0026#39;\u0026#39; echo \u0026#39;生成server端私钥\u0026#39; openssl genrsa -out server.key ${RSALEN} \u0026gt;\u0026gt; /dev/null 2\u0026gt;\u0026amp;1 echo \u0026#39;生成server端自签名\u0026#39; openssl req -new \\ -key server.key \\ -subj \u0026#34;/CN=${CN}\u0026#34; \\ -out server.csr echo \u0026#39;签发server端证书\u0026#39; openssl x509 -req -sha256 \\ -in server.csr \\ -CA ca.crt -CAkey ca.key -CAcreateserial \\ -out server.crt -text \u0026gt;\u0026gt; /dev/null 2\u0026gt;\u0026amp;1 echo \u0026#39;删除server端自签名证书\u0026#39; rm server.csr echo \u0026#39;\u0026#39; echo \u0026#39;生成client私钥\u0026#39; openssl genrsa -out client.key ${RSALEN} \u0026gt;\u0026gt; /dev/null 2\u0026gt;\u0026amp;1 echo \u0026#39;生成client自签名\u0026#39; openssl req -new \\ -subj \u0026#34;/CN=${CN}\u0026#34; \\ -key client.key \\ -out client.csr echo \u0026#39;签发client证书\u0026#39; openssl x509 -req -sha256\\ -CA ca.crt -CAkey ca.key -CAcreateserial\\ -days ${DAYS}\\ -in client.csr\\ -out client.crt\\ -text \u0026gt;\u0026gt; /dev/null 2\u0026gt;\u0026amp;1 echo \u0026#39;删除client端自签名\u0026#39; rm client.csr echo \u0026#39;\u0026#39; echo \u0026#39;删除临时文件\u0026#39; rm ca.srl echo \u0026#39;\u0026#39; echo \u0026#39;完成\u0026#39; 服务端 # package main import ( \u0026#34;crypto/tls\u0026#34; \u0026#34;crypto/x509\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;os\u0026#34; \u0026#34;time\u0026#34; ) var ( caCert = \u0026#34;../../certs/ca.crt\u0026#34; serverCert = \u0026#34;../../certs/server.crt\u0026#34; serverKey = \u0026#34;../../certs/server.key\u0026#34; ) type mtlsHandler struct { } func (m *mtlsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, \u0026#34;Hello World! \u0026#34;, time.Now()) } func main() { pool := x509.NewCertPool() caCertBytes, err := os.ReadFile(caCert) if err != nil { panic(err) } pool.AppendCertsFromPEM(caCertBytes) server := \u0026amp;http.Server{ Addr: \u0026#34;:8443\u0026#34;, Handler: \u0026amp;mtlsHandler{}, TLSConfig: \u0026amp;tls.Config{ ClientCAs: pool, ClientAuth: tls.RequireAndVerifyClientCert, // 需要客户端证书 }, } log.Println(\u0026#34;server started...\u0026#34;) log.Fatalln(server.ListenAndServeTLS(serverCert, serverKey)) } 客户端 # package main import ( \u0026#34;crypto/tls\u0026#34; \u0026#34;crypto/x509\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;io\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;os\u0026#34; ) var ( caCert = \u0026#34;../../certs/ca.crt\u0026#34; clientCert = \u0026#34;../../certs/client.crt\u0026#34; clientKey = \u0026#34;../../certs/client.key\u0026#34; ) func main() { pool := x509.NewCertPool() caCertBytes, err := os.ReadFile(caCert) if err != nil { panic(err) } pool.AppendCertsFromPEM(caCertBytes) clientCertBytes, err := tls.LoadX509KeyPair(clientCert, clientKey) if err != nil { panic(err) } tr := \u0026amp;http.Transport{ TLSClientConfig: \u0026amp;tls.Config{ RootCAs: pool, Certificates: []tls.Certificate{clientCertBytes}, InsecureSkipVerify: true, }, } client := http.Client{ Transport: tr, } r, err := client.Get(\u0026#34;https://127.0.0.1:8443\u0026#34;) // server if err != nil { panic(err) } defer r.Body.Close() b, err := io.ReadAll(r.Body) if err != nil { panic(err) } fmt.Println(string(b)) } « Golang 列表转树\n» Golang 发布类库 - 1\n"},{"id":82,"href":"/go/go-publish-package-01/","title":"Go Publish Package 01","section":"Go","content":" 🏠 首页 / Golang 编程 / Golang 发布类库 - 1\nGolang 发布类库 - 1 # 本页介绍如何在 Github 上发布我们自己的 Golang 类库。\n1、创建 github 仓库托管 go 类库代码,例如 common-go::\n2、将仓库克隆至本地::\ngit clone https://github.com/poneding/common-go.git 3、初始化go类库的module::\ncd common-go go mod init github.com/poneding/common-go mkdir hello 注意:\n使用 go env 命令查看是否开启 go-module 功能,如果没开启需要设置环境变量:go env -w GO111MODULE=on;\nmodule 名称需要与 github 仓库一致,这样其他人才能通过 go get github.com/poneding/commmon-go 下载到你的类库。\n4、编写 go 类库代码,例如::\nhell/hello.go:\npackage hello import \u0026#34;fmt\u0026#34; func Say(name string) { fmt.Printf(\u0026#34;Hello, %s\\n\u0026#34;, name) } 5、提交 go 代码到 github::\ngit add . git commit -m \u0026#34;add hello\u0026#34; git push -u origin main 6、发行版本:\n最佳实践是创建对应的版本发布分支,然后使用发布分支创建 tag,发布:\ngit checkout -b v1 git push -u origin v1 git tag v1.0.0 git push --tags 此时,在 github 仓库 release 中可以看到发布的版本。\n7、创建 demo-go 项目,测试使用 go 类库::\ngo mod init demo-go 在 go.mod 引入 github.com/poneding/common-go@v1.0.0:\ngo.mod:\nmodule demo-go go 1.16 require github.com/poneding/common-go v1.0.0 调用 github.com/poneding/common-go 库的 hello.Say 方法:\nmain.go:\npackage main import ( \u0026#34;github.com/poneding/common-go/hello\u0026#34; ) func main() { hello.Say(\u0026#34;Jay\u0026#34;) } 8、运行::\n$ go run main.go Hello, Jay « Golang 实现双向认证\n» Golang 发布类库 - 2\n"},{"id":83,"href":"/go/go-publish-package-02/","title":"Go Publish Package 02","section":"Go","content":" 🏠 首页 / Golang 编程 / Golang 发布类库 - 2\nGolang 发布类库 - 2 # 本页介绍如何在 Github 上升级我们已发布的 Golang 类库。\nGo 类库版本规则 # go 类库版本的规则:主版本号.次版本号.修订号,其中:\n主版本号:类库进行了不可向下兼容的修改,例如功能重构,这时候主版本号往上追加; 次版本号:类库进行了可向下兼容的修改,例如新增功能,这时候次版本号往上追加; 修订号:类库进行了可向下兼容的修改(修改的规模更小),例如修复或优化功能,这时候修订好往上追加。 Go 类库发版示例 # 同样以 github.com/poneding/common-go 类库为示例。\n小版本升级 # 主版本不升级,次版本或修订版本升级。\nv0.x.x 版本升级至 v1.x.x 也是可以直接升级的。\n当前版本是 v1.0.0,现对该类库进行了功能修改,发布 v1.0.1 版本:\n1、切换至 v1 分支:\ngit checkout v1 2、修改类库代码:\nhello/hello.go:\npackage hello import \u0026#34;fmt\u0026#34; func Say(name string) { fmt.Printf(\u0026#34;Hello, %s\\n\u0026#34;, name) fmt.Println(\u0026#34;common-go version: v1.0.1\u0026#34;) } 3、提交代码并发布:\ngit add . git commit -m \u0026#34;update hello\u0026#34; git push git tag v1.0.1 git push --tags 4、使用 demo-go 测试,升级版本:\n升级类库方式:\n使用 go get -u github.com/poneding/common-go 或 go get github.com/poneding/common-go@latest 升级至该主版本号下最新版本; 使用 go get github.com/poneding/common-go@v1.0.0 升级至指定版本。 $ go get - u github.com/poneding/common-go go: downloading github.com/poneding/common-go v1.0.1 go: found github.com/poneding/common-go/hello in github.com/poneding/common-go v1.0.1 go: github.com/poneding/common-go upgrade =\u0026gt; v1.0.1 查看 demo-go 下的 go.mod 文件,确实升级到了新版本:\ngo.mod:\nmodule demo-go go 1.16 require github.com/poneding/common-go v1.0.1 5、运行测试:\n$ go run main.go Hello, Jay common-go version: v1.0.1 大版本升级 # 主版本升级。\n值得注意的是,使用 go get -u github.com/poneding/common-go 升级类库版本时,无法跨主版本升级,只能升级至当前主版本下最新版本;\nv0.x.x 升级至 v1.x.x 是个例外,可以直接使用 go get -u github.com/poneding/common-go 命令升级。\n当前版本是 v1.0.1,现对该类库进行了功能重构,发布 v2.0.0 版本:\n1、继续按照最佳实践,创建 v2 版本的分支:\ngit checkout -b v2 2、修改 module 名称至新版:\ngo mod edit -module github.com/poneding/common-go/v2 3、v2 版本重构功能:\nhello/hello.go:\npackage hello import \u0026#34;fmt\u0026#34; func Say(firstname, lastname string) { fmt.Printf(\u0026#34;Hello, %s %s\\n\u0026#34;, firstname, lastname) fmt.Println(\u0026#34;common-go version: v2.0.0\u0026#34;) } 4、提交代码并发布:\ngit add . git commit -m \u0026#34;refacte hello\u0026#34; git push -u origin v2 git tag v2.0.0 git push --tags 5、使用 demo-go 测试,升级版本:\n注意,此时无法通过 go get -u github.com/poneding/common-go 升级至不同于当前主版本的最新版本,需要使用 go get github.com/poneding/common-go/v2 升级:\ngo get github.com/poneding/common-go/v2 查看 go.mod 文件,已经添加了 v2 版本依赖包\ngo.mod:\nmodule demo-go go 1.16 require github.com/poneding/common-go v1.0.1 require github.com/poneding/common-go/v2 v2.0.0 6、修改测试代码:\n同时使用 v1 版本和 v2 版本的包函数:\nmain.go:\npackage main import ( \u0026#34;github.com/poneding/common-go/hello\u0026#34; hellov2 \u0026#34;github.com/poneding/common-go/v2/hello\u0026#34; ) func main() { hello.Say(\u0026#34;Jay\u0026#34;) hellov2.Say(\u0026#34;Jay Chou\u0026#34;) } 8、运行测试:\n$ go run main.go test hello: Hello, Jay common-go version: v1.0.1 Hello, Jay Chou common-go version: v2.0.0 使用本地 go 类库 # 如果本地的 go 类库暂未维护到远端,如何引用本地类库的包呢?\n在 go.mod 文件中使用 replace 引用本地 go 类库,这个方式有时候更方便于开发。\ncommon-go 的 module 名称为 github.com/poneding/common-go\nreplace 使用 go 类库相对路径替换 module 的引用\n以下示例将 go 类库的引用切换为本地引用。\ngo.mod:\nmodule demo-go go 1.16 require ( github.com/poneding/common-go v1.0.1 github.com/poneding/common-go/v2 v2.0.0 ) replace ( github.com/poneding/common-go/v2 =\u0026gt; ../common-go ) 由于是本地引用,版本号只需在主版本号的范围内即可。\n结束语 # 主版本升级会给代码的维护和版本的维护增加难度,并且需要下游用户迁移版本。最好是当存在令人信服的原因时才对类库主版本进行升级,例如为了优化代码大规模重构。\n« Golang 发布类库 - 1\n» Go 程序 SOLID 设计原则\n"},{"id":84,"href":"/go/go-solid/","title":"Go Solid","section":"Go","content":" 🏠 首页 / Golang 编程 / Go 程序 SOLID 设计原则\nGo 程序 SOLID 设计原则 # 可重用软件设计的五个原则,SOLID 原则:\n单一职责原则(Single Responsibility Principle) 开放 / 封闭原则(Open / Closed Principle) 里氏替换原则(Liskov Substitution Principle) 接口隔离原则(Interface Segregation Principle) 依赖倒置原则(Dependency Inversion Principle) 单一职责原则 # SOLID 的第一个原则,S,是单一责任原则。\nA class should have one, and only one, reason to change. – Robert C Martin\n现在 Go 显然没有 classses - 相反,我们有更强大的组合概念 - 但是如果你能回顾一下 class 这个词的用法,我认为此时会有一定价值。\n为什么一段代码只有一个改变的原因很重要?嗯,就像你自己的代码可能会改变一样令人沮丧,发现您的代码所依赖的代码在您脚下发生变化更痛苦。当你的代码必须改变时,它应该响应直接刺激作出改变,而不应该成为附带损害的受害者。\n因此,具有单一责任的代码修改的原因最少。\n耦合和内聚 # 描述改变一个软件是多么容易或困难的两个词是:耦合和内聚。\n耦合只是一个词,描述了两个一起变化的东西 —— 一个运动诱导另一个运动。 一个相关但独立的概念是内聚,一种相互吸引的力量。 在软件上下文中,内聚是描述代码片段之间自然相互吸引的特性。\n为了描述 Go 程序中耦合和内聚的单元,我们可能会将谈谈函数和方法,这在讨论 SRP 时很常见,但是我相信它始于 Go 的 package 模型。\n库名称的设计 # 在 Go 中,所有的代码都在某个 package 中,一个设计良好的 package 从其名称开始。包的名称既是其用途的描述,也是名称空间前缀。Go 标准库中的一些优秀 package 示例:\nnet/http - 提供 http 客户端和服务端 os/exec - 执行外部命令 encoding/json - 实现 JSON 文档的编码和解码 当你在自己的内部使用另一个 pakcage 的 symbols 时,要使用 import 声明,它在两个 package 之间建立一个源代码级的耦合。 他们现在彼此知道对方的存在。 糟糕的库名称 # 这种对名字的关注可不是迂腐。命名不佳的 package 如果真的有用途,会失去罗列其用途的机会。\nserver package 提供什么? …, 嗯,希望是服务端,但是它使用哪种协议? private package 提供什么?我不应该看到的东西?它应该有公共符号吗? common package,和它的伴儿 utils package 一样,经常被发现和其他’伙伴’一起发现 我们看到所有像这样的包裹,就成了各种各样的垃圾场,因为它们有许多责任,所以经常毫无理由地改变。 Unix 设计理念 # 在我看来,如果不提及 Doug McIlroy 的 Unix 哲学,任何关于解耦设计的讨论都将是不完整的;小而锋利的工具结合起来,解决更大的任务,通常是原始作者无法想象的任务。\n我认为 Go package 体现了 Unix 哲学的精神。实际上,每个 Go package 本身就是一个小的 Go 程序,一个单一的变更单元,具有单一的责任。\n开放 / 封闭原则 # 第二个原则,即 O,是 Bertrand Meyer 的开放 / 封闭原则,他在 1988 年写道:\nSoftware entities should be open for extension, but closed for modification. – Bertrand Meyer, Object-Oriented Software Construction\n该建议如何适用于 21 年后写的语言?\npackage main type A struct { year int } func (a A) Greet() { fmt.Println(\u0026#34;Hello GolangUK\u0026#34;, a.year) } type B struct { A } func (b B) Greet() { fmt.Println(\u0026#34;Welcome to GolangUK\u0026#34;, b.year) } func main() { var a A a.year = 2016 var b B b.year = 2016 a.Greet() // Hello GolangUK 2016 b.Greet() // Welcome to GolangUK 2016 } 我们有一个类型 A ,有一个字段 year 和一个方法 Greet。我们有第二种类型,B 它嵌入了一个 A,因为 A 嵌入,因此调用者看到 B 的方法覆盖了 A 的方法。因为 A 作为字段嵌入 B ,B 可以提供自己的 Greet 方法,掩盖了 A 的 Greet 方法。\n但嵌入不仅适用于方法,还可以访问嵌入类型的字段。如您所见,因为 A 和 B 都在同一个包中定义,所以 B 可以访问 A 的私有 year 字段,就像在 B 中声明一样。\n因此嵌入是一个强大的工具,允许 Go 的类型对扩展开放。\npackage main type Cat struct { Name string } func (c Cat) Legs() int { return 4 } func (c Cat) PrintLegs() { fmt.Printf(\u0026#34;I have %d legs.\u0026#34;, c.Legs()) } type OctoCat struct { Cat } func (o OctoCat) Legs() int { return 5 } func main() { var octo OctoCat fmt.Println(octo.Legs()) // 5 octo.PrintLegs() // I have 4 legs } 在这个例子中,我们有一个 Cat 类型,可以用它的 Legs 方法计算它的腿数。我们将 Cat 类型嵌入到一个新类型 OctoCat 中,并声明 Octocats 有五条腿。但是,虽然 OctoCat 定义了自己的 Legs 方法,该方法返回 5,但是当调用 PrintLegs 方法时,它返回 4。\n这是因为 PrintLegs 是在 Cat 类型上定义的。 它需要 Cat 作为它的接收器,因此它会发送到 Cat 的 Legs 方法。Cat 不知道它嵌入的类型,因此嵌入时不能改变其方法集。\n因此,我们可以说 Go 的类型虽然对扩展开放,但对修改是封闭的。\n事实上,Go 中的方法只不过是围绕在具有预先声明形式参数(即接收器)的函数的语法糖。\nfunc (c Cat) PrintLegs() { fmt.Printf(\u0026#34;I have %d legs.\u0026#34;, c.Legs()) } func PrintLegs(c Cat) { fmt.Printf(\u0026#34;I have %d legs.\u0026#34;, c.Legs()) } 接收器正是你传入它的函数,函数的第一个参数,并且因为 Go 不支持函数重载,OctoCat 不能替代普通的 Cat 。 这让我想到了下一个原则。\n里氏替换原则 # 由 Barbara Liskov 提出的里氏替换原则粗略地指出,如果两种类型表现出的行为使得调用者无法区分,则这两种类型是可替代的。\n在基于类的语言中,里氏替换原则通常被解释为,具有各种具体子类型的抽象基类的规范。 但是 Go 没有类或继承,因此无法根据抽象类层次结构实现替换。\nInterfaces # 相反,替换是 Go 接口的范围。在 Go 中,类型不需要指定它们实现特定接口,而是任何类型实现接口,只要它具有签名与接口声明匹配的方法。\n我们说在 Go 中,接口是隐式地而不是显式地满足的,这对它们在语言中的使用方式产生了深远的影响。\n设计良好的接口更可能是小型接口;流行的做法是一个接口只包含一个方法。从逻辑上讲,小接口使实现变得简单,反之则很难。因此形成了由普通行为的简单实现组成的 package。\nio.Reader # type Reader interface { // Read reads up to len(buf) bytes into buf. Read(buf []byte) (n int, err error) } 这令我很容易想到了我最喜欢的 Go 接口 io.Reader。\nio.Reader 接口非常简单; Read 将数据读入提供的缓冲区,并将读取的字节数和读取期间遇到的任何错误返回给调用者。看起来很简单,但非常强大。\n因为 io.Reader 可以处理任何表示为字节流的东西,所以我们几乎可以在任何东西上创建 Reader; 常量字符串,字节数组,标准输入,网络流,gzip 的 tar 文件,通过 ssh 远程执行的命令的标准输出。\n并且所有这些实现都可以互相替代,因为它们实现了相同的简单契约。\n因此,适用于 Go 的里氏替换原则,可以通过已故 Jim Weirich 的格言来概括。\nRequire no more, promise no less. – Jim Weirich\n接口隔离原则 # 第四个原则是接口隔离原则,其内容如下:\nClients should not be forced to depend on methods they do not use. –Robert C. Martin\n在 Go 中,接口隔离原则的应用可以指的是,隔离功能完成其工作所需的行为的过程。举一个具体的例子,假设我已经完成了‘编写一个将 Document 结构保存到磁盘的函数’的任务。\n// Save writes the contents of doc to the file f. func Save(f *os.File, doc *Document) error 我可以定义此函数,让我们称之为 Save,它将给定的 Document 写入到 *os.File。 但是这样做会有一些问题。\nSave 的签名排除了将数据写入网络位置的选项。假设网络存储可能以后成为需求,此功能的签名必须改变,并影响其所有调用者。\n由于 Save 直接操作磁盘上的文件,因此测试起来很不方便。要验证其操作,测试必须在写入后读取文件的内容。 此外,测试必须确保将 f 写入临时位置并随后将其删除。\n*os.File 还定义了许多与 Save 无关的方法,比如读取目录并检查路径是否是文件链接。 如果 Save 函数的签名能只描述 *os.File 相关的部分,将会很实用。\n我们如何处理这些问题呢?\n// Save writes the contents of doc to the supplied ReadWriterCloser. func Save(rwc io.ReadWriteCloser, doc *Document) error 使用 io.ReadWriteCloser 我们可以应用接口隔离原则,使用更通用的文件类型的接口来重新定义 Save。\n通过此更改,任何实现了 io.ReadWriteCloser 接口的类型都可以代替之前的 *os.File。使得 Save 应用程序更广泛,并向 Save 调用者阐明,*os.File 类型的哪些方法与操作相关。\n做为 Save 的编写者,我不再可以选择调用 *os.File 的那些不相关的方法,因为它隐藏在 io.ReadWriteCloser 接口背后。我们可以进一步采用接口隔离原理。\n首先,如果 Save 遵循单一责任原则,它将不可能读取它刚刚编写的文件来验证其内容 - 这应该是另一段代码的责任。因此,我们可以将我们传递给 Save 的接口的规范缩小,仅写入和关闭。\n// Save writes the contents of doc to the supplied WriteCloser. func Save(wc io.WriteCloser, doc *Document) error 其次,通过向 Save 提供一个关闭其流的机制,我们继续这种机制以使其看起来像文件类型的东西,这就产生一个问题,wc 会在什么情况下关闭。Save 可能会无条件地调用 Close,抑或在成功的情况下调用 Close。\n这给 Save 的调用者带来了问题,因为它可能希望在写入文档之后将其他数据写入流。\ntype NopCloser struct { io.Writer } // Close has no effect on the underlying writer. func (c *NopCloser) Close() error { return nil } 一个粗略的解决方案是定义一个新类型,它嵌入一个 io.Writer 并覆盖 Close 方法,以阻止 Save 方法关闭底层数据流。\n但这样可能会违反里氏替换原则,因为 NopCloser 实际上并没有关闭任何东西。\n// Save writes the contents of doc to the supplied Writer. func Save(w io.Writer, doc *Document) error 一个更好的解决方案是重新定义 Save 只接收 io.Writer,完全剥离它除了将数据写入流之外做任何事情的责任。\n通过应用接口隔离原则,我们的 Save 功能,同时得到了一个在需求方面最具体的函数 - 它只需要一个可写的参数 - 并且具有最通用的功能,现在我们可以使用 Save 保存我们的数据到任何一个实现 io.Writer 的地方。\nA great rule of thumb for Go is accept interfaces, return structs. – Jack Lindamood\n退一步说,这句话是一个有趣的模因,在过去的几年里,它渗透入 Go 思潮。\n这个推特大小的版本缺乏细节,这不是 Jack 的错,但我认为它代表了第一个正当有理的 Go 设计传统\n依赖倒置原则 # 最后一个 SOLID 原则是依赖倒置原则,该原则指出:\nHigh-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions. – Robert C. Martin\n但是,对于 Go 程序员来说,依赖倒置在实践中意味着什么呢?\n如果您已经应用了我们之前谈到的所有原则,那么您的代码应该已经被分解为离散包,每个包都有一个明确定义的责任或目的。您的代码应该根据接口描述其依赖关系,并且应该考虑这些接口以仅描述这些函数所需的行为。 换句话说,除此之外没什么应该要做的。\n所以我认为,在 Go 的上下文中,Martin 所指的是 import graph 的结构。\n在 Go 中,import graph 必须是非循环的。 不遵守这种非循环要求将导致编译失败,但更为严重地是它代表设计中存在严重错误。\n在所有条件相同的情况下,精心设计的 Go 程序的 import graph 应该是宽的,相对平坦的,而不是高而窄的。 如果你有一个 package,其函数无法在不借助另一个 package 的情况下运行,那么这或许表明代码没有很好地沿 pakcage 边界分解。\n依赖倒置原则鼓励您将特定的责任,沿着 import graph 尽可能的推向更高层级,推给 main package 或顶级处理程序,留下较低级别的代码来处理抽象接口。\n总结 # 回顾一下,当应用于 Go 时,每个 SOLID 原则都是关于设计的强有力陈述,但综合起来它们具有中心主题。\n单一职责原则,鼓励您将功能,类型、方法结构化为具有自然内聚的包;类型属于彼此,函数服务于单一目的。 开放 / 封闭原则,鼓励您使用嵌入将简单类型组合成更复杂的类型。 里氏替换原则,鼓励您根据接口而不是具体类型来表达包之间的依赖关系。通过定义小型接口,我们可以更加确信,实现将忠实地满足他们的契约。 接口隔离原则,进一步采用了这个想法,并鼓励您定义仅依赖于他们所需行为的函数和方法。如果您的函数仅需要具有单个接口类型的参数的方法,则该函数更可能只有一个责任。 依赖倒置原则,鼓励您按照从编译时间到运行时间的时序,转移 package 所依赖的知识。在 Go 中,我们可以通过特定 package 使用的 import 语句的数量减少看到了这一点。 如果要总结一下本次演讲,那可能就是这样:interfaces let you apply the SOLID principles to Go programs。\n因为接口让 Go 程序员描述他们的 package 提供了什么 - 而不是它怎么做的。换个说法就是 “解耦”,这确实是目标,因为越松散耦合的软件越容易修改。\n正如 Sandi Metz 所说:\nDesign is the art of arranging code that needs to work today, and to be easy to change forever. – Sandi Metz\n因为如果 Go 想要成为公司长期投资的语言,Go 程序的可维护性,更容易变更,将是他们决策的关键因素。\n« Golang 发布类库 - 2\n» Golang 标准库\n"},{"id":85,"href":"/go/go-stdlib/","title":"Go Stdlib","section":"Go","content":" 🏠 首页 / Golang 编程 / Golang 标准库\nGolang 标准库 # fmt # 格式化打印\n%v 原样输出 %T 打印类型 %t bool %s string %f float %d 10进制整数 %b 2进制整数 %o 8进制整数 %x 16进制整数 0-9,a-f %X 16进制整数 0-9,A-F %c char %p pointer %.2f float 保留两位 path # file := \u0026#34;./logs/2021-01-25/error.log\u0026#34; fileName := path.Base(file) # 返回文件名:error.log fileExt := path.Ext(file) # 返回文件后缀:.log fileDir := path.Dir(file) # 返回文件路径: ./logs/2021-01-25 os/exec # Golang语言有一个包叫做 os/exec,使用该包可以直接在程序中调用主机的命令,使用示例如下:\nfunc OsExecUsage() error { fmt.Println(\u0026#34;docker build...\u0026#34;) cmd := exec.Command(\u0026#34;docker\u0026#34;, \u0026#34;build\u0026#34;, \u0026#34;.\u0026#34; ,\u0026#34;-t\u0026#34; ,\u0026#34;demo\u0026#34;) // 实时打印输出 cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Run() if err != nil { fmt.Println(\u0026#34;cmd.Output: \u0026#34;, err) return err } return nil } « Go 程序 SOLID 设计原则\n» testing\n"},{"id":86,"href":"/go/go-testing/","title":"Go Testing","section":"Go","content":" 🏠 首页 / Golang 编程 / testing\ntesting # 命令 # 测试\ngo test -run=TestCompare -v . 运行测试,测试函数名称中仅包含 TestCompare 前缀。\n列出包内测试文件\ngo list -f={{.GoTestFiles}} . 列出包外测试文件\ngo list -f={{.XTestGoFiles}} . 包内测试:测试文件的包名称与被测包一致,可以访问被测包内所有成员,相当于白盒测试;\n包外测试:测试文件的包名称与被测包不一致,一般在被测包名称后面添加 _test 后缀,只能访问被测包内公开成员,相当于黑盒测试。\n« Golang 标准库\n» Golang\n"},{"id":87,"href":"/go/go/","title":"Go","section":"Go","content":" 🏠 首页 / Golang 编程 / Golang\nGolang # 资料 # Go 安全指南 Go 语言编程规范 Golang channel # golang 实现并发是通过通信共享内存,channel 是 go 语言中goroutine 的通信管道,通过 channel 将值从一个 goroutine 发送到另一个 goroutine。\n语法 # 创建 channel:\n使用 make() 函数创建:\nch := make(chan int) 默认创建一个无缓存 channel。\n发送数据到 channel:\nch \u0026lt;- x 从 channel 读取数据:\nx := \u0026lt;-ch 关闭 channel:\n使用 close() 函数关闭:\nclose(ch) 当你的程序不再需要往 channel 中发送数据时,可以关闭 channel。\n如果往已经关闭的 channal 发送数据,程序发生异常。\n无缓冲 channel # 如果当前没有一个 goroutine 对无缓冲 channel 接收数据,那么无缓冲 channel 会阻止发送数据。\n有缓冲 channel # 类似队列机制,创建时需要设定缓冲大小。\n创建一个有缓冲 channel:\nch := make(chan int ch, 10) 在有 goroutine 接收 channel 数据之前,可以先向 channel 中发送10个数据。\n无缓冲 channel 与有缓冲 channel的区别 # 现在,你可能想知道何时使用这两种类型。 这完全取决于你希望 goroutine 之间的通信如何进行。 无缓冲 channel 同步通信。 它们保证每次发送数据时,程序都会被阻止,直到有人从 channel 中读取数据。\n相反,有缓冲 channel 将发送和接收操作解耦。 它们不会阻止程序,但你必须小心使用,因为可能最终会导致死锁(如前文所述)。 使用无缓冲 channel 时,可以控制可并发运行的 goroutine 的数量。 例如,你可能要对 API 进行调用,并且想要控制每秒执行的调用次数。 否则,你可能会被阻止。\n读取文件 # 一次性全读 # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;io/ioutil\u0026#34; ) func main() { bytes, e := ioutil.ReadFile(\u0026#34;/Users/dp/tmp/0811/03/file.txt\u0026#34;) if e != nil { panic(\u0026#34;read file error.\u0026#34;) } fmt.Println(string(bytes)) } 按行读取 # package main import ( \u0026#34;bufio\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;io\u0026#34; \u0026#34;os\u0026#34; ) func main() { f, e := os.Open(\u0026#34;C:/Users/dp/go/src/0811/03/file.txt\u0026#34;) if e != nil { panic(\u0026#34;read file error.\u0026#34;) } defer f.Close() r := bufio.NewReader(f) for { line, _, eof := r.ReadLine() if eof == io.EOF { break // read last line. } fmt.Println(string(line)) } } 优雅退出 # 不处理优雅退出 # package main import ( \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;os\u0026#34; \u0026#34;os/signal\u0026#34; \u0026#34;time\u0026#34; \u0026#34;github.com/gin-gonic/gin\u0026#34; ) func main() { router := gin.Default() router.GET(\u0026#34;/\u0026#34;, func(c *gin.Context) { time.Sleep(5 * time.Second) c.String(http.StatusOK, \u0026#34;Welcome Gin Server\u0026#34;) }) server := \u0026amp;http.Server{ Addr: \u0026#34;:8080\u0026#34;, Handler: router, } quit := make(chan os.Signal) signal.Notify(quit, os.Interrupt) go func() { \u0026lt;-quit log.Println(\u0026#34;receive interrupt signal\u0026#34;) if err := server.Close(); err != nil { log.Fatal(\u0026#34;Server Close:\u0026#34;, err) } }() if err := server.ListenAndServe(); err != nil { if err == http.ErrServerClosed { log.Println(\u0026#34;Server closed under request\u0026#34;) } else { log.Fatal(\u0026#34;Server closed unexpect\u0026#34;) } } log.Println(\u0026#34;Server exiting\u0026#34;) } 优雅退出 1(with context) # package main import ( \u0026#34;context\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;os/signal\u0026#34; \u0026#34;syscall\u0026#34; \u0026#34;time\u0026#34; \u0026#34;github.com/gin-gonic/gin\u0026#34; ) func main() { // Create context that listens for the interrupt signal from the OS. ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() router := gin.Default() router.GET(\u0026#34;/\u0026#34;, func(c *gin.Context) { time.Sleep(10 * time.Second) c.String(http.StatusOK, \u0026#34;Welcome Gin Server\u0026#34;) }) srv := \u0026amp;http.Server{ Addr: \u0026#34;:8080\u0026#34;, Handler: router, } // Initializing the server in a goroutine so that // it won\u0026#39;t block the graceful shutdown handling below go func() { if err := srv.ListenAndServe(); err != nil \u0026amp;\u0026amp; err != http.ErrServerClosed { log.Fatalf(\u0026#34;listen: %s\\n\u0026#34;, err) } }() // Listen for the interrupt signal. \u0026lt;-ctx.Done() // Restore default behavior on the interrupt signal and notify user of shutdown. stop() log.Println(\u0026#34;shutting down gracefully, press Ctrl+C again to force\u0026#34;) // The context is used to inform the server it has 5 seconds to finish // the request it is currently handling ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatal(\u0026#34;Server forced to shutdown: \u0026#34;, err) } log.Println(\u0026#34;Server exiting\u0026#34;) } 优雅退出 2(without context) # package main import ( \u0026#34;context\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;os\u0026#34; \u0026#34;os/signal\u0026#34; \u0026#34;syscall\u0026#34; \u0026#34;time\u0026#34; \u0026#34;github.com/gin-gonic/gin\u0026#34; ) func main() { router := gin.Default() router.GET(\u0026#34;/\u0026#34;, func(c *gin.Context) { time.Sleep(5 * time.Second) c.String(http.StatusOK, \u0026#34;Welcome Gin Server\u0026#34;) }) srv := \u0026amp;http.Server{ Addr: \u0026#34;:8080\u0026#34;, Handler: router, } // Initializing the server in a goroutine so that // it won\u0026#39;t block the graceful shutdown handling below go func() { if err := srv.ListenAndServe(); err != nil \u0026amp;\u0026amp; err != http.ErrServerClosed { log.Fatalf(\u0026#34;listen: %s\\n\u0026#34;, err) } }() // Wait for interrupt signal to gracefully shutdown the server with // a timeout of 5 seconds. quit := make(chan os.Signal, 1) // kill (no param) default send syscall.SIGTERM // kill -2 is syscall.SIGINT // kill -9 is syscall.SIGKILL but can\u0026#39;t be catch, so don\u0026#39;t need add it signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) \u0026lt;-quit log.Println(\u0026#34;Shutting down server...\u0026#34;) // The context is used to inform the server it has 5 seconds to finish // the request it is currently handling ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatal(\u0026#34;Server forced to shutdown: \u0026#34;, err) } log.Println(\u0026#34;Server exiting\u0026#34;) } 函数式选项模式 # 函数式选项模式:Functional Options Pattern,是 Golang 中一种常用的设计模式,用于解决构造函数参数过多的问题。\n示例:\npackage main func main() { NewServer(\u0026#34;test\u0026#34;, SetHost(\u0026#34;localhost\u0026#34;), SetPort(8081)) } type Server struct { Label string Host string Port int32 } func NewServer(label string, opts ...func(s *Server)) *Server { s := \u0026amp;Server{ Label: label, } for _, opt := range opts { opt(s) } return s } type Option func(s *Server) func SetHost(host string) Option { return func(s *Server) { s.Host = host } } func SetPort(port int32) Option { return func(s *Server) { s.Port = port } } 调用 API # 使用 Golang 调用 API 代码示例:\nGet # func GetApi() { resp, err := http.Get(\u0026#34;https://baidu.com\u0026#34;) if err != nil { fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } defer resp.Body.Close() fmt.Printf(\u0026#34;Status: %s\\n\u0026#34;, resp.Status) fmt.Printf(\u0026#34;StatusCode: %d\\n\u0026#34;, resp.StatusCode) body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } fmt.Printf(\u0026#34;StatusCode: %s\\n\u0026#34;, string(body)) } Post # 1 简单 Post:\nfunc PostApi1() { json := `{\u0026#34;id\u0026#34;:\u0026#34;u-001\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;Jay\u0026#34;,\u0026#34;age\u0026#34;:18}` resp, err := http.Post(\u0026#34;https://example.com/user\u0026#34;, \u0026#34;application/json\u0026#34;, bytes.NewBuffer([]byte(json))) defer resp.Body.Close() fmt.Printf(\u0026#34;Status: %s\\n\u0026#34;, resp.Status) fmt.Printf(\u0026#34;StatusCode: %d\\n\u0026#34;, resp.StatusCode) body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } fmt.Printf(\u0026#34;StatusCode: %s\\n\u0026#34;, string(body)) } 2 附带 Header:\nfunc PostApi2() { // 1. json //json := `{\u0026#34;id\u0026#34;:\u0026#34;u-001\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;Jay\u0026#34;,\u0026#34;age\u0026#34;:18}` //req, _ := http.NewRequest(http.MethodPost, \u0026#34;https://example.com/user\u0026#34;, bytes.NewBuffer([]byte(json))) // 2. map reqBody, _ := json.Marshal(map[string]string{ \u0026#34;id\u0026#34;: \u0026#34;u-001\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Jay\u0026#34;, \u0026#34;age\u0026#34;: \u0026#34;18\u0026#34;, }) req, _ := http.NewRequest(http.MethodPost, \u0026#34;https://example.com/user\u0026#34;, bytes.NewBuffer(reqBody)) req.Header.Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) req.Header.Set(\u0026#34;My_Custom_Header\u0026#34;, \u0026#34;Value\u0026#34;) client := http.Client{} resp, err := client.Do(req) if err != nil { fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } defer resp.Body.Close() fmt.Printf(\u0026#34;Status: %s\\n\u0026#34;, resp.Status) fmt.Printf(\u0026#34;StatusCode: %d\\n\u0026#34;, resp.StatusCode) body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } fmt.Printf(\u0026#34;StatusCode: %s\\n\u0026#34;, string(body)) } 3 构造请求对象:\ntype User struct { Id string `json:\u0026#34;id\u0026#34;` Name string `json:\u0026#34;name\u0026#34;` Age int `json:\u0026#34;age\u0026#34;` } func PostApi3() { user := User{ Id: \u0026#34;u-001\u0026#34;, Name: \u0026#34;Jay\u0026#34;, Age: 18, } buf := new(bytes.Buffer) json.NewEncoder(buf).Encode(user) resp, err := http.Post(\u0026#34;https://example.com/user\u0026#34;, \u0026#34;application/json\u0026#34;, buf) defer resp.Body.Close() fmt.Printf(\u0026#34;Status: %s\\n\u0026#34;, resp.Status) fmt.Printf(\u0026#34;StatusCode: %d\\n\u0026#34;, resp.StatusCode) body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } fmt.Printf(\u0026#34;StatusCode: %s\\n\u0026#34;, string(body)) } 4 OAuth2:\n// go get golang.org/x/oauth2 func getAccessToken()string{ var authCfg = \u0026amp;clientcredentials.Config{ ClientID: \u0026#34;xxx\u0026#34;, ClientSecret: \u0026#34;xxx\u0026#34;, TokenURL: \u0026#34;https://example.com/connect/token\u0026#34;, EndpointParams: url.Values{ \u0026#34;grant_type\u0026#34;: {\u0026#34;client_credentials\u0026#34;}, }, } token, err := authCfg.TokenSource(context.Background()).Token() if err != nil { fmt.Errorf(\u0026#34;get access token failed. ERROR: %s\\n\u0026#34;, err.Error()) } return token.AccessToken } func OAuth2Api(){ json := `{\u0026#34;id\u0026#34;:\u0026#34;u-001\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;Jay\u0026#34;,\u0026#34;age\u0026#34;:18}` req, _ := http.NewRequest(http.MethodPost, \u0026#34;https://example.com/user\u0026#34;, bytes.NewBuffer([]byte(json))) req.Header.Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) req.Header.Set(\u0026#34;Bearer\u0026#34;, getAccessToken()) client := http.Client{} resp, err := client.Do(req) if err != nil { fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } defer resp.Body.Close() fmt.Printf(\u0026#34;Status: %s\\n\u0026#34;, resp.Status) fmt.Printf(\u0026#34;StatusCode: %d\\n\u0026#34;, resp.StatusCode) body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } fmt.Printf(\u0026#34;StatusCode: %s\\n\u0026#34;, string(body)) } 5 上传文件:\nfunc FileApi(){ file,err:=os.Open(\u0026#34;hello.txt\u0026#34;) if err!=nil{ fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } defer file.Close() var reqBody bytes.Buffer multiPartWriter:=multipart.NewWriter(\u0026amp;reqBody) fileWriter,err:=multiPartWriter.CreateFormFile(\u0026#34;file_field\u0026#34;,\u0026#34;hello.txt\u0026#34;) if err!=nil{ fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } _,err=io.Copy(fileWriter,file) if err!=nil{ fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } fieldWriter,err:=multiPartWriter.CreateFormField(\u0026#34;normal_field\u0026#34;) if err!=nil{ fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } _,err=fieldWriter.Write([]byte(\u0026#34;value\u0026#34;)) if err!=nil{ fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } multiPartWriter.Close() req,err:=http.NewRequest(http.MethodPost,\u0026#34;http://example.com/file\u0026#34;,\u0026amp;reqBody) if err!=nil{ fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } req.Header.Set(\u0026#34;Content-Type\u0026#34;,multiPartWriter.FormDataContentType()) client:=http.Client{} resp,err:=client.Do(req) if err!=nil{ fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } defer resp.Body.Close() fmt.Printf(\u0026#34;Status: %s\\n\u0026#34;, resp.Status) fmt.Printf(\u0026#34;StatusCode: %d\\n\u0026#34;, resp.StatusCode) body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } fmt.Printf(\u0026#34;StatusCode: %s\\n\u0026#34;, string(body)) } 调用 Jenkins API # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;strings\u0026#34; ) type JenkinsConfig struct { User string `json:\u0026#34;user\u0026#34;` Token string `json:\u0026#34;token\u0026#34;` } var jenkinsConf *JenkinsConfig func main() { jenkinsConf := \u0026amp;JenkinsConfig{ User: \u0026#34;xxx\u0026#34;, Token: \u0026#34;xxxxxx\u0026#34; } job := \u0026#34;jenkins-job-1\u0026#34; disableJenkins(job) } func disableJenkins(job string) { // disabled jenkins job r, err := http.NewRequest(http.MethodPost, fmt.Sprintf(\u0026#34;https://jenkins.example.com/job/%s/disable\u0026#34;, job), nil) if err != nil { log.Errorf(\u0026#34;new jenkins request failed: %s\u0026#34;, err) } r.SetBasicAuth(jenkinsConf.User, jenkinsConf.Token) client := \u0026amp;http.Client{} resp, err := client.Do(r) if err != nil { log.Errorf(\u0026#34;request failed: %s\u0026#34;, err) } if resp.StatusCode == http.StatusOK { log.Healthf(\u0026#34;disable jenkins job [%s] successfully\u0026#34;, job) } else { log.Warnf(\u0026#34;disable jenkins job [%s] status [%d]\u0026#34;, job, resp.StatusCode) } } 位运算 # package main import \u0026#34;fmt\u0026#34; func main() { /* 位运算符: 将数值,转为二进制后,按位操作 按位\u0026amp;: 对应位的值如果都为 1 才为 1,有一个为 0 就为 0 按位|: 对应位的值如果都是 0 才为 0,有一个为 1 就为 1 异或^: 二元:a^b 对应位的值不同为 1,相同为 0 一元:^a 按位取反: 1---\u0026gt;0 0---\u0026gt;1 位清空:\u0026amp;^ 对于 a \u0026amp;^ b 对于 b 上的每个数值 如果为 0,则取 a 对应位上的数值 如果为 1,则结果位就取 0 位移运算符: \u0026lt;\u0026lt;:按位左移,将 a 转为二进制,向左移动 b 位 a \u0026lt;\u0026lt; b \u0026gt;\u0026gt;: 按位右移,将 a 转为二进制,向右移动 b 位 a \u0026gt;\u0026gt; b */ a := 60 b := 13 /* a: 60 0011 1100 b: 13 0000 1101 \u0026amp; 0000 1100 | 0011 1101 ^ 0011 0001 \u0026amp;^ 0011 0000 a : 0000 0000 ... 0011 1100 ^ 1111 1111 ... 1100 0011 */ fmt.Printf(\u0026#34;a:%d, %b\\n\u0026#34;,a,a) fmt.Printf(\u0026#34;b:%d, %b\\n\u0026#34;,b,b) res1 := a \u0026amp; b fmt.Println(res1) // 12 res2 := a | b fmt.Println(res2) // 61 res3 := a ^ b fmt.Println(res3) // 49 res4 := a \u0026amp;^ b fmt.Println(res4) // 48 res5 := ^a fmt.Println(res5) c:=8 /* c : ... 0000 1000 0000 100000 \u0026gt;\u0026gt; 0000 10 */ res6 := c \u0026lt;\u0026lt; 2 fmt.Println(res6) res7 := c \u0026gt;\u0026gt; 2 fmt.Println(res7) } 实现简易网络连接客户端 # 实现类似 nc 的客户端命令工具:\n$ nc www.baidu.com 80 GET / HTTP/1.1 注意:需要连续两次回车,才会连接\n废话少说,上代码:\nmain.go\npackage main import ( \u0026#34;io\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net\u0026#34; \u0026#34;os\u0026#34; ) func main() { if len(os.Args) != 3 { log.Fatalln(\u0026#34;Usage: nc [host] [port]\u0026#34;) } host, port := os.Args[1], os.Args[2] c, err := net.Dial(\u0026#34;tcp\u0026#34;, host+\u0026#34;:\u0026#34;+port) if err != nil { log.Fatalln(err) } go func() { io.Copy(c, os.Stdin) }() io.Copy(os.Stdout, c) } 测试:\n$ go build -o nc main.go $ ./nc www.baidu.com 80 GET / HTTP/1.1 git # package gitutil import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/go-git/go-git/v5\u0026#34; \u0026#34;github.com/go-git/go-git/v5/plumbing\u0026#34; \u0026#34;github.com/go-git/go-git/v5/plumbing/transport/http\u0026#34; \u0026#34;os\u0026#34; ) func Clone() { _, err := git.PlainClone(\u0026#34;/var/app/ash\u0026#34;, false, \u0026amp;git.CloneOptions{ URL: \u0026#34;https://github.com/poneding/ash.git\u0026#34;, ReferenceName: plumbing.NewBranchReferenceName(\u0026#34;master\u0026#34;), Auth: \u0026amp;http.BasicAuth{ Username: \u0026#34;poneding\u0026#34;, Password: \u0026#34;xxxxxx\u0026#34;, }, Progress: os.Stdout, }) if err != nil { fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } } elasticsearch # package esutil import ( \u0026#34;bytes\u0026#34; \u0026#34;encoding/json\u0026#34; \u0026#34;errors\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;github.com/elastic/go-elasticsearch/v7\u0026#34; \u0026#34;strings\u0026#34; ) func NewESClient2(esAddress []string) (*elasticsearch.Client, error) { if len(esAddress) == 0 { panic(\u0026#34;Invalid parameters: esAddress.\u0026#34;) } esConfig := elasticsearch.Config{Addresses: esAddress} esClient, err := elasticsearch.NewClient(esConfig) if err != nil { fmt.Errorf(\u0026#34;GetESClient ERROR: %s\\n\u0026#34;, err) panic(err) } return esClient, nil } func QueryLogs2(esClient *elasticsearch.Client, query QueryLogModel) ([]LogModel, error) { var ( buf bytes.Buffer r map[string]interface{} ) index := query.App + \u0026#34;-*\u0026#34; q := map[string]interface{}{ \u0026#34;query\u0026#34;: map[string]interface{}{ \u0026#34;match_phrase\u0026#34;: map[string]interface{}{ \u0026#34;kubernetes.labels.app\u0026#34;: query.App, }, }, } if err := json.NewEncoder(\u0026amp;buf).Encode(q); err != nil { fmt.Errorf(\u0026#34;QueryLogs ERROR: %s\\n\u0026#34;, err) return nil, err } searchRes, err := esClient.Search( esClient.Search.WithIndex(index), esClient.Search.WithBody(\u0026amp;buf), esClient.Search.WithQuery(query.Filter), esClient.Search.WithFilterPath(\u0026#34;hits.hits\u0026#34;), esClient.Search.WithSize(query.Size), esClient.Search.WithSort(\u0026#34;@timestamp:desc\u0026#34;), esClient.Search.WithSource(\u0026#34;@timestamp\u0026#34;,\u0026#34;level\u0026#34;,\u0026#34;log\u0026#34;,\u0026#34;msg\u0026#34;), ) defer searchRes.Body.Close() if err != nil || searchRes.IsError() { fmt.Errorf(\u0026#34;QueryLogs ERROR: %s\\n\u0026#34;, err.Error()) return nil, errors.New(strings.Join([]string{\u0026#34;es.Search ERROR:\u0026#34;, searchRes.Status(), err.Error()}, \u0026#34; \u0026#34;)) } if err := json.NewDecoder(searchRes.Body).Decode(\u0026amp;r); err != nil { fmt.Errorf(\u0026#34;QueryLogs ERROR: %s\\n\u0026#34;, err.Error()) return nil, err } res := make([]LogModel, 0) for _, hit := range r[\u0026#34;hits\u0026#34;].(map[string]interface{})[\u0026#34;hits\u0026#34;].([]interface{}) { source := hit.(map[string]interface{})[\u0026#34;_source\u0026#34;].(map[string]interface{}) logModel := LogModel{ Timestamp: source[\u0026#34;@timestamp\u0026#34;].(string), Log: source[\u0026#34;log\u0026#34;].(string), } level, ok := source[\u0026#34;level\u0026#34;] if ok { logModel.Level = level.(string) } log, ok := source[\u0026#34;msg\u0026#34;] if ok { logModel.Log = log.(string) } res = append(res, logModel) } return res, nil } package es import ( \u0026#34;context\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;github.com/olivere/elastic/v7\u0026#34; \u0026#34;reflect\u0026#34; \u0026#34;time\u0026#34; ) func NewESClient(esAddresses []string) (*elastic.Client, error) { client, err := elastic.NewClient(elastic.SetSniff(false), elastic.SetURL(esAddresses...)) if err != nil { fmt.Errorf(\u0026#34;NewESClient ERROR: %s\\n\u0026#34;, err) panic(err) } return client, nil } func QueryLogs(esClient *elastic.Client, queryModel QueryLogModel) ([]LogModel, error) { res := make([]LogModel, 0) boolQuery := elastic.NewBoolQuery() index := queryModel.App + \u0026#34;-*\u0026#34; query := esClient.Search(index).FilterPath(\u0026#34;hits.hits\u0026#34;).Sort(\u0026#34;@timestamp\u0026#34;, false) if len(queryModel.Filter) \u0026gt; 0 { boolQuery.Filter(elastic.NewQueryStringQuery(queryModel.Filter)) } if len(queryModel.Level) \u0026gt; 0 { boolQuery.Filter(elastic.NewMatchPhraseQuery(\u0026#34;level\u0026#34;, queryModel.Level)) } if queryModel.Page \u0026lt;= 0 { queryModel.Page = 1 } if queryModel.Size \u0026lt;= 0 { queryModel.Size = 50 } query = query.From((queryModel.Page-1)*queryModel.Size + 1).Size(queryModel.Size) if queryModel.StartAt == (time.Time{}) { queryModel.StartAt = time.Now().Add(-15 * time.Minute).UTC() } if queryModel.EndAt == (time.Time{}) { queryModel.EndAt = time.Now().UTC() } boolQuery.Filter(elastic.NewRangeQuery(\u0026#34;@timestamp\u0026#34;).Gte(queryModel.StartAt).Lte(queryModel.EndAt)) query = query.Query(boolQuery) queryRes, err := query.Do(context.Background()) if err != nil { fmt.Errorf(\u0026#34;QueryLogs ERROR: %s\\n\u0026#34;, err.Error()) return res, err } for _, item := range queryRes.Each(reflect.TypeOf(LogModel{})) { if t, ok := item.(LogModel); ok { res = append(res, LogModel{ Timestamp: t.Timestamp, Level: t.Level, // Msg storing source log here. Log: t.Msg, Msg: t.Log, App: t.Kubernetes.Labels.App, }) } } return res, nil } func QueryErrorLogs(esClient *elastic.Client) error { query := esClient.Search(\u0026#34;dev-core-*\u0026#34;).FilterPath(\u0026#34;hits.hits\u0026#34;).Sort(\u0026#34;@timestamp\u0026#34;, false).Size(100).Query(elastic.NewMatchPhraseQuery(\u0026#34;level\u0026#34;, \u0026#34;error\u0026#34;)) queryRes, err := query.Do(context.Background()) if err != nil { fmt.Errorf(\u0026#34;QueryLogs ERROR: %s\\n\u0026#34;, err.Error()) } for _, item := range queryRes.Each(reflect.TypeOf(LogModel{})) { if t, ok := item.(LogModel); ok { fmt.Println(t.Log) } } return nil } « testing\n» gopkg-errors.md\n"},{"id":88,"href":"/go/gopkg-errors/","title":"Gopkg Errors","section":"Go","content":" 🏠 首页 / Golang 编程 / gopkg-errors.md\ntitle: Go 包 - errors date: 2022-10-19T14:44:06+08:00 lastmod: 2022-11-03T03:32:31.646Z tags:\nGolang gopkg keywords: Golang gopkg errors weight: 1 errors 包为你的 Go 程序提供一种对程序员调试、查看日志更友好的错误处理方式。\nGo 程序中传统的错误处理方法:\nif err != nil { return err } 递归的向上传递错误,这种方式有一个缺陷:最终处理错误的位置无法获取错误的调用上下文信息。\nerrors 包以不破坏错误的原始值的方式向错误中的添加调用上下文信息。\n获取包 # go get github.com/pkg/errors 错误添加上下文 # The errors.Wrap function returns a new error that adds context to the original error. For example\n_, err := ioutil.ReadAll(r) if err != nil { return errors.Wrap(err, \u0026#34;read failed\u0026#34;) } Retrieving the cause of an error # Using errors.Wrap constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by errors.Cause.\ntype causer interface { Cause() error } errors.Cause will recursively retrieve the topmost error which does not implement causer, which is assumed to be the original cause. For example:\nswitch err := errors.Cause(err).(type) { case *MyError: // handle specifically default: // unknown error } Read the package documentation for more information.\nRoadmap # With the upcoming Go2 error proposals this package is moving into maintenance mode. The roadmap for a 1.0 release is as follows:\n0.9. Remove pre Go 1.9 and Go 1.10 support, address outstanding pull requests (if possible) 1.0. Final release. Contributing # Because of the Go2 errors changes, this package is not accepting proposals for new functionality. With that said, we welcome pull requests, bug fixes and issue reports.\nBefore sending a PR, please discuss your change by raising an issue.\nLicense # BSD-2-Clause\n« Golang\n» Goreleaser\n"},{"id":89,"href":"/go/goreleaser/","title":"Goreleaser","section":"Go","content":" 🏠 首页 / Golang 编程 / Goreleaser\nGoreleaser # Go 程序项目的自动化发布工具,简单的发布命令帮助我们省去大量的重复工作。\n安装 # MacOs:\nbrew install goreleaser/tap/goreleaser 源码编译:\ngit clone https://github.com/goreleaser/goreleaser cd goreleaser go get ./... go build -o goreleaser . ./goreleaser --version 初始化 # 在go项目下运行以下命令,生成.goreleaser.yml文件:\ngoreleaser init 生成文件后,自行配置,相信你能看的懂。\n验证 .goreleaser.yml # goreleaser check 使用本地环境构建\ngoreleaser build --single-target 配置 github token # token 必须至少包含 write:package 权限,才能上传到发布资源中。\n从github生成token,写入文件:\nmkdir ~/.config/goreleaser vim ~/.config/goreleaser/github_token 或者直接在终端导入环境配置:\nexport GITHUB_TOKEN=\u0026#34;YOUR_GITHUB_TOKEN\u0026#34; 为项目打上 tag # git tag v0.1.0 -m \u0026#34;release v0.1.0\u0026#34; git push origin v0.1.0 执行 goreleaser # goreleaser --clean # 跳过 git 修改验证 goreleaser build --skip-validate --clean 如果目前还不想打 tag,可以基于最近的一次提交 直接使用一下 gorelease 命令:\ngoreleaser build --snapshot Dry run # 如果你想在真实的发布之前做发布测试,你可以尝试 dry run 。\nBuild only 模式 # 使用如下命令,只会编译当前项目,可以验证项目是否存储编译错误。\ngoreleaser build Release 标识 # 使用 --skip-publish 命令标识来跳过发布到远端。\ngoreleaser release --skip-publish goreleaser release --snapshot --skip-publish --clean 工作原理 # 条件:\n项目下有一个规范的 ·goreleaser.yml 文件 干净的 git 目录 符合 SemVer-compatible 的版本命名 步骤:\ndefaulting:为每个步骤配置合理的默认值 building:构建二进制文件(binaries),归档文件(archives),包文件(packages),Docker镜像等 publishing:将构建的文件发布到配置的 GitHub Release 资源,Docker Hub 等 annoucing:发布完成后,配置通知 步骤可以通过命令标识 --skip-{step_name} 来跳过,如果当中某步骤失败,之后的步骤将不会运行。\nCGO # 很遗憾,如果你的 Go 程序项目需要使用到 CGO 的跨平台编译,Docker 镜像是不支持的,并且你的配置将不会看到 clean 。\n也许这里可以帮助到你: Cross-compiling Go with CGO - GoReleaser\n« gopkg-errors.md\n» Mac M1 交叉编译 CGO\n"},{"id":90,"href":"/go/mac-appl-silicon-cross-compile-cgo/","title":"MAC Appl Silicon Cross Compile Cgo","section":"Go","content":" 🏠 首页 / Golang 编程 / Mac M1 交叉编译 CGO\nMac M1 交叉编译 CGO # 方法一 # 1、安装依赖\nbrew tap messense/macos-cross-toolchains brew install x86_64-unknown-linux-gnu brew install aarch64-unknown-linux-gnu 2、添加到 PATH\nexport PATH=$PATH:/opt/homebrew/Cellar/x86_64-unknown-linux-gnu/11.2.0_1/bin::/opt/homebrew/Cellar/aarch64-unknown-linux-gnu/11.2.0_1/bin 3、编译 CGO 程序\nCGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=x86_64-unknown-linux-gnu-gcc CXX=x86_64-unknown-linux-gnu-g++ go build CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=aarch64-unknown-linux-gnu-gcc CXX=aarch64-unknown-linux-gnu-g++ go build 方法二 # 1、安装依赖\nbrew install FiloSottile/musl-cross/musl-cross 2、添加到 PATH\nexport PATH=$PATH:/opt/homebrew/Cellar/musl-cross/0.9.9_1/bin 3、编译 CGO 程序\n# -tags=musl 不能省略不然会出现其他错误 CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=x86_64-linux-musl-gcc CXX=x86_64-linux-musl-g++ go build -tags=musl # 如果linux不想安装musl支持 CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=x86_64-linux-musl-gcc CXX=x86_64-linux-musl-g++ CGO_LDFLAGS=\u0026#34;-static\u0026#34; go build -tags=musl CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=x86_64-linux-musl-gcc CGO_LDFLAGS=\u0026#34;-static\u0026#34; go build CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=x86_64-linux-musl-gcc CGO_LDFLAGS=\u0026#34;-static\u0026#34; go build 参考 # https://blog.csdn.net/qq_41416964/article/details/129571304 https://zhuanlan.zhihu.com/p/338891206 « Goreleaser\n» pprof\n"},{"id":91,"href":"/go/pprof/","title":"Pprof","section":"Go","content":" 🏠 首页 / Golang 编程 / pprof\npprof # pprof 是性能调试工具,可以生成类似火焰图、堆栈图,内存分析图等。\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;math/rand\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;time\u0026#34; _ \u0026#34;net/http/pprof\u0026#34; ) // 吃内存 type Eater struct { Name string Buffer [][]int } var e Eater func main() { e = Eater{Name: \u0026#34;eater\u0026#34;} http.HandleFunc(\u0026#34;/go\u0026#34;, goHandler) http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil) // 如果不使用默认的 mux(http.DefaultServeMux),可以使用如下方式集成 pprof // mux := http.NewServeMux() // mux.HandleFunc(\u0026#34;/go\u0026#34;, goHandler) // mux.HandleFunc(\u0026#34;/debug/pprof/\u0026#34;, pprof.Index) // mux.HandleFunc(\u0026#34;/debug/pprof/cmdline\u0026#34;, pprof.Cmdline) // mux.HandleFunc(\u0026#34;/debug/pprof/profile\u0026#34;, pprof.Profile) // mux.HandleFunc(\u0026#34;/debug/pprof/symbol\u0026#34;, pprof.Symbol) // mux.HandleFunc(\u0026#34;/debug/pprof/trace\u0026#34;, pprof.Trace) // http.ListenAndServe(\u0026#34;:8080\u0026#34;, mux) fmt.Println(e.Name) } // 模拟创建 goroutine,内存没有及时回收 func goHandler(w http.ResponseWriter, r *http.Request) { for i := 0; i \u0026lt; 10; i++ { go func() { time.Sleep(time.Hour) }() e.EatMem() } w.Write([]byte(\u0026#34;ok\u0026#34;)) } func (e *Eater) EatMem() { e.Buffer = append(e.Buffer, generateWithCap(1024*1024)) } func generateWithCap(n int) []int { rand.Seed(time.Now().UnixNano()) nums := make([]int, 0, n) for i := 0; i \u0026lt; n; i++ { nums = append(nums, rand.Int()) } return nums } 运行:\ngo run main.go 访问 http://localhost:8080/go 模拟业务。\n访问 http://localhost:8080/debug/pprof/ 分析程序性能。\n图形方式分析:\n# 查看cpu go tool pprof -http=:6060 http://127.0.0.1:8080/debug/pprof/profile # 查看heap go tool pprof -http=:6060 http://127.0.0.1:8080/debug/pprof/heap # 查看goroutine go tool pprof -http=:6060 http://127.0.0.1:8080/debug/pprof/goroutine 需要提前安装工具 Graphviz\n内存使用:\ngoroutine 使用:\n« Mac M1 交叉编译 CGO\n» 使用 Go 生成 OpenSSH 兼容的 RSA 密钥对\n"},{"id":92,"href":"/go/ssh-keygen-with-go/","title":"SSH Keygen With Go","section":"Go","content":" 🏠 首页 / Golang 编程 / 使用 Go 生成 OpenSSH 兼容的 RSA 密钥对\n使用 Go 生成 OpenSSH 兼容的 RSA 密钥对 # 我们可以使用 ssh-keygen 命令生成一对用于 SSH 访问的私钥和公钥。本文将介绍如何使用 Go 生成一对 OpenSSH 兼容的 RSA 密钥对。\n以下代码中 GenOpenSSHKeyPair 方法用于生成一对用于 SSH 访问的私钥和公钥。生成的私钥以 PEM 编码,公钥以 OpenSSH authorized_keys 文件中包含的格式进行编码。\npackage util import ( \u0026#34;crypto/rand\u0026#34; \u0026#34;crypto/rsa\u0026#34; \u0026#34;crypto/x509\u0026#34; \u0026#34;encoding/pem\u0026#34; \u0026#34;golang.org/x/crypto/ssh\u0026#34; ) // GenOpenSSHKeyPair make a pair of private and public keys for SSH access. // Private Key generated is PEM encoded // Public key is encoded in the format for inclusion in an OpenSSH authorized_keys file. func GenOpenSSHKeyPair() ([]byte, []byte, error) { privateKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return nil, nil, err } privateKeyPEM := \u0026amp;pem.Block{ Type: \u0026#34;RSA PRIVATE KEY\u0026#34;, Bytes: x509.MarshalPKCS1PrivateKey(privateKey), } rsaSSHPriKeyBytes := pem.EncodeToMemory(privateKeyPEM) pub, err := ssh.NewPublicKey(\u0026amp;privateKey.PublicKey) if err != nil { return nil, nil, err } rsaSSHPubKeyBytes := ssh.MarshalAuthorizedKey(pub) return rsaSSHPriKeyBytes, rsaSSHPubKeyBytes, nil } « pprof\n"},{"id":93,"href":"/graphql/","title":"Graphql","section":"","content":" 🏠 首页 / Graphql\nGraphql # Graphql\n"},{"id":94,"href":"/graphql/graphql/","title":"Graphql","section":"Graphql","content":" 🏠 首页 / Graphql / Graphql\nGraphql # https://graphql.cn/learn/\n介绍 # 一种 API 查询语言,也是一种满足使用现有数据完成几乎所有数据查询的运行时。\n对 API 数据提供可理解的完整描述,赋予客户端准确获取所需数据,使得 API 的演进更加轻松,也可以使用它来构建强大的开发者工具(LowCode?)。\n特性 # 所得即所取:\n请求什么,返回什么,因为总是根据你请求的结构返回可预见的结果。\n单取多得:\n单次请求,返回多种结构的数据,不仅对资源属性查询,而且还会沿着资源的引用关系进一步查询。\n这也是对比 Restful API 的一种优势,Restful API 对多资源的查询时,往往需要多次访问不同的API,这无疑增加了网络连接的压力。\n类型系统描述所有:\nGraphQL API 基于类型和字段的方式进行组织,而非入口端点。你可以通过一个单一入口端点得到你的访问数据的所有能力。GraphQL 使用类型来保证应用只请求可能的数据,否则会提供了明确的有用的错误信息。应用也可以使用类型,而避免编写手动解析代码。\nAPI无版本:\nAPI 演进只需要往 Graphql 类型系统中添加字段和类型,而不影响现有查询。\n统一共享:\nGraphQL 让你的整个应用共享一套 API,而不用被限制于特定存储引擎。GraphQL 引擎已经有多种语言实现,通过 GraphQL API 能够更好利用你的现有数据和代码。你只需要为类型系统的字段编写函数,GraphQL 就能通过优化并发的方式来调用它们。\n资源定义,请求和响应 # 数据描述:\ntype User { name: String phone: String Friends: [User] } 数据请求:\n{ user(name: \u0026#34;dp\u0026#34;) { phone } } 请求结果:\n{ \u0026#34;user\u0026#34;: { \u0026#34;phone\u0026#34;: \u0026#34;18800001111\u0026#34; } } 变量 # 变量前缀必须为 $,后跟其类型,例如:$user User\n变量必须是标量,枚举型或输入对象类型。\n如果类型后没有跟 !,说明变量是可选的,可以不传入参数,否则必须出传入。\n默认变量:\nquery HeroNameAndFriends($episode: Episode = \u0026#34;JEDI\u0026#34;) { hero(episode: $episode) { name friends { name } } } GraphQLite # 是一个为 GraphQL schema 定义提供基于注释的语法的库。 它与框架无关,可用于 Symfony 和 Laravel。\nSchema # 文档 # learn: https://graphql.cn/learn/\ncode: https://graphql.org/code/\nawesome-graphql: https://github.com/chentsulin/awesome-graphql\n"},{"id":95,"href":"/grpc/","title":"Grpc","section":"","content":" 🏠 首页 / Grpc\nGrpc # gRPC 实战\n"},{"id":96,"href":"/grpc/gRPC/","title":"G Rpc","section":"Grpc","content":" 🏠 首页 / Grpc / gRPC 实战\ngRPC 实战 # 准备 # golang 1.13+ Protoc "},{"id":97,"href":"/istio/","title":"Istio","section":"","content":" 🏠 首页 / Istio\nIstio # Istio\n使用 aws-acm 管理 tls 密钥和证书\n安装 Istio\n授权策略 Authorization Policy\n应用平台实现应用金丝雀发布\nIstio 0-1 使用Istio实现Cors\n使用 Istio 实现服务超时\n应用层级设置访问白名单\n实现 Https 协议的转发\nIstio 0-1 流量管理方案\n"},{"id":98,"href":"/istio/aws-acm-tls-management/","title":"Aws Acm Tls Management","section":"Istio","content":" 🏠 首页 / Istio / 使用 aws-acm 管理 tls 密钥和证书\n使用 aws-acm 管理 tls 密钥和证书 # 参考: https://medium.com/faun/managing-tls-keys-and-certs-in-istio-using-amazons-acm-8ff9a0b99033\n在Ingressgateway service的annotation中添加:\nkind: Service apiVersion: v1 metadata: name: my-ingressgateway namespace: istio-system labels: app: my-ingressgateway annotations: # external-dns.alpha.kubernetes.io/hostname: example.com service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: \u0026#39;3600\u0026#39; service.beta.kubernetes.io/aws-load-balancer-ssl-cert: \u0026gt;- arn:aws:acm:ap-southeast-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx service.beta.kubernetes.io/aws-load-balancer-ssl-ports: https .... service.yaml:\napiVersion: v1 kind: Service metadata: name: demo-api labels: app: demop-api spec: ports: - name: http port: 80 targetPort: 80 selector: app: demo-api pod 所在的 namespace 需要开启 istio-injection,例如:kubectl label namespace default istio-injection=enabled\ngateway.yaml\napiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: demo-api-gateway annotations: ingress.kubernetes.io/force-ssl-redirect: \u0026#34;true\u0026#34; kubernetes.io/ingress.class: \u0026#34;istio-gateway\u0026#34; spec: selector: istio: hpa-ingressgateway # use istio default controller servers: - port: number: 80 name: http protocol: HTTP hosts: - \u0026#34;example.com\u0026#34; tls: httpsRedirect: true # sends 301 redirect for http requests - port: number: 443 name: https protocol: HTTP hosts: - \u0026#34;example.com\u0026#34; example.com 替换成你访问服务的域名;\ntls 设置自动跳转 https;\n将 443 端口设置成 HTTP 协议,作为负载均衡器的 HTTPS 流量的目标端。\nvirtual-service.yaml:\napiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: demo-api spec: hosts: # - \u0026#34;demo-api-01.example.dev\u0026#34; - \u0026#34;example.com\u0026#34; gateways: - demo-api-gateway http: - match: - uri: prefix: /api/demo route: - destination: port: number: 80 host: demo-api example.com 替换成你访问服务的域名。\n« Istio\n» 安装 Istio\n"},{"id":99,"href":"/istio/installation/","title":"Installation","section":"Istio","content":" 🏠 首页 / Istio / 安装 Istio\n安装 Istio # 安装istioctl # Step 1 下载:\n以下操作步骤默认会安装最新版 istioctl。\ncurl -L https://istio.io/downloadIstio | sh - 以上命令默认会安装最新版 istioctl,如果需要安装指定版本例如 1.6.8,使用以下命令。\ncurl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.6.8 TARGET_ARCH=x86_64 sh - Step 2 配置:\nstep 1会下载istio包,目录istio-{ISTIO_VERSION},进入目录\ncd istio-{ISTIO_VERSION} 拷贝bin目录下的istioctl二进制文件到PATH目录下:\ncp bin/istio /usr/local/bin « 使用 aws-acm 管理 tls 密钥和证书\n» 授权策略 Authorization Policy\n"},{"id":100,"href":"/istio/istio-auth-policy/","title":"Istio Auth Policy","section":"Istio","content":" 🏠 首页 / Istio / 授权策略 Authorization Policy\n授权策略 Authorization Policy # 授权架构 # 由于服务网格的 Sidecar 设计模式,每个工作负载都会有一个 Envoy 代理,而每个代理都运行着授权引擎,以此给请求授权。授权引擎依靠授权策略来鉴定请求权限,返回 ALLOW 或 DENY 鉴权结果。\n授权启用 # 将授权策略应用到工作负载即生效访问控制。对于没有应用授权策略的工作负载,则不会对请求做访问控制。\n授权策略 # 资源定义 # selector:\n标签选择器,通过标签选择器选择同命名空间下的目标工作负载,对目标工作负载启用访问控制。\naction:\n当满足rules条件时,控制ALLOW或DENY请求。\nrules:\n访问控制的请求条件:\nfrom:请求来源 to:请求目标 when:应用规则所需的提交 示例 # 以下授权策略允许两个源(服务帐号 cluster.local/ns/default/sa/sleep 和命名空间 dev),在使用有效的 JWT 令牌发送请求时,可以访问命名空间 foo 中的带有标签 app: httpbin 和 version: v1 的工作负载。\napiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: httpbin namespace: foo spec: selector: matchLabels: app: httpbin version: v1 action: ALLOW rules: - from: - source: principals: [\u0026#34;cluster.local/ns/default/sa/sleep\u0026#34;] - source: namespaces: [\u0026#34;dev\u0026#34;] to: - operation: methods: [\u0026#34;GET\u0026#34;] when: - key: request.auth.claims[iss] values: [\u0026#34;https://accounts.google.com\u0026#34;] 下例授权策略,如果请求来源不是命名空间 foo,请求将被拒绝。\napiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: httpbin-deny namespace: foo spec: selector: matchLabels: app: httpbin version: v1 action: DENY rules: - from: - source: notNamespaces: [\u0026#34;foo\u0026#34;] « 安装 Istio\n» 应用平台实现应用金丝雀发布\n"},{"id":101,"href":"/istio/istio-canary-deploy/","title":"Istio Canary Deploy","section":"Istio","content":" 🏠 首页 / Istio / 应用平台实现应用金丝雀发布\n应用平台实现应用金丝雀发布 # 实现思路 # 应用正常的 CI 流程添加请求参数:\nCanaryMode【bool,true | false,缺省值 false】 CanaryWeight 【int,缺省值 10】 使用 Istio 的 DetinationRule + VirtualService 实现流量按权重分配到不同版本应用。\n概念 # Iteration:发布迭代号,。是一个字段值,使用金丝雀发布时累加该值,从 0 累加。\nRetract:动作,弃用金丝雀版本。当金丝雀版本存在不可忽视的问题时撤回金丝雀执行该动作。\nRatify:动作,确认使用金丝雀版本。当金丝雀版本确认可以全面投入使用执行该动作。\n前提 # 使用金丝雀发布的前提条件:\n应用处于发布状态,已经至少发布一次,当前存在稳定的运行版本; 应用已经开启 Istio Gateway,涉及到 VirtualService 的流量转发; 应用当前不是金丝雀状态,要不然乱套了。 发布细节 # CI 发布除了创建或更新 Deployment,Service 之外,默认创建或更新 istio 的 DestinationRule 资源;\ndeployment:\napiVersion: apps/v1 kind: Deployment metadata: name: myapp-pbd3n69-v{iteration} # 出于兼容历史发布,当iteration为0时,name不附带iteration,iteration大于0时,name将附带iteration labels: app: myapp-pbd3n69 version: v{iteration} spec: selector: matchLabels: app: myapp-pbd3n69 version: v{iteration} template: metadata: labels: app: myapp-pbd3n69 version: v{iteration} ... service:\napiVersion: v1 kind: Service metadata: name: myapp-pbd3n69 spec: selector: app: myapp-pbd3n69 ... destination rule:\napiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: myapp-pbd3n69 spec: host: myapp-pbd3n69 # 注意:这里是 Service 的 name subsets: - labels: version: v{iteration} name: subset-v{iteration} virtual service:\napiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: virtual-service-example-com spec: hosts: - example.com http: - match: - uri: prefix: /xxx name: myapp-pbd3n69 route: - destination: host: myapp-pbd3n69 # 注意:这里是 Service 的 name port: number: 80 subset: subset-v{iteration} weight: 100 使用金丝雀发布,涉及到金丝雀版本 Deployment 的创建,DestinationRule 和 VirtualService 的更新:\ndeployment:\napiVersion: apps/v1 kind: Deployment metadata: name: myapp-pbd3n69-v{iteration+1} labels: app: myapp-pbd3n69 version: v{iteration+1} spec: selector: matchLabels: app: myapp-pbd3n69 version: v{iteration+1} template: metadata: labels: app: myapp-pbd3n69 version: v{iteration+1} destination rule:\napiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: ash-pbd3n69 namespace: test spec: host: ash-pbd3n69 # 注意:这里是 Service 的 name subsets: - labels: version: v{iteration} name: subset-v{iteration} - labels: version: v{iteration+1} name: subset-v{iteration+1} virtual service:\napiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: virtual-service-example-com spec: hosts: - example.com http: - match: - uri: prefix: /xxx name: myapp-pbd3n69 route: - destination: host: myapp-pbd3n69 # 注意:这里是 Service 的 name port: number: 80 subset: subset-v{iteration} weight: {canaryWeight} - destination: host: myapp-pbd3n69 # 注意:这里是 Service 的 name port: number: 80 subset: subset-v{iteration+1} weight: {100-canaryWeight} 发布完成后,Iteration+1,应用状态更新为 Canary。在 Canary 状态下,由于存在两个版本的 Deployment,将无法手动应用垂直或水平扩容。\n撤回(Retract) # 删除金丝雀版本的 Deployment,\nDestinationRule 更新\napiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: ash-pbd3n69 namespace: test spec: host: ash-pbd3n69 # 注意:这里是 Service 的 name subsets: - labels: version: v{iteration-1} name: subset-v{iteration-1} VirtualService 更新\napiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: virtual-service-example-com spec: hosts: - example.com http: - match: - uri: prefix: /xxx name: myapp-pbd3n69 route: - destination: host: myapp-pbd3n69 # 注意:这里是 Service 的 name port: number: 80 subset: subset-v{iteration-1} weight: 100 Iteration-1\n批准(Ratify) # 删除之前稳定版本的 Deployment,\nDestinationRule 更新\napiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: ash-pbd3n69 namespace: test spec: host: ash-pbd3n69 # 注意:这里是 Service 的 name subsets: - labels: version: v{iteration} name: subset-v{iteration} VirtualService 更新\napiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: virtual-service-example-com spec: hosts: - example.com http: - match: - uri: prefix: /xxx name: myapp-pbd3n69 route: - destination: host: myapp-pbd3n69 # 注意:这里是 Service 的 name port: number: 80 subset: subset-v{iteration} weight: 100 « 授权策略 Authorization Policy\n» Istio 0-1 使用Istio实现Cors\n"},{"id":102,"href":"/istio/istio-cors/","title":"Istio Cors","section":"Istio","content":" 🏠 首页 / Istio / Istio 0-1 使用Istio实现Cors\nIstio 0-1 使用Istio实现Cors # Cors # Cors(Cross-Origin Resource Sharing):跨域资源共享,是一种基于 HTTP Header 的机制,该机制通过允许服务器标示除了它自己以外的其它 origin(域,协议和端口),这样浏览器可以访问加载这些资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的\u0026quot;预检\u0026quot;请求。在预检中,浏览器发送的头中标示有 HTTP 方法和真实请求中会用到的头。\n跨源HTTP请求的一个例子:运行在 https://a.com 的JavaScript代码使用 XMLHttpRequest来发起一个到 https://b.com/data.js 的请求。\n出于安全性,浏览器限制脚本内发起的跨域 HTTP 请求。 例如,XMLHttpRequest 和 Fetch API 遵循同源策略。 这意味着使用这些 API 的 Web 应用程序只能从加载应用程序的同一个域请求 HTTP 资源,除非响应报文包含了正确 CORS 响应头。\n更多 Cors 知识: 跨域资源共享(CORS)\nIstio 实现 # 基于 Istio VirtualService 的配置实现。\n官方文档: Istio / Virtual Service#CorsPolicy\n在目标服务上设置允许的请求域 Hello:\napiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: b spec: http: - route: - destination: host: b.default.svc.cluster.local corsPolicy: allowOrigins: - exact: https://a.com - exact: https://b.com allowMethods: - GET 配置 allowOrigins 以及 allowMethods。\n« 应用平台实现应用金丝雀发布\n» 使用 Istio 实现服务超时\n"},{"id":103,"href":"/istio/istio-timeout/","title":"Istio Timeout","section":"Istio","content":" 🏠 首页 / Istio / 使用 Istio 实现服务超时\n使用 Istio 实现服务超时 # 参考 # https://www.servicemesher.com/blog/circuit-breaking-and-outlier-detection-in-istio/ 超时 # 为了防止无限期的等待服务,一般都会给服务设置超时时间,AWS 的 LoadBalancer 默认的超时时间是 60s。但是不同的服务,可能需要不同的超时设置,例如 DocumentApi 超时时间可能需要设置的长一点。\nLoadBalancer 的超时是全局的,我们基于 Istio 服务网格集成了针对单个服务的超时功能。\n重试 # 重试也是一个服务很常用的功能,例如某次请求分配到了一个问题节点,请求失败,则自动重试特定次数。\n熔断/限流 # 为了防止大量涌入请求使得服务崩溃,引入熔断功能。熔断,可以限制当前请求连接数在一个特定范围内。\n配置服务 VirtualService 既可实现:\napiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: myapp spec: hosts: - myapp http: - route: - destination: host: myapp subset: v1 timeout: 1s timeout:请求超过设定的超时时间,响应返回 504 请求超时。\n« Istio 0-1 使用Istio实现Cors\n» 应用层级设置访问白名单\n"},{"id":104,"href":"/istio/istio-white-manifest/","title":"Istio White Manifest","section":"Istio","content":" 🏠 首页 / Istio / 应用层级设置访问白名单\n应用层级设置访问白名单 # 需求 # 两个应用,foo 和 bar,应用 foo 只允许 IP 地址为 1.2.3.4 访问,应用 bar 只允许 IP 地址为 5.6.7.8 访问。\n实现 # 基于 istio 的 AuthorizationPolicy 实现。\n假设,现在 K8s 集群中已经安装 istio,并且有一个正在运行着的 istio-ingressgateway 转发应用 foo 和 bar:\n通过: https://www.example.com/foo 访问 foo;\n通过: https://www.example.com/bar 访问 bar;\n插曲 # 按照 istio 官方文档,使用 AuthorizationPolicy 即可实现基于应用层级的访问白名单设置:\napiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: foo spec: selector: matchLabels: app: foo action: ALLOW rules: - from: - source: ipBlocks: [\u0026#34;1.2.3.4\u0026#34;] 想让以上AuthorizationPolicy生效,需要在 foo 工作负载所在的命名空间注入 istio-proxy,通过给命名空间添加 label,添加 lable 后,工作负载启动新的 Pod 会自动注入 istio-proxy 容器:\nkubectl label namespace default istio-injection=enabled 但是,你会发现你使用 https://www.example.com/foo 一直返回的信息都是 RBAC: access denied。为什么会这样呢?\n如果你在 ipBlocks 中加入 istio-ingressgateway(是一个 Deployment)Pod 的 IP,你会发现这时候是可以成功访问的。\n分析流量的转发路径应该就能知道,实际上到达目标应用 foo 的请求,都由istio-ingressgateway转发了,所以源 IP 都会是 istio-ingressgateway Pod 的 IP,从而导致外部访问 IP 的白名单设置无法生效。\n那么如果获取外部IP呢。\n最终方案 # Step 0: 获取客户端源IP:\n如果使用 AWS-EKS 创建 istio-ingressgateway 服务时默认创建的 classic 类型的 LoadBalancer,需要修改成 network类型:\n通过向 istio-ingressgateway Service 添加 annotation:service.beta.kubernetes.io/aws-load-balancer-type: nlb\n更新 istio-gateway Service:spec.externalTrafficPolicy: Local,现在访问工作负载可以获取到源 IP,这就为我们设置 IP 白名单造就了条件。\n现在只需要为这个 istio-ingressgateway 应用上 AuthorizationPolicy 即可:\nStep 1: 默认允许所有 IP 访问 istio-ingressgateway:\napiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: allow-all-for-istio-ingressgateway namespace: istio-system spec: selector: matchLabels: istio: ingressgateway action: ALLOW rules: - from: - source: ipBlocks: [\u0026#34;0.0.0.0/0\u0026#34;] 默认是允许所有 IP 都可以访问 istio-ingressgateway 下转发的服务的,如果有服务例如 foo 和 bar 需要单独添加 IP 访问百名单则继续参考下面的步骤。\nStep 2: 只允许 IP 1.2.3.4 访问 foo:\napiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: foo namespace: istio-system spec: selector: matchLabels: istio: ingressgateway action: DENY rules: - from: - source: notIpBlocks: [\u0026#34;1.2.3.4\u0026#34;] to: - operation: hosts: - www.example.com paths: - /foo - /foo/* 由于默认是允许所有 IP 都可以访问 istio-ingressgateway 下转发的服务,所以为单独应用设置访问百名的时候使用 DENY + notIpBlocks 来完成。\nipBlocks 和 notIpBlocks 允许配置IP和IP的CIDR,例如:1.2.3.4 或 1.2.3.4/24。\nStep 3: 只允许 IP 5.6.7.8 访问 bar:\napiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: bar namespace: istio-system spec: selector: matchLabels: istio: ingressgateway action: DENY rules: - from: - source: notIpBlocks: [\u0026#34;5.6.7.8\u0026#34;] to: - operation: hosts: - www.example.com paths: - /bar - /bar/* 附录 # 如果相同的 LoadBalancer 下的服务都是用同一白名单设置的话,则没必要这么麻烦的设置 AuthorizationPolicy 了,只需要为 Service 设置参数即可:\napiVersion: v1 kind: Service metadata: name: istio-gateway ... spec: type: LoadBalancer loadBalancerSourceRanges: - \u0026#34;1.2.3.4/24\u0026#34; ... « 使用 Istio 实现服务超时\n» 实现 Https 协议的转发\n"},{"id":105,"href":"/istio/Istio/","title":"Istio","section":"Istio","content":" 🏠 首页 / Istio / Istio\nIstio # 简介 # Istio,是一种服务网格的平台。在微服务系统中起着连接,保护,控制和观察服务的作用。它可以降低微服务部署的复杂程度,减轻开发团队压力,无缝接入现有分布式应用程序,可以集成日志,遥测,和策略系统的 API 接口。\n服务网格:\nService Mesh 是一个基础设施层,用于处理服务间通信。云原生应用有着复杂的服务拓扑,Service Mesh 保证请求可以在这些拓扑中可靠地穿梭。在实际应用当中,Service Mesh 通常是由一系列轻量级的网络代理组成的,它们与应用程序部署在一起,但应用程序不需要知道它们的存在。\n用来描述组成应用程序的微服务网络以及它们之间的交互。\n实现需求包括:\n服务发现 负载均衡 故障恢复 指标和监控 A/B 测试 金丝雀发布 速率控制 访问控制 端到端认证 istioctl # 管理 istio 的命令行工具。\n安装 # curl -L https://istio.io/downloadIstio | sh - cp istio-x.x.x /usr/local cd istio-x.x.x export PATH=$PWD/bin:$PATH Istio 的绝大多数治理能力都是在 Sidecar 而非应用程序中实现,因此是非侵入的; Istio 的调用链埋点逻辑也是在 Sidecar 代理中完成,对应用程序非侵入,但应用程序需做适当的修改,即配合在请求头上传递生成的 Trace 相关信息。 关键功能:\n流量管理 可观察性 策略执行 服务身份和安全 平台支持 集成和定制 架构 # 数据面板 # Envoy # Envoy 是用 C++ 开发的高性能代理,用于协调服务网格中所有服务的入站和出站流量。Envoy 代理是唯一与数据平面流量交互的 Istio 组件。\n控制面板 # Pilot # 为 Envoy sidecar 提供服务发现、用于智能路由的流量管理功能(例如,A/B 测试、金丝雀发布等)以及弹性功能(超时、重试、熔断器等)。\n结构:\nAbstract Model Platform Adapter 功能:\n请求路由 服务发现和负载均衡 故障处理 故障注入 规则配置 Citadel # 通过内置的身份和证书管理,可以支持强大的服务到服务以及最终用户的身份验证。\nGalley # 是 Istio 的配置验证、提取、处理和分发组件。它负责将其余的 Istio 组件与从底层平台(例如 Kubernetes)获取用户配置的细节隔离开来。\nMixer # 流量管理 # 服务注册中心,服务发现系统,默认轮询策略 负载均衡池\n虚拟服务(Virtual Service) # 虚拟服务让您配置如何在服务网格内将请求路由到服务,这基于 Istio 和平台提供的基本的连通性和服务发现能力。\n示例:\napiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: reviews spec: hosts: - reviews gateway: http: - match: - headers: end-user: exact: jason route: - destination: host: reviews subset: v2 - route: - destination: host: reviews subset: v3 路由规则 # - match: - headers: end-user: exact: jason - match: - uri: prefix: /reviews 路由规则的优先级:从上往下,满足规则即流向路由目标\nDestination # route: - destination: host: reviews subset: v1 weight: 90 - destination: host: reviews subset: v2 weight: 10 目标规则(Destination Rule) # Deployment =\u0026gt; Pod =\u0026gt; Service =\u0026gt; DestinationRule =\u0026gt; VirtualService=\u0026gt; Gateway\n负载均衡的子集\napiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: my-destination-rule spec: host: my-svc trafficPolicy: loadBalancer: simple: RANDOM subsets: - name: v1 labels: version: v1 - name: v2 labels: version: v2 trafficPolicy: loadBalancer: simple: ROUND_ROBIN - name: v3 labels: version: v3 子集基于一个或多个 labels\nsubsets.trafficPolicy.loadBalancer.simple 指定访问 Service 后端的 endpoint 的流量策略。RANDOM:随机,ROUND_ROBIN:轮询。\n网关(Gateway) # 为网格来管理入站和出站流量,可以让您指定要进入或离开网格的流量。\napiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: ext-host-gwy spec: selector: app: my-gateway-controller servers: - port: number: 443 name: https protocol: HTTPS hosts: - ext-host.example.com tls: mode: SIMPLE serverCertificate: /tmp/tls.crt privateKey: /tmp/tls.key 将指定 host 的流量流入网格,然后将网关绑定到 VirtualService 上进行路由规则的指定。\n服务入口(Service Entry) # apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: svc-entry spec: hosts: - ext-svc.example.com ports: - number: 443 name: https protocol: HTTPS location: MESH_EXTERNAL resolution: DNS Sidecar # 网络弹性和测试 # 超时 # apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: ratings spec: hosts: - ratings http: - route: - destination: host: ratings subset: v1 timeout: 10s 下面的示例是一个虚拟服务,它对 ratings 服务的 v1 子集的调用指定 10 秒超时。\n重试 # apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: ratings spec: hosts: - ratings http: - route: - destination: host: ratings subset: v1 retries: attempts: 3 perTryTimeout: 2s 下面的示例配置了在初始调用失败后最多重试 3 次来连接到服务子集,每个重试都有 2 秒的超时。\n熔断 # apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: reviews spec: host: reviews subsets: - name: v1 labels: version: v1 trafficPolicy: connectionPool: tcp: maxConnections: 100 到达目标主机的请求超过预设值,则触发熔断,停止请求连接到该主机。\n故障注入(混沌工程) # apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: ratings spec: hosts: - ratings http: - fault: delay: percentage: value: 50 fixedDelay: 5s route: - destination: host: ratings subset: v1 下面的虚拟服务为百分之五十的访问 ratings 服务的请求配置了一个 5 秒的延迟。\napiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: ratings spec: hosts: - ratings http: - fault: abort: httpStatus: 500 percentage: value: 100 route: - destination: host: ratings subset: v1 下面的虚拟服务为所有访问 ratings 服务的请求配置了一个中止响应。\n流量转移 # 逐步调整百分比的流量,完成由部分路由到完全路由的迁移升级。\n如:v1 80% + v2 20% =\u0026gt; v2 100%\n记录 # 管理 ingressgateway https 连接使用AWS的 TLS 密钥和证书。\nkind: Service apiVersion: v1 metadata: name: istio-ingressgateway namespace: istio-system ... annotations: ... service.beta.kubernetes.io/aws-load-balancer-backend-protocol: tcp service.beta.kubernetes.io/aws-load-balancer-ssl-cert: \u0026gt;- arn:aws:acm:ap-southeast-1:314566904004:certificate/379dd055-44a7-42b6-a303-84938146b304 service.beta.kubernetes.io/aws-load-balancer-ssl-ports: https Gateway:\napiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: demo-gateway spec: selector: istio: ingressgateway servers: - port: number: 80 name: http protocol: HTTP hosts: - \u0026#34;*\u0026#34; tls: httpsRedirect: true # sends 301 redirect for http requests - port: number: 443 name: https-443 protocol: HTTP hosts: - \u0026#34;*\u0026#34; » 使用 aws-acm 管理 tls 密钥和证书\n"},{"id":106,"href":"/istio/tls-transform/","title":"Tls Transform","section":"Istio","content":" 🏠 首页 / Istio / 实现 Https 协议的转发\n实现 Https 协议的转发 # apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: demo spec: http: - headers: request: set: X-Forwarded-Proto: https match: - uri: prefix: / name: demo.default « 应用层级设置访问白名单\n» Istio 0-1 流量管理方案\n"},{"id":107,"href":"/istio/traffic-management/","title":"Traffic Management","section":"Istio","content":" 🏠 首页 / Istio / Istio 0-1 流量管理方案\nIstio 0-1 流量管理方案 # 设置 istio-system 命名空间下 istiod Deployment 的环境变量:\nPILOT_ENABLE_VIRTUAL_SERVICE_DELEGATE =true:\n« 实现 Https 协议的转发\n"},{"id":108,"href":"/kubernetes/","title":"Kubernetes","section":"","content":" 🏠 首页 / Kubernetes\nKubernetes # 反亲和性提高服务可用性\napiserver-builder\napiserver\n二进制搭建 K8s - 1 机器准备\n二进制搭建 K8s - 2 部署 etcd 集群\n二进制搭建 K8s - 3 部署 Master\n二进制搭建 K8s - 4 部署 Node\nKubernetes 0-1 尝试理解云原生\n集群联邦\n了解 ConfigMap\n定期删除 ElasticSearch 日志索引\n强制删除 K8s 资源\nGateway API 实践\nKubernetes 0-1 Helm Kubernetes 的包管理工具\nKubernetes 0-1 实现Pod自动扩缩HPA\nHTTP 客户端调用 Kubernetes APIServer\nInformer\n通过 Ingress 进行灰度发布\n安装 Kubernetes\nK3s\nKubernetes 0-1 K8s部署coredns\nKubernetes 0-1 K8s部署Dashboard\nKubernetes 0-1 K8s部署EFK\n可能需要运行多次以下命令,确保k8s资源都创建\nKubernetes 0-1 K8s部署Zookeeper和Kafka\nKubernetes 定制开发 01:K8s API 概念\nKubernetes 定制开发 02:CRD\nKubernetes 定制开发 50:扩展调度器\n简单介绍 K8s\nkubeadm 安装 Kubernetes (Docker)\nkubeadm 安装 k8s (containerd)\nKubeadm 升级 K8s\nkubebuilder 实战\nkubectl\nKubernetes 0-1 Kubernetes最佳实践\nKubernetes Dashboard\nKubernetes 中资源名称规范\nKuberentes\nKubeVirt 创建 Windows 虚拟机\nKubevirt 实践\nKustomize\nKubernetes 0-1 Pod中的livenessProbe和readinessProbe解读\nlocal 存储卷实践\nKubernetes 0-1 K8s自建LoadBalancer\n使用 nfs 持久化存储\nKubernetes 0-1 了解 Pod\nKubernetes 编程\nPrometheus-监控Kong完整操作\nPrometheus\nPVC 扩容\n了解 Secret\n了解 Service\nTelepresence\nKubernetes 0-1 使用preStop优雅终止Pod\nTerraform\nVelero + Minio 备份与恢复\n了解 Volume\nVPA\n"},{"id":109,"href":"/kubernetes/anti-affinity-improves-service-availability/","title":"Anti Affinity Improves Service Availability","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 反亲和性提高服务可用性\n反亲和性提高服务可用性 # 在 Kubernetes 中部署服务时,我们通常会部署多副本来提高服务的可用性。但是当这些副本集中部署在一个节点,而且很不幸,该节点出现故障,那么服务很容易陷入不可用状态。\n下面介绍一种方法,将服务副本分散部署在不同的节点(把鸡蛋放在不同的篮子里),避免单个节点故障导致服务多副本毁坏,提高服务可用性。\n反亲和 # apiVersion: apps/v1 kind: Deployment metadata: name: nginx labels: app: nginx spec: selector: matchLabels: app: nginx replicas: 5 template: metadata: labels: app: nginx spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - nginx topologyKey: kubernetes.io/hostname containers: - name: nginx image: nginx ports: - name: tcp containerPort: 80 使用 kubernetes.io/hostname 作为拓扑域,查看匹配规则,即同一打有同样标签 app=nginx 的 pod 会调度到不同的节点。\npodAntiAffinity 使用场景:\n将一个服务的 Pod 分散在不同的主机或者拓扑域中,提高服务本身的稳定性。 给 Pod 对于一个节点的独占访问权限来保证资源隔离,保证不会有其它 Pod 来分享节点资源。 把可能会相互影响的服务的 Pod 分散在不同的主机上 对于亲和性和反亲和性,每种都有三种规则可以设置:\nRequiredDuringSchedulingRequiredDuringExecution:在调度期间要求满足亲和性或者反亲和性规则,如果不能满足规则,则 Pod 不能被调度到对应的主机上。在之后的运行过程中,如果因为某些原因(比如修改 label)导致规则不能满足,系统会尝试把 Pod 从主机上删除(现在版本还不支持)。 RequiredDuringSchedulingIgnoredDuringExecution:在调度期间要求满足亲和性或者反亲和性规则,如果不能满足规则,则 Pod 不能被调度到对应的主机上。在之后的运行过程中,系统不会再检查这些规则是否满足。 PreferredDuringSchedulingIgnoredDuringExecution:在调度期间尽量满足亲和性或者反亲和性规则,如果不能满足规则,Pod 也有可能被调度到对应的主机上。在之后的运行过程中,系统不会再检查这些规则是否满足。 亲和性/反亲和性调度策略比较 # 调度策略 匹配标签 操作符 拓扑域支持 调度目标 nodeAffinity 主机 In, NotIn, Exists, DoesNotExist, Gt, Lt 否 pod 到指定主机 podAffinity Pod In, NotIn, Exists, DoesNotExist 是 pod 与指定 pod 同一拓扑域 PodAntiAffinity Pod In, NotIn, Exists, DoesNotExist 是 pod 与指定 pod 非同一拓扑域 » apiserver-builder\n"},{"id":110,"href":"/kubernetes/apiserver-builder/","title":"Apiserver Builder","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / apiserver-builder\napiserver-builder # 安装 # go install sigs.k8s.io/apiserver-builder-alpha/cmd/apiserver-boot@v1.23.0 搭建 # 初始化项目:\n⚠️ 注意:由于历史原因需要进入 $(go env GOPATH)/src/\u0026lt;package\u0026gt; 包目录下执行初始化命令。\nmkdir -p $(go env GOPATH)/src/github.com/poneding/apiserver-demo \u0026amp;\u0026amp; cd $(go env GOPATH)/src/github.com/poneding/apiserver-demo apiserver-boot init repo --domain k8sdev.poneding.com 创建 API:\n# apiserver-boot create \u0026lt;group\u0026gt; \u0026lt;version\u0026gt; \u0026lt;resource\u0026gt; apiserver-boot create demo v1alpha1 User apiserver-boot create group version resource --group demo --version v1alpha1 --kind User 参考 # https://github.com/kubernetes-sigs/apiserver-builder-alpha/blob/master/docs/tools_user_guide.md https://github.com/kubernetes-sigs/apiserver-builder-alpha/blob/master/README.md « 反亲和性提高服务可用性\n» apiserver\n"},{"id":111,"href":"/kubernetes/apiserver/","title":"Apiserver","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / apiserver\napiserver # 每一个 api 版本均有一个 apiservice 与之对应\nk api-versions | wc -l 30 k get apiservices.apiregistration.k8s.io| wc -l 30 « apiserver-builder\n» 二进制搭建 K8s - 1 机器准备\n"},{"id":112,"href":"/kubernetes/binary-build-k8s-01-prepare-nodes/","title":"Binary Build K8s 01 Prepare Nodes","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 二进制搭建 K8s - 1 机器准备\n二进制搭建 K8s - 1 机器准备 # 写在前面 # 记录和分享使用二进制搭建 K8s 集群的详细过程,由于操作比较冗长,大概会分四篇写完:\n机器准备: 部署 etcd 集群: 部署 Master: 部署 Node: 整个目标是使用二进制的方式搭建一个小型 K8s 集群(1 个 Master,2 个 Node),供自己学习测试。\n至于为什么要自己去用二进制的方式去搭建 K8s,而不是选用 minikube 或者 kubeadm 去搭建?\n因为使用二进制搭建,K8s 的每个组件,每个工具都需要你手动的安装和配置,帮助你加深对 K8s 组织架构和工作原理的了解。\n准备工作 # 三台 centos7 虚拟机,自己学习使用的话 1 核 1G 应该就够了。\n虚拟机能够连网,相关的安装包文件下载和 Docker 下载镜像需要使用到外网。\n当前虚拟机:\nk8s-master01: 192.168.115.131 k8s-node01: 192.168.115.132 k8s-node02: 192.168.115.133 虚拟机初始化 # 不做特殊说明的话:\n以下操作需要在 Master 和 Node 的所有机器上执行\n使用 sudo 权限执行命令\n配置网络接口 # # 使用 ip addr 获取不到机器的 IP 时执行 dhclient 命令 dhclient 安装基础软件 # yum install vim ntp wget -y 修改主机名并添加 hosts # 在k8s-master01上执行\nhostnamectl set-hostname \u0026#34;k8s-master01\u0026#34; 在k8s-node01上执行\nhostnamectl set-hostname \u0026#34;k8s-node01\u0026#34; 在k8s-node02上执行\nhostnamectl set-hostname \u0026#34;k8s-node02\u0026#34; 添加 hosts # vim /etc/hosts 执行上行命令,在文件中追加以下内容:\n192.168.115.131 k8s-master01 192.168.115.132 k8s-node01 192.168.115.133 k8s-node02 关闭防火墙、selinux、swap # systemctl stop firewalld systemctl disable firewalld setenforce 0 sed -i \u0026#39;s/enforcing/disabled/\u0026#39; /etc/selinux/config swapoff -a vim /etc/fstab # 编辑 etc/fstab 文件,注释 swap 所在的行 同步时间 # ntpdate time.windows.com Master 准备文件 # 在 Master 机器执行:\nmkdir /root/kubernetes/resources -p cd /root/kubernetes/resources wget https://dl.k8s.io/v1.18.3/kubernetes-server-linux-amd64.tar.gz wget https://github.com/etcd-io/etcd/releases/download/v3.4.9/etcd-v3.4.9-linux-amd64.tar.gz wget https://pkg.cfssl.org/R1.2/cfssl_linux-amd64 wget https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64 wget https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64 wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml Node 准备文件 # 在 Node 机器执行:\nmkdir /root/kubernetes/resources -p cd /root/kubernetes/resources wget https://dl.k8s.io/v1.18.3/kubernetes-node-linux-amd64.tar.gz wget https://github.com/etcd-io/etcd/releases/download/v3.4.9/etcd-v3.4.9-linux-amd64.tar.gz wget https://github.com/containernetworking/plugins/releases/download/v0.8.6/cni-plugins-linux-amd64-v0.8.6.tgz wget https://download.docker.com/linux/static/stable/x86_64/docker-19.03.9.tgz 有些文件的较大,下载花费时间可能较长。可以提前下载好之后,拷贝到虚拟机。\n第一段落机器准备愉快结束。\n« apiserver\n» 二进制搭建 K8s - 2 部署 etcd 集群\n"},{"id":113,"href":"/kubernetes/binary-build-k8s-02-deploy-etcd/","title":"Binary Build K8s 02 Deploy Etcd","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 二进制搭建 K8s - 2 部署 etcd 集群\n二进制搭建 K8s - 2 部署 etcd 集群 # 写在前面 # 记录和分享使用二进制搭建 K8s 集群的详细过程,由于操作比较冗长,大概会分四篇写完:\n机器准备: 部署 etcd 集群: 部署 Master: 部署 Node: etcd 作为 K8s 的数据库,需要首先安装,为其他组件做服务基础。\netcd 是一个分布式的数据库系统,为了模拟 etcd 的高可用,我们将 etcd 部署在三台虚拟机上,正好就部署在 K8s 集群所使用的三台机器上吧。\netcd 集群,K8s 组件之间通信,为了安全可靠,我们最好启用 HTTPS 安全机制。K8s 提供了基于 CA 签名的双向数字证书认证方式和简单的基于 HTTP Base 或 Token 的认证方式,其中 CA 证书方式的安全性最高。我们使用 cfssl 为我们的 K8s 集群配置 CA 证书,此外也可以使用 openssl。\n安装 cfssl # 在 Master 机器执行:\ncd /root/kubernetes/resources cp cfssl_linux-amd64 /usr/bin/cfssl cp cfssljson_linux-amd64 /usr/bin/cfssljson cp cfssl-certinfo_linux-amd64 /usr/bin/cfssl-certinfo chmod +x /usr/bin/cfssl /usr/bin/cfssljson /usr/bin/cfssl-certinfo 在所有机器执行:\nmkdir /etc/etcd/ssl -p 制作 etcd 证书 # 在 Master 机器执行:\nmkdir /root/kubernetes/resources/cert/etcd -p cd /root/kubernetes/resources/cert/etcd 编辑 ca-config.json\nvim ca-config.json 写入文件内容如下:\n{ \u0026#34;signing\u0026#34;: { \u0026#34;default\u0026#34;: { \u0026#34;expiry\u0026#34;: \u0026#34;87600h\u0026#34; }, \u0026#34;profiles\u0026#34;: { \u0026#34;etcd\u0026#34;: { \u0026#34;expiry\u0026#34;: \u0026#34;87600h\u0026#34;, \u0026#34;usages\u0026#34;: [ \u0026#34;signing\u0026#34;, \u0026#34;key encipherment\u0026#34;, \u0026#34;server auth\u0026#34;, \u0026#34;client auth\u0026#34; ] } } } } 编辑 ca-csr.json:\nvim ca-csr.json 写入文件内容如下:\n{ \u0026#34;CN\u0026#34;: \u0026#34;etcd ca\u0026#34;, \u0026#34;key\u0026#34;: { \u0026#34;algo\u0026#34;: \u0026#34;rsa\u0026#34;, \u0026#34;size\u0026#34;: 2048 }, \u0026#34;names\u0026#34;: [ { \u0026#34;C\u0026#34;: \u0026#34;CN\u0026#34;, \u0026#34;L\u0026#34;: \u0026#34;Hunan\u0026#34;, \u0026#34;ST\u0026#34;: \u0026#34;Changsha\u0026#34; } ] } 生成 ca 证书和密钥:\ncfssl gencert -initca ca-csr.json | cfssljson -bare ca 编辑 server-csr.json:\nvim server-csr.json 写入文件内容如下:\n{ \u0026#34;CN\u0026#34;: \u0026#34;etcd\u0026#34;, \u0026#34;hosts\u0026#34;: [ \u0026#34;192.168.115.131\u0026#34;, \u0026#34;192.168.115.132\u0026#34;, \u0026#34;192.168.115.133\u0026#34; ], \u0026#34;key\u0026#34;: { \u0026#34;algo\u0026#34;: \u0026#34;rsa\u0026#34;, \u0026#34;size\u0026#34;: 2048 }, \u0026#34;names\u0026#34;: [ { \u0026#34;C\u0026#34;: \u0026#34;CN\u0026#34;, \u0026#34;L\u0026#34;: \u0026#34;Hunan\u0026#34;, \u0026#34;ST\u0026#34;: \u0026#34;Changsha\u0026#34; } ] } hosts 中配置所有 Master 和 Node 的 IP 列表。\n生成 etcd 证书和密钥\ncfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=etcd server-csr.json | cfssljson -bare server # 此时目录下会生成 7 个文件 ls ca-config.json ca.csr ca-csr.json ca-key.pem ca.pem server.csr server-csr.json server-key.pem server.pem 拷贝证书\ncp ca.pem server-key.pem server.pem /etc/etcd/ssl scp ca.pem server-key.pem server.pem 192.168.115.132:/etc/etcd/ssl scp ca.pem server-key.pem server.pem 192.168.115.133:/etc/etcd/ssl 安装 etcd 集群 # 在所有机器执行:\ncd /root/kubernetes/resources tar -zxvf /root/kubernetes/resources/etcd-v3.4.9-linux-amd64.tar.gz cp ./etcd-v3.4.9-linux-amd64/etcd ./etcd-v3.4.9-linux-amd64/etcdctl /usr/bin 配置 etcd # 这里开始命令需要分别在 Master 和 Node 机器执行,配置 etcd.conf\nvim /etc/etcd/etcd.conf k8s-master01 写入文件内容如下:\n[Member] ETCD_NAME=\u0026#34;etcd01\u0026#34; ETCD_DATA_DIR=\u0026#34;/var/lib/etcd/default.etcd\u0026#34; ETCD_LISTEN_PEER_URLS=\u0026#34;https://192.168.115.131:2380\u0026#34; ETCD_LISTEN_CLIENT_URLS=\u0026#34;https://192.168.115.131:2379,https://127.0.0.1:2379\u0026#34; [Clustering] ETCD_INITIAL_ADVERTISE_PEER_URLS=\u0026#34;https://192.168.115.131:2380\u0026#34; ETCD_ADVERTISE_CLIENT_URLS=\u0026#34;https://192.168.115.131:2379\u0026#34; ETCD_INITIAL_CLUSTER=\u0026#34;etcd01=https://192.168.115.131:2380,etcd02=https://192.168.115.132:2380,etcd03=https://192.168.115.133:2380\u0026#34; ETCD_INITIAL_CLUSTER_TOKEN=\u0026#34;etcd-cluster\u0026#34; ETCD_INITIAL_CLUSTER_STATE=\u0026#34;new\u0026#34; k8s-node01 写入文件内容如下:\n[Member] ETCD_NAME=\u0026#34;etcd02\u0026#34; ETCD_DATA_DIR=\u0026#34;/var/lib/etcd/default.etcd\u0026#34; ETCD_LISTEN_PEER_URLS=\u0026#34;https://192.168.115.132:2380\u0026#34; ETCD_LISTEN_CLIENT_URLS=\u0026#34;https://192.168.115.132:2379,https://127.0.0.1:2379\u0026#34; [Clustering] ETCD_INITIAL_ADVERTISE_PEER_URLS=\u0026#34;https://192.168.115.132:2380\u0026#34; ETCD_ADVERTISE_CLIENT_URLS=\u0026#34;https://192.168.115.132:2379\u0026#34; ETCD_INITIAL_CLUSTER=\u0026#34;etcd01=https://192.168.115.131:2380,etcd02=https://192.168.115.132:2380,etcd03=https://192.168.115.133:2380\u0026#34; ETCD_INITIAL_CLUSTER_TOKEN=\u0026#34;etcd-cluster\u0026#34; ETCD_INITIAL_CLUSTER_STATE=\u0026#34;new\u0026#34; k8s-node02 写入文件内容如下:\n[Member] ETCD_NAME=\u0026#34;etcd03\u0026#34; ETCD_DATA_DIR=\u0026#34;/var/lib/etcd/default.etcd\u0026#34; ETCD_LISTEN_PEER_URLS=\u0026#34;https://192.168.115.133:2380\u0026#34; ETCD_LISTEN_CLIENT_URLS=\u0026#34;https://192.168.115.133:2379,https://127.0.0.1:2379\u0026#34; [Clustering] ETCD_INITIAL_ADVERTISE_PEER_URLS=\u0026#34;https://192.168.115.133:2380\u0026#34; ETCD_ADVERTISE_CLIENT_URLS=\u0026#34;https://192.168.115.133:2379\u0026#34; ETCD_INITIAL_CLUSTER=\u0026#34;etcd01=https://192.168.115.131:2380,etcd02=https://192.168.115.132:2380,etcd03=https://192.168.115.133:2380\u0026#34; ETCD_INITIAL_CLUSTER_TOKEN=\u0026#34;etcd-cluster\u0026#34; ETCD_INITIAL_CLUSTER_STATE=\u0026#34;new\u0026#34; 这里开始在所有机器执行,设置 etcd 服务配置文件\nmkdir -p /var/lib/etcd vim /usr/lib/systemd/system/etcd.service 执行上行命令,写入文件内容如下:\n[Unit] Description=Etcd Server After=network.target After=network-online.target Wants=network-online.target [Service] Type=notify EnvironmentFile=/etc/etcd/etcd.conf ExecStart=/usr/bin/etcd \\ --cert-file=/etc/etcd/ssl/server.pem \\ --key-file=/etc/etcd/ssl/server-key.pem \\ --peer-cert-file=/etc/etcd/ssl/server.pem \\ --peer-key-file=/etc/etcd/ssl/server-key.pem \\ --trusted-ca-file=/etc/etcd/ssl/ca.pem \\ --peer-trusted-ca-file=/etc/etcd/ssl/ca.pem Restart=on-failure LimitNOFILE=65536 [Install] WantedBy=multi-user.target etcd3.4 版本会自动 EnvironmentFile 文件中的环境变量,不需要再 ExecStart 的命令参数重复设置,否则会报:\u0026quot;xxx\u0026quot; is shadowed by corresponding command-line flag 的错误信息。\n启动 etcd,并且设置开机自动运行 etcd\nsystemctl daemon-reload systemctl start etcd.service systemctl enable etcd.service 检查 etcd 集群的健康状态\netcdctl endpoint health --cacert=/etc/etcd/ssl/ca.pem --cert=/etc/etcd/ssl/server.pem --key=/etc/etcd/ssl/server-key.pem --endpoints=\u0026#34;https://192.168.115.131:2379,https://192.168.115.132:2379,https://192.168.115.133:2379\u0026#34; 输出如下,说明 etcd 集群已经部署成功。\nhttps://192.168.115.133:2379 is healthy: successfully committed proposal: took = 15.805605ms https://192.168.115.132:2379 is healthy: successfully committed proposal: took = 22.127986ms https://192.168.115.131:2379 is healthy: successfully committed proposal: took = 24.829669ms 第二段落部署 etcd 集群愉快结束。\n« 二进制搭建 K8s - 1 机器准备\n» 二进制搭建 K8s - 3 部署 Master\n"},{"id":114,"href":"/kubernetes/binary-build-k8s-03-deploy-master/","title":"Binary Build K8s 03 Deploy Master","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 二进制搭建 K8s - 3 部署 Master\n二进制搭建 K8s - 3 部署 Master # 写在前面 # 记录和分享使用二进制搭建 K8s 集群的详细过程,由于操作比较冗长,大概会分四篇写完:\n机器准备: 部署 etcd 集群: 部署 Master: 部署 Node: 我们已经知道在 K8s 的 Master 上存在着 kube-apiserver、kube-controller-manager、kube-scheduler 三大组件。本篇介绍在 Master 机器安装这些组件,除此之外,如果想在 Master 机器上操作集群,还需要安装 kubectl 工具。\n安装 kubectl # kubernetes 的安装包里已经将 kubectl 包含进去了,部署很简单:\ncd /root/kubernetes/resources/ tar -zxvf ./kubernetes-server-linux-amd64.tar.gz cp kubernetes/server/bin/kubectl /usr/bin kubectl api-versions 制作 kubernetes 证书 # mkdir /root/kubernetes/resources/cert/kubernetes /etc/kubernetes/{ssl,bin} -p cp kubernetes/server/bin/kube-apiserver kubernetes/server/bin/kube-controller-manager kubernetes/server/bin/kube-scheduler /etc/kubernetes/bin cd /root/kubernetes/resources/cert/kubernetes 接下来都在 Master 机器上执行,编辑 ca-config.json\nvim ca-config.json 写入文件内容如下:\n{ \u0026#34;signing\u0026#34;: { \u0026#34;default\u0026#34;: { \u0026#34;expiry\u0026#34;: \u0026#34;87600h\u0026#34; }, \u0026#34;profiles\u0026#34;: { \u0026#34;kubernetes\u0026#34;: { \u0026#34;expiry\u0026#34;: \u0026#34;87600h\u0026#34;, \u0026#34;usages\u0026#34;: [ \u0026#34;signing\u0026#34;, \u0026#34;key encipherment\u0026#34;, \u0026#34;server auth\u0026#34;, \u0026#34;client auth\u0026#34; ] } } } } 编辑 ca-csr.json:\nvim ca-csr.json 写入文件内容如下:\n{ \u0026#34;CN\u0026#34;: \u0026#34;kubernetes\u0026#34;, \u0026#34;key\u0026#34;: { \u0026#34;algo\u0026#34;: \u0026#34;rsa\u0026#34;, \u0026#34;size\u0026#34;: 2048 }, \u0026#34;names\u0026#34;: [ { \u0026#34;C\u0026#34;: \u0026#34;CN\u0026#34;, \u0026#34;L\u0026#34;: \u0026#34;Hunan\u0026#34;, \u0026#34;ST\u0026#34;: \u0026#34;Changsha\u0026#34;, \u0026#34;O\u0026#34;: \u0026#34;kubernetes\u0026#34;, \u0026#34;OU\u0026#34;: \u0026#34;System\u0026#34; } ] } 生成 ca 证书和密钥:\ncfssl gencert -initca ca-csr.json | cfssljson -bare ca 制作 kube-apiserver、kube-proxy、admin 证书,编辑 kube-apiserver-csr.json:\nvim kube-apiserver-csr.json 写入文件内容如下:\n{ \u0026#34;CN\u0026#34;: \u0026#34;kubernetes\u0026#34;, \u0026#34;hosts\u0026#34;: [ \u0026#34;10.0.0.1\u0026#34;, \u0026#34;127.0.0.1\u0026#34;, \u0026#34;kubernetes\u0026#34;, \u0026#34;kubernetes.default\u0026#34;, \u0026#34;kubernetes.default.svc\u0026#34;, \u0026#34;kubernetes.default.svc.cluster\u0026#34;, \u0026#34;kubernetes.default.svc.cluster.local\u0026#34;, \u0026#34;192.168.115.131\u0026#34;, \u0026#34;192.168.115.132\u0026#34;, \u0026#34;192.168.115.133\u0026#34; ], \u0026#34;key\u0026#34;: { \u0026#34;algo\u0026#34;: \u0026#34;rsa\u0026#34;, \u0026#34;size\u0026#34;: 2048 }, \u0026#34;names\u0026#34;: [ { \u0026#34;C\u0026#34;: \u0026#34;CN\u0026#34;, \u0026#34;L\u0026#34;: \u0026#34;Hunan\u0026#34;, \u0026#34;ST\u0026#34;: \u0026#34;Changsha\u0026#34;, \u0026#34;O\u0026#34;: \u0026#34;kubernetes\u0026#34;, \u0026#34;OU\u0026#34;: \u0026#34;System\u0026#34; } ] } 编辑 kube-proxy-csr.json:\nvim kube-proxy-csr.json 写入文件内容如下:\n{ \u0026#34;CN\u0026#34;: \u0026#34;system:kube-proxy\u0026#34;, \u0026#34;hosts\u0026#34;: [], \u0026#34;key\u0026#34;: { \u0026#34;algo\u0026#34;: \u0026#34;rsa\u0026#34;, \u0026#34;size\u0026#34;: 2048 }, \u0026#34;names\u0026#34;: [ { \u0026#34;C\u0026#34;: \u0026#34;CN\u0026#34;, \u0026#34;L\u0026#34;: \u0026#34;Hunan\u0026#34;, \u0026#34;ST\u0026#34;: \u0026#34;Changsha\u0026#34;, \u0026#34;O\u0026#34;: \u0026#34;kubernetes\u0026#34;, \u0026#34;OU\u0026#34;: \u0026#34;System\u0026#34; } ] } 编辑 admin-csr.json:\nvim admin-csr.json 写入文件内容如下:\n{ \u0026#34;CN\u0026#34;: \u0026#34;admin\u0026#34;, \u0026#34;hosts\u0026#34;: [], \u0026#34;key\u0026#34;: { \u0026#34;algo\u0026#34;: \u0026#34;rsa\u0026#34;, \u0026#34;size\u0026#34;: 2048 }, \u0026#34;names\u0026#34;: [ { \u0026#34;C\u0026#34;: \u0026#34;CN\u0026#34;, \u0026#34;L\u0026#34;: \u0026#34;Hunan\u0026#34;, \u0026#34;ST\u0026#34;: \u0026#34;Changsha\u0026#34;, \u0026#34;O\u0026#34;: \u0026#34;system:masters\u0026#34;, \u0026#34;OU\u0026#34;: \u0026#34;System\u0026#34; } ] } 生成证书和密钥\ncfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes kube-apiserver-csr.json | cfssljson -bare kube-apiserver cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes kube-proxy-csr.json | cfssljson -bare kube-proxy cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes admin-csr.json | cfssljson -bare admin # 此时目录下生成的文件 ll -rw-r--r--. 1 root root 1001 May 28 00:32 admin.csr -rw-r--r--. 1 root root 282 May 28 00:32 admin-csr.json -rw-------. 1 root root 1679 May 28 00:32 admin-key.pem -rw-r--r--. 1 root root 1407 May 28 00:32 admin.pem -rw-r--r--. 1 root root 294 May 28 00:30 ca-config.json -rw-r--r--. 1 root root 1013 May 28 00:31 ca.csr -rw-r--r--. 1 root root 284 May 28 00:30 ca-csr.json -rw-------. 1 root root 1675 May 28 00:31 ca-key.pem -rw-r--r--. 1 root root 1383 May 28 00:31 ca.pem -rw-r--r--. 1 root root 1273 May 28 00:32 kube-apiserver.csr -rw-r--r--. 1 root root 597 May 28 00:31 kube-apiserver-csr.json -rw-------. 1 root root 1679 May 28 00:32 kube-apiserver-key.pem -rw-r--r--. 1 root root 1655 May 28 00:32 kube-apiserver.pem -rw-r--r--. 1 root root 1009 May 28 00:32 kube-proxy.csr -rw-r--r--. 1 root root 287 May 28 00:31 kube-proxy-csr.json -rw-------. 1 root root 1679 May 28 00:32 kube-proxy-key.pem -rw-r--r--. 1 root root 1411 May 28 00:32 kube-proxy.pem 将 kube-proxy 证书拷贝到 Node:\n前提,需要在 Node 机器创建目录,以下命令在 Node 机器上执行:\nmkdir /etc/kubernetes/ -p 然后再在Master机器执行拷贝操作。\ncp ca.pem ca-key.pem kube-apiserver.pem kube-apiserver-key.pem kube-proxy.pem kube-proxy-key.pem /etc/kubernetes/ssl scp -r /etc/kubernetes/ssl 192.168.115.132:/etc/kubernetes scp -r /etc/kubernetes/ssl 192.168.115.133:/etc/kubernetes 创建 TLSBootstrapping Token # cd /etc/kubernetes head -c 16 /dev/urandom | od -An -t x | tr -d \u0026#39; \u0026#39; # 执行上一步会得到一个 token,例如 d5c5d767b64db39db132b433e9c45fbc,编辑文件 token.csv 时需要 vim token.csv 写入文件内容,替换生成的 token\nd5c5d767b64db39db132b433e9c45fbc,kubelet-bootstrap,10001,\u0026#34;system:node-bootstrapper\u0026#34; 安装 kube-apiserver # 准备 kube-apiserver 配置文件\nvim apiserver 执行上行命令,写入文件内容如下:\nKUBE_API_ARGS=\u0026#34;--logtostderr=false \\ --v=2 \\ --log-dir=/var/log/kubernetes \\ --etcd-servers=https://192.168.115.131:2379,https://192.168.115.132:2379,https://192.168.115.133:2379 \\ --bind-address=192.168.115.131 \\ --secure-port=6443 \\ --advertise-address=192.168.115.131 \\ --allow-privileged=true \\ --service-cluster-ip-range=10.0.0.0/24 \\ --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota,NodeRestriction \\ --authorization-mode=RBAC,Node \\ --enable-bootstrap-token-auth=true \\ --token-auth-file=/etc/kubernetes/token.csv \\ --service-node-port-range=30000-32767 \\ --kubelet-client-certificate=/etc/kubernetes/ssl/kube-apiserver.pem \\ --kubelet-client-key=/etc/kubernetes/ssl/kube-apiserver-key.pem \\ --tls-cert-file=/etc/kubernetes/ssl/kube-apiserver.pem \\ --tls-private-key-file=/etc/kubernetes/ssl/kube-apiserver-key.pem \\ --client-ca-file=/etc/kubernetes/ssl/ca.pem \\ --service-account-key-file=/etc/kubernetes/ssl/ca-key.pem \\ --etcd-cafile=/etc/etcd/ssl/ca.pem \\ --etcd-certfile=/etc/etcd/ssl/server.pem \\ --etcd-keyfile=/etc/etcd/ssl/server-key.pem \\ --audit-log-maxage=30 \\ --audit-log-maxbackup=3 \\ --audit-log-maxsize=100 \\ --audit-log-path=/var/logs/kubernetes/k8s-audit.log\u0026#34; 准备 kube-apiserver 服务配置文件\nvim /usr/lib/systemd/system/kube-apiserver.service 执行上行命令,写入文件内容如下:\n[Unit] Description=Kubernetes API Server Documentation=https://github.com/GoogleCloudPlatform/kubernetes After=etcd.service Wants=etcd.service [Service] Type=notify EnvironmentFile=/etc/kubernetes/apiserver ExecStart=/etc/kubernetes/bin/kube-apiserver $KUBE_API_ARGS Restart=on-failure LimitNOFILE=65536 [Install] WantedBy=multi-user.target 启动 kube-apiserver:\nsystemctl daemon-reload systemctl start kube-apiserver systemctl enable kube-apiserver systemctl status kube-apiserver 安装 kube-controller-manager # 准备 kube-controller-manger 配置文件\nvim controller-manager 执行上行命令,写入文件内容如下:\nKUBE_CONTROLLER_MANAGER_ARGS=\u0026#34;--logtostderr=false \\ --v=2 \\ --log-dir=/var/log/kubernetes \\ --leader-elect=true \\ --master=127.0.0.1:8080 \\ --bind-address=127.0.0.1 \\ --allocate-node-cidrs=true \\ --cluster-cidr=10.244.0.0/16 \\ --service-cluster-ip-range=10.0.0.0/24 \\ --cluster-signing-cert-file=/etc/kubernetes/ssl/ca.pem \\ --cluster-signing-key-file=/etc/kubernetes/ssl/ca-key.pem \\ --root-ca-file=/etc/kubernetes/ssl/ca.pem \\ --service-account-private-key-file=/etc/kubernetes/ssl/ca-key.pem \\ --experimental-cluster-signing-duration=87600h0m0s\u0026#34; 准备 kube-controller-manger 服务配置文件\nvim /usr/lib/systemd/system/kube-controller-manager.service 执行上行命令,写入文件内容如下:\n[Unit] Description=Kubernetes Controller Manager Documentation=https://github.com/GoogleCloudPlatform/kubernetes After=kube-apiserver.service Requires=kube-apiserver.service [Service] EnvironmentFile=/etc/kubernetes/controller-manager ExecStart=/etc/kubernetes/bin/kube-controller-manager $KUBE_CONTROLLER_MANAGER_ARGS Restart=on-failure LimitNOFILE=65536 [Install] WantedBy=multi-user.target 启动kube-controller-manager:\nsystemctl daemon-reload systemctl start kube-controller-manager systemctl enable kube-controller-manager systemctl status kube-controller-manager 安装 kube-scheduler # 准备 kube-scheduler 配置文件\nvim scheduler 执行上行命令,写入文件内容如下:\nKUBE_SCHEDULER_ARGS=\u0026#34;--logtostderr=false \\ --v=2 \\ --log-dir=/var/log/kubernetes \\ --master=127.0.0.1:8080 \\ --leader-elect \\ --bind-address=127.0.0.1\u0026#34; 准备 kube-scheduler 服务配置文件\nvim /usr/lib/systemd/system/kube-scheduler.service 执行上行命令,写入文件内容如下:\n[Unit] Description=Kubernetes Scheduler Documentation=https://github.com/GoogleCloudPlatform/kubernetes After=kube-apiserver.service Requires=kube-apiserver.service [Service] EnvironmentFile=/etc/kubernetes/scheduler ExecStart=/etc/kubernetes/bin/kube-scheduler $KUBE_SCHEDULER_ARGS Restart=on-failure LimitNOFILE=65536 [Install] WantedBy=multi-user.target 启动 kube-scheduler:\nsystemctl daemon-reload systemctl start kube-scheduler systemctl enable kube-scheduler systemctl status kube-scheduler kubelet-bootstrap 授权 # kubectl create clusterrolebinding kubelet-bootstrap \\ --clusterrole=system:node-bootstrapper \\ --user=kubelet-bootstrap 查看 Master 状态\nkubectl get cs 如果 Master 部署成功,应该输出:\nNAME STATUS MESSAGE ERROR scheduler Healthy ok controller-manager Healthy ok etcd-2 Healthy {\u0026#34;health\u0026#34;:\u0026#34;true\u0026#34;} etcd-1 Healthy {\u0026#34;health\u0026#34;:\u0026#34;true\u0026#34;} etcd-0 Healthy {\u0026#34;health\u0026#34;:\u0026#34;true\u0026#34;} apiserver 授权 kubelet # 准备 apiserver-to-kubelet-rbac.yaml 文件\ncd /root/kubernetes/resources vim apiserver-to-kubelet-rbac.yaml 执行上行命令,写入文件内容如下:\napiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: annotations: rbac.authorization.kubernetes.io/autoupdate: \u0026#34;true\u0026#34; labels: kubernetes.io/bootstrapping: rbac-defaults name: system:kube-apiserver-to-kubelet rules: - apiGroups: - \u0026#34;\u0026#34; resources: - nodes/proxy - nodes/stats - nodes/log - nodes/spec - nodes/metrics - pods/log verbs: - \u0026#34;*\u0026#34; --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: system:kube-apiserver namespace: \u0026#34;\u0026#34; roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:kube-apiserver-to-kubelet subjects: - apiGroup: rbac.authorization.k8s.io kind: User name: kubernetes # This role allows full access to the kubelet API apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: kubelet-api-admin labels: addonmanager.kubernetes.io/mode: Reconcile rules: - apiGroups: - \u0026#34;\u0026#34; resources: - nodes/proxy - nodes/log - nodes/stats - nodes/metrics - nodes/spec verbs: - \u0026#34;*\u0026#34; # This binding gives the kube-apiserver user full access to the kubelet API --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: kube-apiserver-kubelet-api-admin labels: addonmanager.kubernetes.io/mode: Reconcile roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: kubelet-api-admin subjects: - apiGroup: rbac.authorization.k8s.io kind: User name: kube-apiserver 执行以下命令:\nkubectl apply -f apiserver-to-kubelet-rbac.yaml 第三段落部署 Master顺利结束。\n« 二进制搭建 K8s - 2 部署 etcd 集群\n» 二进制搭建 K8s - 4 部署 Node\n"},{"id":115,"href":"/kubernetes/binary-build-k8s-04-deploy-worker/","title":"Binary Build K8s 04 Deploy Worker","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 二进制搭建 K8s - 4 部署 Node\n二进制搭建 K8s - 4 部署 Node # 写在前面 # 记录和分享使用二进制搭建 K8s 集群的详细过程,由于操作比较冗长,大概会分四篇写完:\n机器准备: 部署 etcd 集群: 部署 Master: 部署 Node: K8s 的 Node 上需要运行 kubelet 和 kube-proxy。本篇介绍在 Node 机器安装这两个组件,除此之外,安装通信需要的 cni 插件。\n本篇的执行命令需要在准备的两台Node机器上执行。\n安装 docker # 可以参照官网: https://docs.docker.com/engine/install/\n# 卸载老版本或重装 docker 时执行第一行 yum remove docker \\ docker-client \\ docker-client-latest \\ docker-common \\ docker-latest \\ docker-latest-logrotate \\ docker-logrotate \\ docker-engine -y # 安装 docker yum install -y yum-utils yum-config-manager \\ --add-repo \\ https://download.docker.com/linux/centos/docker-ce.repo yum install docker-ce docker-ce-cli containerd.io -y # 查看 Docker 版本 docker version 启动 Docker\nsystemctl enable docker systemctl start docker 安装 kubelet # cd /root/kubernetes/resources tar -zxvf ./kubernetes-node-linux-amd64.tar.gz mkdir /etc/kubernetes/{ssl,bin} -p cp kubernetes/node/bin/kubelet ./kubernetes/node/bin/kube-proxy /etc/kubernetes/bin cd /etc/kubernetes 准备 kubelet 配置文件\nvim kubelet 执行上行命令,在 k8s-node01 写入文件内容如下:\nKUBELET_ARGS=\u0026#34;--logtostderr=false \\ --v=2 \\ --log-dir=/var/log/kubernetes \\ --enable-server=true \\ --kubeconfig=/etc/kubernetes/kubelet.kubeconfig \\ --hostname-override=k8s-node01 \\ --network-plugin=cni \\ --bootstrap-kubeconfig=/etc/kubernetes/bootstrap.kubeconfig \\ --config=/etc/kubernetes/kubelet-config.yml \\ --cert-dir=/etc/kubernetes/ssl\u0026#34; 在 k8s-node02 写入文件内容如下:\nKUBELET_ARGS=\u0026#34;--logtostderr=false \\ --v=2 \\ --log-dir=/var/log/kubernetes \\ --enable-server=true \\ --kubeconfig=/etc/kubernetes/kubelet.kubeconfig \\ --hostname-override=k8s-node02 \\ --network-plugin=cni \\ --bootstrap-kubeconfig=/etc/kubernetes/bootstrap.kubeconfig \\ --config=/etc/kubernetes/kubelet-config.yml \\ --cert-dir=/etc/kubernetes/ssl\u0026#34; 准备 bootstrap.kubeconfig 文件\nvim /etc/kubernetes/bootstrap.kubeconfig 执行上行命令,写入文件内容如下:\napiVersion: v1 clusters: - cluster: certificate-authority: /etc/kubernetes/ssl/ca.pem server: https://192.168.115.131:6443 name: kubernetes contexts: - context: cluster: kubernetes user: kubelet-bootstrap name: default current-context: default kind: Config preferences: {} users: - name: kubelet-bootstrap user: token: d5c5d767b64db39db132b433e9c45fbc 注意:token 的值需要替换为 master 生成的 token.csv 中所用的 token。\n准备 kubelet-config.yml 文件\nvim kubelet-config.yml 执行上行命令,写入文件内容如下:\nkind: KubeletConfiguration apiVersion: kubelet.config.k8s.io/v1beta1 address: 0.0.0.0 port: 10250 readOnlyPort: 10255 cgroupDriver: cgroupfs clusterDNS: - 10.0.0.2 clusterDomain: cluster.local failSwapOn: false authentication: anonymous: enabled: false webhook: cacheTTL: 2m0s enabled: true x509: clientCAFile: /etc/kubernetes/ssl/ca.pem authorization: mode: Webhook webhook: cacheAuthorizedTTL: 5m0s cacheUnauthorizedTTL: 30s evictionHard: imagefs.available: 15% memory.available: 100Mi nodefs.available: 10% nodefs.inodesFree: 5% maxOpenFiles: 1000000 maxPods: 110 准备 kubelet.kubeconfig 文件\nvim kubelet.kubeconfig 执行上行命令,写入文件内容如下:\nkubelet.kubeconfig apiVersion: v1 clusters: - cluster: certificate-authority: /etc/kubernetes/ssl/ca.pem server: https://192.168.115.131:6443 name: kubernetes contexts: - context: cluster: kubernetes namespace: default user: default-auth name: default-context current-context: default-context kind: Config preferences: {} users: - name: default-auth user: client-certificate: /etc/kubernetes/ssl/kubelet-client-current.pem client-key: /etc/kubernetes/ssl/kubelet-client-current.pem 准备kubelet服务配置文件\nvim /usr/lib/systemd/system/kubelet.service 执行上行命令,写入文件内容如下:\n[Unit] Description=Kubelet Documentation=https://github.com/GoogleCloudPlatform/kubernetes After=docker.service Requires=docker.service [Service] EnvironmentFile=/etc/kubernetes/kubelet ExecStart=/etc/kubernetes/bin/kubelet $KUBELET_ARGS Restart=on-failure [Install] WantedBy=multi-user.target 启动 kubelet:\nsystemctl daemon-reload systemctl start kubelet systemctl enable kubelet systemctl status kubelet 给 Node 颁发证书,在 Master 上执行:\nkubectl get csr # 输出如下 NAME AGE SIGNERNAME REQUESTOR CONDITION node-csr-a-BmW9xMglOXlUdwBjD2QQphXLdu4iwtamEIIbhJKcY 10m kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending node-csr-zDDrVyKH7ug8fTUcDjdvDgh-f9rVCyoHuLMGaWbykAQ 10m kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending 得到证书的 NAME,给其 Approve:\nkubectl certificate approve node-csr-a-BmW9xMglOXlUdwBjD2QQphXLdu4iwtamEIIbhJKcY kubectl certificate approve node-csr-zDDrVyKH7ug8fTUcDjdvDgh-f9rVCyoHuLMGaWbykAQ 再次查看证书,证书的 CONDITION 就会更新了\nkubectl get csr # 输出如下 NAME AGE SIGNERNAME REQUESTOR CONDITION node-csr-a-BmW9xMglOXlUdwBjD2QQphXLdu4iwtamEIIbhJKcY 10m kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Approved,Issued node-csr-zDDrVyKH7ug8fTUcDjdvDgh-f9rVCyoHuLMGaWbykAQ 10m kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Approved,Issued 接下来使用查看 Node 的命令,应该可以获取到 Node 信息:\nkubectl get node # 输出如下 NAME STATUS ROLES AGE VERSION k8s-node01 NotReady \u0026lt;none\u0026gt; 50s v1.18.3 k8s-node02 NotReady \u0026lt;none\u0026gt; 56s v1.18.3 安装 kube-proxy # 准备 kube-proxy 配置文件\nvim kube-proxy 执行上行命令,写入文件内容如下:\nKUBE_PROXY_ARGS=\u0026#34;--logtostderr=false \\ --v=2 \\ --log-dir=/var/log/kubernetes \\ --config=/etc/kubernetes/kube-proxy-config.yml\u0026#34; 准备 kube-proxy-config.yml 文件\nvim /etc/kubernetes/kube-proxy-config.yml 执行上行命令,在 k8s-node01 写入文件内容如下:\nkind: KubeProxyConfiguration apiVersion: kubeproxy.config.k8s.io/v1alpha1 address: 0.0.0.0 metricsBindAddress: 0.0.0.0:10249 iclientConnection: kubeconfig: /etc/kubernetes/kube-proxy.kubeconfig hostnameOverride: k8s-node01 clusterCIDR: 10.0.0.0/24 mode: ipvs ipvs: scheduler: \u0026#34;rr\u0026#34; iptables: masqueradeAll: true 在 k8s-node02 写入文件内容如下:\nkind: KubeProxyConfiguration apiVersion: kubeproxy.config.k8s.io/v1alpha1 address: 0.0.0.0 metricsBindAddress: 0.0.0.0:10249 clientConnection: kubeconfig: /etc/kubernetes/kube-proxy.kubeconfig hostnameOverride: k8s-node02 clusterCIDR: 10.0.0.0/24 mode: ipvs ipvs: scheduler: \u0026#34;rr\u0026#34; iptables: masqueradeAll: true 准备 kube-proxy.kubeconfig 文件\nvim /etc/kubernetes/kube-proxy.kubeconfig 执行上行命令,写入文件内容如下:\napiVersion: v1 clusters: - cluster: certificate-authority: /etc/kubernetes/ssl/ca.pem server: https://192.168.115.131:6443 name: kubernetes contexts: - context: cluster: kubernetes user: kube-proxy name: default current-context: default kind: Config preferences: {} users: - name: kube-proxy user: client-certificate: /etc/kubernetes/ssl/kube-proxy.pem client-key: /etc/kubernetes/ssl/kube-proxy-key.pem 准备 kube-proxy 服务配置文件\nvim /usr/lib/systemd/system/kube-proxy.service 执行上行命令,写入文件内容如下:\n[Unit] Description=Kube-Proxy Documentation=https://github.com/GoogleCloudPlatform/kubernetes After=network.target Requires=network.target [Service] EnvironmentFile=/etc/kubernetes/kube-proxy ExecStart=/etc/kubernetes/bin/kube-proxy $KUBE_PROXY_ARGS Restart=on-failure [Install] WantedBy=multi-user.target 启动 kubelet:\nsystemctl daemon-reload systemctl start kube-proxy systemctl enable kube-proxy systemctl status kube-proxy 部署 cni 网络插件 # cd /root/kubernetes/resources mkdir -p /opt/cni/bin /etc/cni/net.d tar -zxvf cni-plugins-linux-amd64-v0.8.6.tgz -C /opt/cni/bin 部署 Flannel 集群网络 # 需要在 Master 机器上执行\ncd /root/kubernetes/resources kubectl apply -f kube-flannel.yml 创建角色绑定\nkubectl create clusterrolebinding kube-apiserver:kubelet-apis --clusterrole=system:kubelet-api-admin --user kubernetes K8s 集群测试 # 部署一个 nginx 的 deployment:\nkubectl create deployment nginx --image=nginx # 在等待几秒后,获取 deployment kubectl get deployment ifconfig cni0 kubectl expose deployment nginx --port=80 --type=NodePort kubectl get svc 可以看到 nginx 已经启动成功。\nNAME READY UP-TO-DATE AVAILABLE AGE nginx 1/1 1 1 7m7s 注意:如果启动失败,可能是由于网络原因拉取镜像失败导致。可以通过 kubectl describe pod \u0026lt;pod-name\u0026gt; 查看。\n使用 service 暴露 K8s 集群内部 Pod 服务:\nkubectl expose deployment nginx --port=80 --type=NodePort # 获取 service kubectl get svc 可以看到,service 将 nginx 的服务转发到了 31839 端口\nNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.0.0.1 \u0026lt;none\u0026gt; 443/TCP 10h nginx NodePort 10.0.0.101 \u0026lt;none\u0026gt; 80:31839/TCP 10s 此时,我们在 Node 机器上使用该端口访问 nginx,可以看到成功访问。\n[root@k8s-node01]# curl 192.168.115.132:31839 \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;Welcome to nginx!\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1\u0026gt;Welcome to nginx!\u0026lt;/h1\u0026gt; \u0026lt;p\u0026gt;If you see this page, the nginx web server is successfully installed and working. Further configuration is required.\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;For online documentation and support please refer to \u0026lt;a href=\u0026#34;http://nginx.org/\u0026#34;\u0026gt;nginx.org\u0026lt;/a\u0026gt;.\u0026lt;br/\u0026gt; Commercial support is available at \u0026lt;a href=\u0026#34;http://nginx.com/\u0026#34;\u0026gt;nginx.com\u0026lt;/a\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;\u0026lt;em\u0026gt;Thank you for using nginx.\u0026lt;/em\u0026gt;\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 好了,至此第四段落部署 Node也顺利结束。\n结束语 # 在使用二进制搭建 K8s 集群的过程中,搭建的过程参考了很多园友的博客。由于我是使用最新的 K8s、etcd 版本搭建的,遇到了很多的问题,但没有关系,好事多磨。\n在遇到问题的时候,几乎都是通过查看 K8s 中组件的运行状态和日志来寻找问题根源和解决方案的。\n大部分问题都是出在配置方面,或是文件路径配置问题,或是新版本的配置不兼容问题。\n« 二进制搭建 K8s - 3 部署 Master\n» Kubernetes 0-1 尝试理解云原生\n"},{"id":116,"href":"/kubernetes/cloud-native-understood/","title":"Cloud Native Understood","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 尝试理解云原生\nKubernetes 0-1 尝试理解云原生 # 最初的云原生定义:\n应用容器化 面向微服务架构 应用支持容器编排调度 重新定义云原生:\n云原生技术有利于各组织在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展的应用。云原生的代表技术包括容器、服务网格、微服务、不可变基础设施和声明式API。\n这些技术能够构建容错性好、易于管理和便于观察的松耦合系统。结合可靠的自动化手段,云原生技术使工程师能够轻松地对系统作出频繁和可预测的重大变更。\n云原生本身不能称为是一种架构,它首先是一种基础设施,运行在其上的应用称作云原生应用,只有符合云原生设计哲学的应用架构才叫云原生应用架构。\n« 二进制搭建 K8s - 4 部署 Node\n» 集群联邦\n"},{"id":117,"href":"/kubernetes/cluster-federation/","title":"Cluster Federation","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 集群联邦\n集群联邦 # 云服务提供商的集群联邦是一种将多个独立的 Kubernetes 集群组合在一起的方法。这种方法允许用户在多个集群之间共享资源,例如 Pods、Services、Deployments 等。集群联邦的目标是在多个集群上引入新的控制面板,提供一个统一的视图,使用户可以在多个集群之间无缝地部署和管理应用程序。\n概念 # 数据中心:Region,是一个物理位置,包含多个可用性区域。 可用性区域:Availability Zone(AZ),是一个独立的数据中心,包含 N 多服务器节点。 管理集群:或者宿主集群,是一个集群联邦的核心,用于管理多个工作集群。 联邦集群:或者工作集群,是一个普通的 Kubernetes 集群,用于部署工作负载。 集群联邦需要解决的问题 # 跨集群服务发现:连通多个集群,使得服务可以在多个集群之间发现,让请求跨越集群边界。 跨集群调度:将负载调度到多个集群,保证服务的稳定性以及可用性。 集群联邦开源项目 # Kubefed # 项目地址\n之前由 Kubernetes 官方多集群兴趣小组开发,目前已经停止维护。\n架构原理:\n将联邦资源(FederationResource)从管理集群同步到工作集群。\n这其中通过三个概念来实现:\nTemplate:定义了联邦资源的模板,用于指定联邦资源的属性 Placement:定义了联邦集群资源的部署位置,用于指定联邦资源的部署位置。 Overrides:定义了联邦集群资源的覆盖规则,用于覆盖联邦资源的属性。 kubefed 为所有的 Kubernetes 原生资源提供了对应的联邦资源,例如 FederatedService、FederatedDeployment 等。\n联邦资源中定义了原生资源的 Template、又通过 Overrides 定义了资源同步到不同的工作集群时需要做的变更,例如:\nkind: FederatedDeployment ... spec: ... overrides: # Apply overrides to cluster1 - clusterName: cluster1 clusterOverrides: # Set the replicas field to 5 - path: \u0026#34;/spec/replicas\u0026#34; value: 5 # Set the image of the first container - path: \u0026#34;/spec/template/spec/containers/0/image\u0026#34; value: \u0026#34;nginx:1.17.0-alpine\u0026#34; # Ensure the annotation \u0026#34;foo: bar\u0026#34; exists - path: \u0026#34;/metadata/annotations\u0026#34; op: \u0026#34;add\u0026#34; value: foo: bar # Ensure an annotation with key \u0026#34;foo\u0026#34; does not exist - path: \u0026#34;/metadata/annotations/foo\u0026#34; op: \u0026#34;remove\u0026#34; # Adds an argument `-q` at index 0 of the args list # this will obviously shift the existing arguments, if any - path: \u0026#34;/spec/template/spec/containers/0/args/0\u0026#34; op: \u0026#34;add\u0026#34; value: \u0026#34;-q\u0026#34; Karmada # 项目地址 管理集群包含三个主要组件:\nAPIServer ControllerManager:将联邦资源同步到工作集群并管理联邦资源的生命周期。 Scheduler Karmada 将资源模板转换成成员集群的资源需要经过以下几个步骤:\nDeployment、Service、ConfigMap 等资源模板经过 PropagationPolicy 生成一组 ResourceBinding,每个 ResourceBinding 都对应特定的成员集群; ResourceBinding 根据 OverridePolicy 改变一些资源以适应的不同成员集群,例如:集群名等参数,这些资源定义会存储在 Work 对象中; Work 对象中存储的资源定义会被提交到成员集群中,成员集群中的 Controller Manager 等控制面板组件会负责这些资源的处理,例如:根据 Deployment 创建 Pod 等。 如下示例:\n# propagationpolicy.yaml apiVersion: policy.karmada.io/v1alpha1 kind: PropagationPolicy metadata: name: example-policy spec: resourceSelectors: - apiVersion: apps/v1 kind: Deployment name: nginx placement: clusterAffinity: clusterNames: - member1 # overridepolicy.yaml apiVersion: policy.karmada.io/v1alpha1 kind: OverridePolicy metadata: name: example-override namespace: default spec: resourceSelectors: - apiVersion: apps/v1 kind: Deployment name: nginx overrideRules: - targetCluster: clusterNames: - member1 overriders: plaintext: - path: \u0026#34;/metadata/annotations\u0026#34; operator: add value: foo: bar « Kubernetes 0-1 尝试理解云原生\n» 了解 ConfigMap\n"},{"id":118,"href":"/kubernetes/configmap-understood/","title":"Configmap Understood","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 了解 ConfigMap\n了解 ConfigMap # 几乎所有的应用都需要配置信息,在 K8s 部署应用,最佳实践是将应用的配置信息(环境变量或者配置文件)和程序本身分离,这样配置信息的更新和复用都可以更简单,也使得程序更加灵活。\nKubernetes 允许将配置选项分离到单独的资源对象 ConfigMap 中,本质上是一个键值对映射,值可以是一个短 string 串,也可以是一个完整的配置文件。\n本篇主要介绍 ConfigMap 资源的创建和使用。\nConfigMap 的创建 # 可以直接通过 kubectl create configmap 命令创建,也可以先编写 configmap 的 yaml 文件再使用kubectl apply -f \u0026lt;filename\u0026gt;创建,推荐使用后者。\n单行命令创建 ConfigMap # 创建一个键值对的 ConfigMap: kubectl create configmap first-config --from-literal=user=admin 创建完成之后,使用 kubectl describe configmap first-config 查看,可以看到这个 configmap 的键值内容。\n可以使用多组 --from-literal=\u0026lt;key\u0026gt;=\u0026lt;value\u0026gt; 参数,在 configmap 中定义多组键值对。\n创建一个文件内容的 ConfigMap 假如我当前有一个配置文件 app.json,文件内容如下:\n{ \u0026#34;App\u0026#34;: \u0026#34;MyApp\u0026#34;, \u0026#34;Version\u0026#34;: \u0026#34;v1.0\u0026#34; } 使用以下命令创建 ConfigMap:\nkubectl create configmap second-config --from-file=app.json 创建完成之后,使用 kubectl describe configmap second-config 查看 configmap 的键值内容:\n默认使用文件名称 app.json 作为键值对的 key,也可以通过 --from-file=app_config=app.json 指定key为 app_config;\n可以使用多组 --from-file=\u0026lt;key\u0026gt;=\u0026lt;filename\u0026gt; 参数,在 configmap 中定义多组文件;\n--from-file= 后面可以直接跟某个文件路径,这样会将目录下的所有文件引入到 ConfigMap;\n--from-literal 和 --from-file 可以共同使用,键值合并。\n删除创建的 first-config 和 second-config:\nkubectl delete configmap first-config kubectl delete configmap second-config 基于资源清单文件创建 ConfigMap # 创建一个键值对的 ConfigMap: 首先定义 ConfigMap 的资源文件 first-config.yaml,定义如下:\nvim first-config.yaml apiVersion: v1 kind: ConfigMap metadata: name: first-config data: user: \u0026#34;admin\u0026#34; 使用 kubectl apply 命令创建 ConfigMap 资源:\nkubectl apply -f first-config.yaml 创建完成之后,使用 kubectl describe configmap first-config 查看,可以看到这个 configmap 的键值内容,结果与上文第一次创建的 first-configmap 是一致的。\n可以在 data 下定义多组键值对。\n创建一个文件内容的 ConfigMap 首先定义 ConfigMap 的资源文件 second-config.yaml,定义如下:\nvim second-config.yaml apiVersion: v1 kind: ConfigMap metadata: name: second-config data: app.json: | { \u0026#34;App\u0026#34;: \u0026#34;MyApp\u0026#34;, \u0026#34;Version\u0026#34;: \u0026#34;v1.0\u0026#34; } 使用 kubectl apply 命令创建 ConfigMap 资源:\nkubectl apply -f second-config.yaml 创建完成之后,使用 kubectl describe configmap second-config 查看,可以看到这个 configmap 的键值内容,结果与上文第一次创建的 second-configmap 是一致的。\n可以在 data 下定义多组文件,也可以和键值对一起定义;\n其本质也是键值对,只是 value 为多行格式的文本内容;\n对 value 有格式要求,”|“接换行并且文件内容的每行按照 key 都往后退格。\n删除创建的 first-config 和 second-config:\nkubectl delete configmap first-config kubectl delete configmap second-config ConfigMap 的使用 # 我们创建了 ConfigMap 后,我们的 Pod 资源就可以利用了。主要有两种方式使用 ConfigMap:\n使用 ConfigMap 作为容器的环境变量 使用 ConfigMap 作为 Volume 向容器提供文件 使用 ConfigMap 作为容器的环境变量 # 假如有一个名为 first-config的 ConfigMap,里面包含了一个键为 user,我想将这个 ConfigMap 中 user 键用到我的环境变量 USER_NAME 中,可以使用如下方式:\n... env: - name: USER_NAME valueFrom: configMapKeyRef: name: first-config key: user ... 如果有一个名为 second-config ConfigMap 中包含多个键如 HOST,PORT,USER_NAME,PASSWORD,我想将这个 ConfigMap 中所有的键都用到我的环境变量中,可以使用如下方式:\n... spec: container: - image: \u0026lt;some-image\u0026gt; envFrom: - prefix: DB_ configMapRef: name: second-config ... 容器将会生成 DB_HOST,DB_PORT,DB_USER_NAME,DB_PASSWORD 四个环境变量,prefix 也可以不配置,则直接使用 ConfigMap 的键。\n注意:\nconfigMapRef 与上面 configMapKeyRef 的区别; 如果ConfigMap中有一个为 USER-NAME 键,那么将不会生成 DB_USER-NAME 的环境变量,因为 DB_USER-NAME 不是一个合法的环境变量名称。 使用 ConfigMap 作为 Volume 向容器提供文件 # 从这里开始,结合实际演练可能效果会好一点。\n我仍然使用 poneding/mockapi:v1 镜像运行模拟应用程序,程序的代码在这: https://github.com/poneding/for-docker\n这个应用程序根目录下存在 mysettings/app.json 和 mysettings/secret.json 两个配置文件,需要提前说明的是这个镜像中已经存在了这两个文件,并且文件内容如下:\nmysettings/app.json:\n{ \u0026#34;App\u0026#34;: \u0026#34;Hello Web\u0026#34;, \u0026#34;Version\u0026#34;: \u0026#34;v1\u0026#34; } mysettings/secret.json:\n{ \u0026#34;UserName\u0026#34;: \u0026#34;admin\u0026#34;, \u0026#34;Password\u0026#34;: \u0026#34;123456\u0026#34; } 我现在将这两个配置文件放在我的 ConfigMap 中,并且内容与镜像中略微区别,用于区分程序读取配置源自 ConfigMap,然后使用 Volume 的形式将文件挂载到应用中去。\n首先,定义 ConfigMap 文件如下:\nvim mockapi-mysettings.yaml apiVersion: v1 kind: ConfigMap metadata: name: mockapi-mysettings data: app.json: | { \u0026#34;App\u0026#34;:\u0026#34;Hello Web [From ConfigMap]\u0026#34;, \u0026#34;Version\u0026#34;:\u0026#34;v1 [From ConfigMap]\u0026#34; } secret.json: | { \u0026#34;UserName\u0026#34;:\u0026#34;admin [From ConfigMap]\u0026#34;, \u0026#34;Password\u0026#34;:\u0026#34;123456 [From ConfigMap]\u0026#34; } 定义 Pod 文件如下:\nvim mockapi-pod.yaml apiVersion: v1 kind: Pod metadata: name: mockapi spec: containers: - name: mockapi image: poneding/mockapi:v1 ports: - containerPort: 80 volumeMounts: - mountPath: /app/mysettings name: mockapi-mysettings volumes: - name: mockapi-mysettings configMap: name: mockapi-mysettings Apply 资源:\nkubectl apply -f mockapi-mysettings.yaml kubectl apply -f mockapi-pod.yaml 等待 mockapi 的 Pod 起来之后,我们调用 http://\u0026lt;pod-ip\u0026gt;/configuration/mysettings 查看挂载的配置文件是否生效:\nmockapi 应用访问 /configuration/mysettings 会获取 app.json 和 secret.json 里面的总共四个配置项,然后输出到客户端。\n可以看到,已经获取到了 ConfigMap 里面的配置,进入容器也可以看到里面的问题件内容:\n修改 ConfigMap 自动更新挂载文件:\n现在,如果我想要修改我的配置,比如我的密码修改成了 abcdefg,我不用重新启动 Pod,我只需要修改 ConfigMap 文件:\napiVersion: v1 kind: ConfigMap metadata: name: mockapi-mysettings data: app.json: | { \u0026#34;App\u0026#34;:\u0026#34;Hello Web [From ConfigMap]\u0026#34;, \u0026#34;Version\u0026#34;:\u0026#34;v1 [From ConfigMap]\u0026#34; } secret.json: | { \u0026#34;UserName\u0026#34;:\u0026#34;admin [From ConfigMap]\u0026#34;, \u0026#34;Password\u0026#34;:\u0026#34;abcdefg [From ConfigMap]\u0026#34; } 然后重新 Apply 资源:\nkubectl apply -f mockapi-mysettings.yaml 配置文件的更新需要 1-2 分钟的时间,我们可以连续观察文件的更新情况:\n可以看到,过段时间后配置文件更新了。\n这里遇到了一个小问题,继续访问 /configuration/mysettings,应用程序并没有热更新,但是我使用 hostPath 的 Volume 形式挂载时,修改配置文件是可以热更新的,希望有缘人能给我解答吧。\n多文件目录下挂载单个文件:\n在实际的应用中,可能 secret.json 的变动更为频繁,而 app.json 文件几乎不会变动,我现在只想使用 ConfigMap 传递 secret.json 文件,而不再传递 app.json,这时,修改 ConfigMap 文件如下:\napiVersion: v1 kind: ConfigMap metadata: name: mockapi-mysettings data: secret.json: | { \u0026#34;UserName\u0026#34;:\u0026#34;admin [From ConfigMap]\u0026#34;, \u0026#34;Password\u0026#34;:\u0026#34;123456 [From ConfigMap]\u0026#34; } 然后,重新 Apply 资源:\nkubectl apply -f mockapi-mysettings.yaml 这时候,我们继续访问 /configuration/mysettings:\n很遗憾的发现,我们的 app.json 的配置项都失效了,我们接着去容器中一探究竟:\n原来,app.json 文件已经不存在了。\n大概推测一下可以知道,**如果使用 ConfigMap 挂载到容器的一个目录,那么该目录会被 ConfigMap 所覆盖。**那么如果只挂载单个目录呢?使用 subPath!\n修改 Pod 文件如下:\napiVersion: v1 kind: Pod metadata: name: mockapi spec: containers: - name: mockapi image: poneding/mockapi:v1 ports: - containerPort: 80 volumeMounts: - mountPath: /app/mysettings/secret.json name: mockapi-mysettings subPath: secret.json volumes: - name: mockapi-mysettings configMap: name: mockapi-mysettings mountPath 是容器中目标文件路径;\nsubPath 是ConfgMap文件的 Key 值。\n然后,重新 Apply 资源:\nkubectl delete -f mockapi-pod.yaml kubectl apply -f mockapi-pod.yaml 等待Pod运行后,我们访问 /configuration/mysettings,并查看 mysettings 目录下文件:\n可以看到成功的挂载 secret.json 文件而没有挂载 app.json 文件,[From ConfigMap] 就是证明。\n需要额外注意的是,在使用过程中发现,使用 subPath 挂载单文件的话,ConfigMap 的更新不会同步更新到容器对应文件中。这时候的解决办法是一个目录只存放一个配置文件,然后 ConfigMap 挂载到目录。\n如果一个 ConfigMap(app-config)中定义了多个文件(app1.json、app2.json),但是 app1-pod 中只会使用到 app1.json,可以使用如下方式:\n... volumeMounts: - mountPath: /config/path name: mockapi-mysettings volumes: - name: app-config configMap: name: app-config items: - key: app1.json path: app.json items 的 key 用于指定 ConfigMap 的 key,path 则可以定义为程序中的文件名。\n« 集群联邦\n» 定期删除 ElasticSearch 日志索引\n"},{"id":119,"href":"/kubernetes/delete-es-log-index-scheduler/","title":"Delete Es Log Index Scheduler","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 定期删除 ElasticSearch 日志索引\n定期删除 ElasticSearch 日志索引 # 背景 # 当前在 K8s 集群中部署了一套 EFK 日志监控系统,日复一日,ElasticSearch收集的数据越来越多,内存以及存储占用越来越高,需要定期来删除老旧的日志数据,来解放内存和存储空间,考虑到 K8s 中 cronjob 的功能特性,打算使用它制定一个es日志索引清除脚本,定时清除日志数据。\nConfigMap # 这个configMap用来存储一个shell脚本,该shell脚本执行日志索引清除操作:\napiVersion: v1 kind: ConfigMap metadata: name: es-log-indices-clear-configmap namespace: efk data: clean-indices.sh: | #/bin/bash LAST_MONTH_DATE=`date -d \u0026#34;1 month ago\u0026#34; +\u0026#34;%Y.%m.%d\u0026#34;` echo Start clear es indices *-${LAST_MONTH_DATE} curl -XDELETE http://elasticsearch:9200/*-${LAST_MONTH_DATE} --- 说明:这里我配置的configmap所在命名空间和efk部署的命名空间一致,并且es的Service的名称是elasticsearch,所以可以使用 http://elasticsearch:9200访问到es服务,否则的话需要是无法访问到的,所以这里需要根据具体情况配置es的服务地址;\nCronJob # CronJob使用了 poneding/sparrow\napiVersion: batch/v1beta1 kind: CronJob metadata: name: clean-indices namespace: efk spec: schedule: \u0026#34;0 0 1/1 * *\u0026#34; jobTemplate: spec: template: spec: containers: - name: auto-recycle-job image: poneding/sparrow args: [\u0026#34;/bin/sh\u0026#34;, \u0026#34;/job/clean-indices.sh\u0026#34;] volumeMounts: - name: vol mountPath: /job volumes: - name: vol configMap: name: es-log-indices-clear-configmap restartPolicy: OnFailure 说明:\n配置 cronjob 镜像为 poneding/sparrow,是自制的镜像,因为 busybox 镜像里面没有集成 curl 命令并且 date 的命令也不完全; 将 configmap 挂载到 job 目录下; 配置 cronjob 调度时间为 0 0 1/1 * *,每天的零点执行job目录下的 clean-indices.sh 脚本文件; 遇到的问题 # 在实际测试过程中遇到了一个问题,我们的脚本是通过 curl DElETE 方法来调用 es 的删除索引 api,我们使用 *-yyyy.MM.dd 通配符匹配并删除符合的 es 索引,但是 ES 默认是有自己的保护机制的,默认禁止使用通配符或 _all 删除索引,这个做法我们也很容易理解,毕竟不能允许如此轻易的大量删除。\n/var/www/app # curl -XDELETE http://elasticsearch:9200/*-2020.10.09 {\u0026#34;error\u0026#34;:{\u0026#34;root_cause\u0026#34;:[{\u0026#34;type\u0026#34;:\u0026#34;illegal_argument_exception\u0026#34;,\u0026#34;reason\u0026#34;:\u0026#34;Wildcard expressions or all indices are not allowed\u0026#34;}],\u0026#34;type\u0026#34;:\u0026#34;illegal_argument_exception\u0026#34;,\u0026#34;reason\u0026#34;:\u0026#34;Wildcard expressions or all indices are not allowed\u0026#34;},\u0026#34;status\u0026#34;:400} 解决方案是设置 elasticsearch 配置项:action.destructive_requires_name 为 false.\n遗憾的是无论我通过修改es的配置文件 elasticsearch.yml(位于 /usr/share/elasticsearch/config 路径下)还是增加环境变量都没起到效果:\n... # es-configmap data: elasticsearch.yml: | cluster.name: \u0026#34;es-cluster\u0026#34; network.host: 0.0.0.0 action.destructive_requires_name: false ... ... # es-statefulset env: - name: cluster.name value: es-cluster - name: action.destructive_requires_name value: \u0026#34;false\u0026#34; ... 最后,又找到了一个方法:使用 ES 语法修改配置。\nPUT /_cluster/settings { \u0026#34;persistent\u0026#34; : { \u0026#34;action.destructive_requires_name\u0026#34; : \u0026#34;false\u0026#34; } } 这时候再次使用通配符执行删除:\n/var/www/app# curl -XDELETE http://elasticsearch:9200/*-2020.10.09 {\u0026#34;acknowledged\u0026#34;:true} 虽然问题是解决了,但是修改配置文件不生效,还是令人困扰,可能是由于 es 集群的关系,单节点的配置并未对集群生效。\n追加,貌似可以从这得到答案 =\u0026gt; 官方文档: https://www.elastic.co/guide/en/elasticsearch/reference/current/settings.html\n« 了解 ConfigMap\n» 强制删除 K8s 资源\n"},{"id":120,"href":"/kubernetes/delete-k8s-resource-force/","title":"Delete K8s Resource Force","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 强制删除 K8s 资源\n强制删除 K8s 资源 # 强制删除 Pod # kubectl delete po \u0026lt;pod\u0026gt; -n \u0026lt;namespace\u0026gt; --force --grace-period=0 强制删除 PVC # kubectl patch pv \u0026lt;pv\u0026gt; -n \u0026lt;namespace\u0026gt; -p \u0026#39;{\u0026#34;metadata\u0026#34;:{\u0026#34;finalizers\u0026#34;:null}}\u0026#39; 强制删除 PV # kubectl patch pvc \u0026lt;pvc\u0026gt; -n \u0026lt;namespace\u0026gt; -p \u0026#39;{\u0026#34;metadata\u0026#34;:{\u0026#34;finalizers\u0026#34;:null}}\u0026#39; 强制删除命名空间 # 在删除 kubesphere 的命名空间时遇到无法删除成功的现象,命名空间一直处于 Terminating 状态。\n$ kubectl get ns |grep kubesphere NAME STATUS AGE kubesphere-controls-system Terminating 22d kubesphere-monitoring-system Terminating 21d 在网上找到了一种解决方案。\n首先获取命名空间的 json 文件,\nkubectl get ns kubesphere-controls-system -o json \u0026gt; temp.json 修改 temp.json 如下:\n{ \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;kind\u0026#34;: \u0026#34;Namespace\u0026#34;, \u0026#34;metadata\u0026#34;: { ... \u0026#34;finalizers\u0026#34;: [ \u0026#34;finalizers.kubesphere.io/namespaces\u0026#34; //删除此行 ], ... } 还有一种情况不同,可能需要删除的地方如下:\n{ \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;kind\u0026#34;: \u0026#34;Namespace\u0026#34;, ... \u0026#34;spec\u0026#34;: { \u0026#34;finalizers\u0026#34;: [ // 删除 \u0026#34;kubernetes\u0026#34; // 删除 ] // 删除 }, ... } 完成文件修改后,本地代理 kubernetes service:\nkubectl proxy --port 8081 以上操作会占用当前终端窗口,另外开启终端,执行命令:\ncurl -k -H \u0026#34;Content-Type:application/json\u0026#34; -X PUT --data-binary @temp.json http://127.0.0.1:8081/api/v1/namespaces/kubesphere-controls-system/finalize 执行以上命令大致输出如下:\n$ curl -k -H \u0026#34;Content-Type:application/json\u0026#34; -X PUT --data-binary @kcs.json http://127.0.0.1:8081/api/v1/namespaces/kubesphere-controls-system/finalize { \u0026#34;kind\u0026#34;: \u0026#34;Namespace\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;metadata\u0026#34;: { ... \u0026#34;deletionGracePeriodSeconds\u0026#34;: 0, \u0026#34;labels\u0026#34;: { \u0026#34;kubesphere.io/namespace\u0026#34;: \u0026#34;kubesphere-controls-system\u0026#34;, \u0026#34;kubesphere.io/workspace\u0026#34;: \u0026#34;system-workspace\u0026#34; }, ... \u0026#34;finalizers\u0026#34;: [ \u0026#34;finalizers.kubesphere.io/namespaces\u0026#34; ] }, \u0026#34;spec\u0026#34;: { }, \u0026#34;status\u0026#34;: { \u0026#34;phase\u0026#34;: \u0026#34;Terminating\u0026#34;, \u0026#34;conditions\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;NamespaceDeletionDiscoveryFailure\u0026#34;, \u0026#34;status\u0026#34;: \u0026#34;False\u0026#34;, \u0026#34;lastTransitionTime\u0026#34;: \u0026#34;2021-01-26T07:22:06Z\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;ResourcesDiscovered\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;All resources successfully discovered\u0026#34; }, { \u0026#34;type\u0026#34;: \u0026#34;NamespaceDeletionGroupVersionParsingFailure\u0026#34;, \u0026#34;status\u0026#34;: \u0026#34;False\u0026#34;, \u0026#34;lastTransitionTime\u0026#34;: \u0026#34;2021-01-26T07:22:06Z\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;ParsedGroupVersions\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;All legacy kube types successfully parsed\u0026#34; }, { \u0026#34;type\u0026#34;: \u0026#34;NamespaceDeletionContentFailure\u0026#34;, \u0026#34;status\u0026#34;: \u0026#34;False\u0026#34;, \u0026#34;lastTransitionTime\u0026#34;: \u0026#34;2021-01-26T07:23:13Z\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;ContentDeleted\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;All content successfully deleted\u0026#34; } ] } } 这时候顽固的的命名空间已经清除掉了。\n« 定期删除 ElasticSearch 日志索引\n» Gateway API 实践\n"},{"id":121,"href":"/kubernetes/gateway-api-practice/","title":"Gateway API Practice","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Gateway API 实践\nGateway API 实践 # Gateway API 还在积极开发中,目前已经发布了 v1.0.0 版本。可以通过 gateway-api 文档 获取最新进展。\nGateway API 概述 # Gateway API 是一个 Kubernetes 的扩展 API,它定义了一套 API 来管理网关、路由、TLS 等资源对象,可以用来替代传统的 Ingress。\n和 Ingress 一样,Gateway API 也是一个抽象层,它定义了一套 API 接口,这些接口由社区中的不同厂商来实现,比如 nginx、envoy、traefik 等。\nAPI 清单 # GatewayClass Gateway HTTPRoute GRPCRoute BackendTLSPolicy ReferenceGrant 安装 Gateway API # # 安装最新版 gateway-api CRDs export LATEST=$(curl -s https://api.github.com/repos/kubernetes-sigs/gateway-api/releases/latest | jq -r .tag_name) kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/$LATEST/crds.yaml 安装 Gateway Controller # gateway-api 只是定义了一套 API,需要 gateway-controller 来实现这些 API。目前已经有许多厂商实现了 gateway-controller,比如 nginx、envoy、traefik 等。\n下面介绍如何安装 nginx-gateway-fabric:\n# 安装最新版 gateway-controller export LATEST=$(curl -s https://api.github.com/repos/nginxinc/nginx-gateway-fabric/releases/latest | jq -r .tag_name) kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/$LATEST/nginx-gateway.yaml 配置 nginx-gateway 使用主机网络:\nkubectl patch deployment nginx-gateway -n nginx-gateway --type=\u0026#39;json\u0026#39; -p=\u0026#39;[{\u0026#34;op\u0026#34;: \u0026#34;add\u0026#34;, \u0026#34;path\u0026#34;: \u0026#34;/spec/template/spec/hostNetwork\u0026#34;, \u0026#34;value\u0026#34;: true}]\u0026#39; 注意:配置使用主机网络之后,宿主机的 80 端口将被占用,因此一个节点只允许调度一个 nginx-gateway Pod,当更新 nginx-gateway 时,您可能需要先停止旧的 Pod,再启动新的 Pod。\n在使用过程中,发现有可能由于宿主机的差异导致 nginx-gateway 健康检查无法通过,Pod 不能正常启动。可以通过修改配置 allowPrivilegeEscalation 字段来解决:\n# 使用主机网络,端口被占用,需要先停止 nginx-gateway kubectl scale deployment nginx-gateway -n nginx-gateway --replicas=0 kubectl patch deployment nginx-gateway -n nginx-gateway --type=\u0026#39;json\u0026#39; -p=\u0026#39;[{\u0026#34;op\u0026#34;: \u0026#34;replace\u0026#34;, \u0026#34;path\u0026#34;: \u0026#34;/spec/template/spec/containers/0/securityContext/allowPrivilegeEscalation\u0026#34;, \u0026#34;value\u0026#34;: true}]\u0026#39; kubectl scale deployment nginx-gateway -n nginx-gateway --replicas=1 实践 # 简单网关路由 # 部署 nginx deployment,并且暴露服务:\nkubectl create deployment nginx --image=nginx kubectl expose deployment nginx --name=nginx --port=80 --target-port=80 部署 nginx gateway \u0026amp; httpRoute:\n```bash kubectl apply -f - \u0026lt;\u0026lt;EOF apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: gateway spec: gatewayClassName: nginx listeners: - name: http port: 80 protocol: HTTP hostname: \u0026#34;*.mydomain.com\u0026#34; --- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: nginx spec: parentRefs: - name: gateway sectionName: http hostnames: - \u0026#34;nginx.mydomain.com\u0026#34; rules: - matches: - path: type: PathPrefix value: / backendRefs: - name: nginx port: 80 TLS 网关路由:CertManager + Let\u0026rsquo;s Encrypt # 特定域名网关:\napiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: mydomain-cert-issuer spec: acme: email: poneding@gmail.com server: https://acme-v02.api.letsencrypt.org/directory privateKeySecretRef: name: mydomain-cert-issuer-account-key solvers: - http01: gatewayHTTPRoute: parentRefs: - name: nginx-gateway namespace: nginx-gateway kind: Gateway selector: dnsNames: - \u0026#34;nginx.mydomain.com\u0026#34; --- apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: nginx-gateway namespace: nginx-gateway annotations: cert-manager.io/cluster-issuer: mydomain-cert-issuer spec: gatewayClassName: nginx listeners: - name: nginx-mydomain-https hostname: \u0026#39;nginx.mydomain.com\u0026#39; protocol: HTTPS port: 443 allowedRoutes: namespaces: from: All tls: certificateRefs: - group: \u0026#34;\u0026#34; kind: Secret name: nginx-mydomain-https-cert mode: Terminate --- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: nginx spec: parentRefs: - name: nginx-gateway namespace: nginx-gateway sectionName: nginx-mydomain-https hostnames: - \u0026#34;nginx.mydomain.com\u0026#34; rules: - matches: backendRefs: - name: nginx port: 80 通配符域名(泛域名)网关:\napiVersion: v1 kind: Secret metadata: name: cloudflare-api-token namespace: cert-manager type: Opaque stringData: api-token: [your_cloudflare_api_token] --- apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: mydomain-cert-issuer spec: acme: email: poneding@gmail.com server: https://acme-v02.api.letsencrypt.org/directory privateKeySecretRef: name: mydomain-cert-issuer-account-key solvers: - dns01: cloudflare: apiTokenSecretRef: name: cloudflare-api-token key: api-token selector: dnsNames: - \u0026#34;*.gateway.mydomain.com\u0026#34; --- apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: nginx-gateway namespace: nginx-gateway annotations: cert-manager.io/cluster-issuer: mydomain-cert-issuer spec: gatewayClassName: nginx listeners: # encrypted by cloudflare - name: wildcard-mydomain-http hostname: \u0026#39;*.mydomain.com\u0026#39; protocol: HTTP port: 80 allowedRoutes: namespaces: from: All - name: wildcard-gateway-mydomain-http hostname: \u0026#39;*.gateway.mydomain.com\u0026#39; protocol: HTTP port: 80 allowedRoutes: namespaces: from: All - name: wildcard-gateway-mydomain-https hostname: \u0026#39;*.gateway.mydomain.com\u0026#39; protocol: HTTPS port: 443 allowedRoutes: namespaces: from: All tls: certificateRefs: - group: \u0026#34;\u0026#34; kind: Secret name: wildcard-gateway-mydomain-cert mode: Terminate --- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: nginx spec: parentRefs: - name: nginx-gateway namespace: nginx-gateway sectionName: wildcard-gateway-mydomain-https hostnames: - \u0026#34;nginx.gateway.mydomain.com\u0026#34; rules: - matches: backendRefs: - name: nginx port: 80 « 强制删除 K8s 资源\n» Kubernetes 0-1 Helm Kubernetes 的包管理工具\n"},{"id":122,"href":"/kubernetes/helm-k8s-package-management-tool/","title":"Helm K8s Package Management Tool","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 Helm Kubernetes 的包管理工具\nKubernetes 0-1 Helm Kubernetes 的包管理工具 # Helm is the best way to find, share, and use software built for Kubernetes.\nHelm是为Kubernetes寻找,共享和使用软件构建的最佳方式。\n简介 # Helm帮助管理Kubernetes应用程序,即使是面对复杂的K8s引用,Helm Charts也可以轻松实现定义,安装和升级。\nHelm是CNCF的毕业项目,由Helm社区维护。\nCharts:\nCharts可以看作是Helm的程序包,一个Chart是描述Kubernetes资源集的文件集合。\nRepository:\n存储和共享Charts,可以看作是Kubernetes程序包的存储中心。\nRelease:\n由一个Chart运行起来的实例,这将在kubernetes集群中生成或更新一组资源,可以使用同一个chart运行成多个release。例如,如果你想运行多个redis服务,你可以通过多次安装redis的chart得到。\n看到以上三个概念,你可能会觉得似曾相识,没错,与docker三个概念——image,registry,container如出一辙,也许现在你会加深点理解了。\n安装 # 可以方便的使用脚本安装\ncurl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 chmod 700 ./get-helm.sh ./get-helm.sh 也可以下载指定版本手动安装\n下载地址: https://github.com/helm/helm/releases\nwget https://get.helm.sh/helm-v3.5.2-linux-amd64.tar.gz tar -zxvf ./helm-v3.5.2-linux-amd64.tar.gz sudo mv linux-amd64/helm /usr/local/bin/helm 第一个helm命令\nhelm help 更多安装方式可查看: https://helm.sh/docs/intro/install/\n使用 # 初始化Helm Chart仓库:\n首先添加helm官方的helm stable charts仓库\nhelm repo add stable https://charts.helm.sh/stable 查询charts仓库:\n例如你想查找prometheus应用仓库,打开 https://artifacthub.io/站点,在查询输入框中输入prometheus之后,搜索可以得到官方或民间的仓库。\n一般选择具有官方标志的仓库,进入该仓库的主页,里面会有charts的基本信息,以及安装命令的帮助。\n添加Prometheus的官方Charts仓库\nhelm repo add prometheus-community https://prometheus-community.github.io/helm-charts 添加仓库后,就可以在当前已经添加的仓库中搜索你需要的kubernetes应用了\n$ helm search repo prometheus NAME CHART VERSION APP VERSION DESCRIPTION prometheus-community/kube-prometheus-stack 13.5.0 0.45.0 kube-prometheus-stack collects Kubernetes manif... prometheus-community/prometheus 13.2.1 2.24.0 Prometheus is a monitoring system and time seri... prometheus-community/prometheus-adapter 2.11.1 v0.8.3 A Helm chart for k8s prometheus adapter ... 创建release:\n使用特定的chart安装kubernetes应用\n$ helm install prometheus prometheus-community/prometheus NAME: prometheus LAST DEPLOYED: Sun Feb 7 02:04:59 2021 NAMESPACE: default STATUS: deployed REVISION: 1 ... 查看release:\n$ helm ls NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION prometheus default 1 2021-02-07 02:04:59 deployed prometheus-13.2.1 2.24.0 $ helm status prometheus NAME: prometheus LAST DEPLOYED: Sun Feb 7 02:04:59 2021 NAMESPACE: default STATUS: deployed REVISION: 1 并且可以查看K8s集群是否正在创建你需要的应用\n$ kubectl get pod NAME READY STATUS RESTARTS AGE prometheus-alertmanager-5dbfffbbc4-jppgj 0/2 Pending 0 36s prometheus-kube-state-metrics-66c4db67ff-r65tg 0/1 ContainerCreating 0 36s prometheus-node-exporter-tvjnx 1/1 Running 0 36s prometheus-pushgateway-798b886754-x4fvq 0/1 ContainerCreating 0 36s prometheus-server-74656c5bbc-k4m6x 0/2 Pending 0 36s 删除 release:\n以下命令会删除所有install创建的所有Kubernetes资源\n$ helm uninstall prometheus release \u0026#34;prometheus\u0026#34; uninstalled 如果你想保存安装的历史信息,方便以后查看发布审计或者回滚发布,可以使用--keep-history命令标识。\n$ helm uninstall prometheus --keep-history release \u0026#34;prometheus\u0026#34; uninstalled # 此时查看安装状态将变成uninstalled $ helm ls NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION $ helm ls -a NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION prometheus default 1 2021-02-07 02:12:46 uninstalled prometheus-13.2.1 2.24.0 $ helm status prometheus NAME: prometheus LAST DEPLOYED: Sat Feb 6 02:49:41 2021 NAMESPACE: default STATUS: uninstalled REVISION: 1 ... 将删除release重新安装/回滚回来:\n每次成功的install或upgrade release后,我们都会得到release的REVISION,在回滚release时需要使用到这个版本号\n$ helm rollback prometheus 1 Rollback was a success! Happy Helming! 定制 # Charts是允许用户定制的,例如你想修改应用端口,或者应用的副本数量,可以通过修改charts的资源文件,然后依据资源文件创建release。\n首先,下载应用的charts文件集合到本地\n$ helm repo add bitnami https://charts.bitnami.com/bitnami \u0026#34;bitnami\u0026#34; has been added to your repositories $ mkdir nginx $ helm show values bitnami/nginx \u0026gt; ./nginx/values.yaml 修改values.yaml文件,如果你对K8s资源的定义有所了解,那么你能轻松的修改该文件。\n$ vim ./nginx/values.yaml ... # 修改nginx deployment副本数为2 replicaCount: 2 ... 文件修改完成后,使用如下命令安装\n$ helm install nginx -f ./nginx/values.yaml bitnami/nginx NAME: nginx LAST DEPLOYED: Sun Feb 7 02:59:48 2021 NAMESPACE: default STATUS: deployed REVISION: 1 ... 以上操作可以使用如下命令代替:\nhelm install nginx --set replicaCount=2 bitnami/nginx 使用\u0026ndash;set设置配置说明:\n如果同时指定了values.yaml文件和使用\u0026ndash;set设置配置,那么\u0026ndash;set设置的配置优先级更高\n多组配置:\u0026ndash;set a=1,b=2\n层级:\u0026ndash;set user.name=jaychou\n数组:\u0026ndash;set users={jay,jack,john} \u0026ndash;set users[0].name=jaychou\n由于文件更易管理和迁移,更推荐修改values.yaml的方式定制Chart:\n查看release的资源创建情况\n$ kubectl get pod NAME READY STATUS RESTARTS AGE nginx-78956d9896-5qvdl 1/1 Running 0 32s nginx-78956d9896-fhw5w 1/1 Running 0 32s 确实,如我们所愿,创建了2个副本实例。\n更新Release:\n已经安装了的release,修改定制文件后升级发布\n$ helm upgrade nginx -f ./values.yaml bitnami/nginx Release \u0026#34;nginx\u0026#34; has been upgraded. Happy Helming! NAME: nginx LAST DEPLOYED: Sun Feb 7 03:05:26 2021 NAMESPACE: default STATUS: deployed REVISION: 2 TEST SUITE: None ... 常用命令 # 帮助命令\nhelm help helm get -h 更新charts仓库\nhelm repo update 更多helm install的方式\n从chart仓库安装,我们上面使用的方式\n本地编写chart模板和values.yaml等文件,然后使用以下命令安装\nhelm install nginx /path/to/nginx-chart 得到chart压缩文件,使用以下命令安装\nhelm install nginx nginx-chart.tgz chart压缩文件的http url,使用以下命令安装\nhelm install nginx https://xxx.com/charts/ngin-chart.tgz 如果你有兴趣,你可以创建自己的charts,执行以下命令会在目录下生成charts文件\n$ helm create my-nginx Creating my-nginx # 定制后 $ helm package my-nginx Successfully packaged chart and saved it to: /path/to/my-nginx-0.1.0.tgz $ helm install my-nginx ./my-nginx-0.1.0.tgz NAME: nginx2 LAST DEPLOYED: Sun Feb 28 14:20:53 2021 NAMESPACE: default STATUS: deployed REVISION: 1 ... 更多了解请前往 官方文档\n« Gateway API 实践\n» Kubernetes 0-1 实现Pod自动扩缩HPA\n"},{"id":123,"href":"/kubernetes/hpa-usage/","title":"Hpa Usage","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 实现Pod自动扩缩HPA\nKubernetes 0-1 实现Pod自动扩缩HPA # https://blog.51cto.com/14143894/2458468?source=dra\n前言 # 在K8s集群中,我们可以通过部署Metrics Server来持续收集Pod的资源利用指标数据,我们可以根据收集到的指标数据来评估是否需要调整Pod的数量以贴合它的使用需求。例如,当我们观察到Pod的CPU利用率过高时,我们可以适当上调Deployment的Replicas字段值,来手动实现Pod的横向扩容。\nMetrics Server的指标数据可以通过Dashboard查看到; 安装Metrics Server # HPA介绍 # HPA(Horizontal Pod Autoscaler,Pod水平自动扩缩),根据Pod的资源利用率自动调整Pod管理器中副本数:Pod资源利用率低,降低Pod副本数,降低资源的使用,节约成本;Pod资源利用率高,增加Pod副本数,提高应用的负载能力。\n示例 # 以部署redis为例,现使用redis\n« Kubernetes 0-1 Helm Kubernetes 的包管理工具\n» HTTP 客户端调用 Kubernetes APIServer\n"},{"id":124,"href":"/kubernetes/http-call-k8s-apiserver/","title":"HTTP Call K8s Apiserver","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / HTTP 客户端调用 Kubernetes APIServer\nHTTP 客户端调用 Kubernetes APIServer # 本篇介绍几种如何通过 HTTP 客户端调用 Kubernetes APIServer 的姿势。\n如何获取 Kubernetes api-server 地址 # 查看 api-server 的几种方式:\n# 1. 直接查看 kubeconfig 文件 $ cat ~/.kube/config apiVersion: v1 clusters: - cluster: server: https://192.168.58.2:8443 ... # 2. kubectl 查看集群信息 $ kubectl cluster-info Kubernetes control plane is running at https://192.168.58.2:8443 ... # 3. kubectl 查看集群配置 $ kubectl config view clusters: - cluster: ... server: https://192.168.58.2:8443 ... 配置环境变量\nKUBE_API=$(kubectl config view -o jsonpath=\u0026#39;{.clusters[0].cluster.server}\u0026#39;) echo $KUBE_API api-server 如何给客户端授权 # 使用证书授权 # 直接调用:\n$ curl $KUBE_API/version curl: (60) SSL certificate problem: unable to get local issuer certificate More details here: https://curl.haxx.se/docs/sslcerts.html curl failed to verify the legitimacy of the server and therefore could not establish a secure connection to it. To learn more about this situation and how to fix it, please visit the web page mentioned above. # 忽略证书验证 $ curl $KUBE_API/version --insecure { \u0026#34;kind\u0026#34;: \u0026#34;Status\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;metadata\u0026#34;: { }, \u0026#34;status\u0026#34;: \u0026#34;Failure\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;Unauthorized\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;Unauthorized\u0026#34;, \u0026#34;code\u0026#34;: 401 } 结果是我们无法顺利调用接口。\n获取证书:\n# 服务端证书 $ kubectl config view --raw \\ -o jsonpath=\u0026#39;{.clusters[0].cluster.certificate-authority-data}\u0026#39; \\ | base64 --decode \u0026gt; ./server-ca.crt # 服务端证书 $ kubectl config view --raw \\ -o jsonpath=\u0026#39;{.users[0].user.client-certificate-data}\u0026#39; \\ | base64 --decode \u0026gt; ./client-ca.crt # 服务端证书密钥 $ kubectl config view --raw \\ -o jsonpath=\u0026#39;{.users[0].user.client-key-data}\u0026#39; \\ | base64 --decode \u0026gt; ./client-ca.key 我们这次尝试使用证书调用 API:\n$ curl $KUBE_API/version --cacert ./server-ca.crt \\ --cert ./client-ca.crt \\ --key ./client-ca.key { \u0026#34;major\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;minor\u0026#34;: \u0026#34;22\u0026#34;, \u0026#34;gitVersion\u0026#34;: \u0026#34;v1.22.6+k3s1\u0026#34;, \u0026#34;gitCommit\u0026#34;: \u0026#34;3228d9cb9a4727d48f60de4f1ab472f7c50df904\u0026#34;, \u0026#34;gitTreeState\u0026#34;: \u0026#34;clean\u0026#34;, \u0026#34;buildDate\u0026#34;: \u0026#34;2022-01-25T01:27:44Z\u0026#34;, \u0026#34;goVersion\u0026#34;: \u0026#34;go1.16.10\u0026#34;, \u0026#34;compiler\u0026#34;: \u0026#34;gc\u0026#34;, \u0026#34;platform\u0026#34;: \u0026#34;linux/amd64\u0026#34; } 此时,可以正常调用 API 了,我们继续调用其他接口:\n$ curl $KUBE_API/apis/apps/v1/deployments --cacert ./server-ca.crt \\ --cert ./client-ca.crt \\ --key ./client-ca.key { \u0026#34;kind\u0026#34;: \u0026#34;DeploymentList\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;apps/v1\u0026#34;, \u0026#34;metadata\u0026#34;: { \u0026#34;resourceVersion\u0026#34;: \u0026#34;654514\u0026#34; }, \u0026#34;items\u0026#34;: [...] } 也 OK 了。\n使用 token 授权 # token 指的是 ServiceAccount 的 Token,每个命名空间内都会存在一个默认的 sa:default。\n我们尝试来生成 sa token,我们首先生成一个 default 命名空间下 default sa 的 token:\n# 获取 sa 采用的 secret $ kubectl get sa default -o jsonpath=\u0026#39;{.secrets[0].name}\u0026#39; # 获取 token DEFAULT_DEFAULT_TOKEN=$(kubectl get secrets \\ $(kubectl get sa default -o jsonpath=\u0026#39;{.secrets[0].name}\u0026#39;) \\ -o jsonpath=\u0026#39;{.data.token}\u0026#39; | base64 --decode) $ echo $DEFAULT_DEFAULT_TOKEN eyJhbGciOiJSUzI1NiIsImtpZCI6IjFJdzhRZlV3TkpRRm5aSzU4b2hWTWI3YmdPUTlyLTBWSU9OQmNlN3cwdGsifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tcW50bGsiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjE0OGJmM2U0LTkzY2EtNGJiMS04MDczLWMzZmIzM2NiYmJmNiIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.ivtk9eRH-35wIaPk4CtPSBoBkuiMxQyta17qMxNjhgedyV5i1QGYty36k0ufbOpBMXr2DsdRy8yQTx2qnH-AcduyxaoCxX-SQ4yGUsKSHCTipktcWqFi-CFzNo6EMCZiX8zAmeXjYOMmF8kh2T6wkHmjERDYsqWPaftasTUrKEYpcawFCMnv0QTpDe-okr6vQx6t7pJ5fx_PCw-GEEZUKQZn1tHIStd77eZd546--rrS6nPczKc3GnVFsDTcPM5HI7T_hXnId1TEnOYM8H5ornJ6uDP2oN_niwV41qOXMM52Bep0cvnikG-kUklLpmZxkwAtQCHDDh36A5JX_oaK5w 💡 如果你的 kubectl 版本大于 1.24,那么你可以直接使用以下命令获取 token:\n$ DEFAULT_DEFAULT_TOKEN=$(kubectl create token default -n default) $ echo $DEFAULT_DEFAULT_TOKEN eyJhbGciOiJSUzI1NiIsImtpZCI6IjFJdzhRZlV3TkpRRm5aSzU4b2hWTWI3YmdPUTlyLTBWSU9OQmNlN3cwdGsifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJrM3MiXSwiZXhwIjoxNjc2NDUwODg2LCJpYXQiOjE2NzY0NDcyODYsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0Iiwic2VydmljZWFjY291bnQiOnsibmFtZSI6ImRlZmF1bHQiLCJ1aWQiOiIxNDhiZjNlNC05M2NhLTRiYjEtODA3My1jM2ZiMzNjYmJiZjYifX0sIm5iZiI6MTY3NjQ0NzI4Niwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6ZGVmYXVsdCJ9.FC6SZ3fKhKML76AYnfaq4Y74mlRMBUxgfsEocrczzoN_NvDbqzZ_0sCAvA_ZdcVXv74hXTTeO1_DLoXZE_aLmGIxH1ImfbCDbxZH1xvNbE-7oozKmWBjYM7VRnNVvNC8EiRmcSEMttnQxgnBqUDZCyU8VA_pujld_RsB4SiD8tpXN5PaSaEx6vz6AWYWtW8wqwcAlIWTGk4hae090a0sLplyB4xx-7SiYjmkM9tVXFz5WWdUYSfyQeM-EfDpH4fNsvefWtW_KeJ5Wg28RuhiLbUv9_UV1RGt11Wh7lf0nNmxobqB8j-PEnphiECMKDv29x5KtQDU1wSgbSMI-_eTlQ 使用 token 调用 API:\n$ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments --cacert ./server-ca.crt \\ -H \u0026#34;Authorization: Bearer $DEFAULT_DEFAULT_TOKEN\u0026#34; { \u0026#34;kind\u0026#34;: \u0026#34;Status\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;metadata\u0026#34;: { } \u0026#34;status\u0026#34;: \u0026#34;Failure\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;deployments.apps is forbidden: User \\\u0026#34;system:serviceaccount:default:default\\\u0026#34; cannot list resource \\\u0026#34;deployments\\\u0026#34; in API group \\\u0026#34;apps\\\u0026#34; at the cluster scope\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;Forbidden\u0026#34;, \u0026#34;details\u0026#34;: { \u0026#34;group\u0026#34;: \u0026#34;apps\u0026#34;, \u0026#34;kind\u0026#34;: \u0026#34;deployments\u0026#34; }, \u0026#34;code\u0026#34;: 403 } 可以看到用户 system:serviceaccount:default:default 并没有权限获取自身命名空间内的 Kubernetes 对象列表。\n接下来让我们尝试给这个 sa 绑定一个 cluster-admin 的 ClusterRole,然后我们再次生成 token,并调用接口:\n$ kubectl create clusterrolebinding default-sa-with-cluster-admin-role \\ --clusterrole cluster-admin --serviceaccount default:default $ DEFAULT_DEFAULT_TOKEN=$(kubectl create token default -n default) $ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments --cacert ./server-ca.crt \\ -H \u0026#34;Authorization: Bearer $DEFAULT_DEFAULT_TOKEN\u0026#34; { \u0026#34;kind\u0026#34;: \u0026#34;DeploymentList\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;apps/v1\u0026#34;, \u0026#34;metadata\u0026#34;: { \u0026#34;resourceVersion\u0026#34;: \u0026#34;2824281\u0026#34; }, \u0026#34;items\u0026#34;: [] } 这完美生效,因为 defualt sa 有了一个超级大的 admin 角色。(另外,你可以单独创建一个特定资源权限的 role,在绑定到一个 sa,来满足最小权限原则)\n在 Pod 内访问 api-server # 在 pod 内部,默认会生成 Kubernetes 服务地址相关的环境变量,并且会在特殊目录下保存证书以及 token,当然这个token 是根据 pod 所使用的 sa 生成的。\n证书文件地址:/var/run/secrets/kubernetes.io/serviceaccount/ca.crt\ntoken 文件地址:/var/run/secrets/kubernetes.io/serviceaccount/ca.crt\n$ kubectl run -it --image curlimages/curl --restart=Never mypod -- sh $ env | grep KUBERNETES KUBERNETES_SERVICE_PORT=443 KUBERNETES_PORT=tcp://10.43.0.1:443 KUBERNETES_PORT_443_TCP_ADDR=10.43.0.1 KUBERNETES_PORT_443_TCP_PORT=443 KUBERNETES_PORT_443_TCP_PROTO=tcp KUBERNETES_SERVICE_PORT_HTTPS=443 KUBERNETES_PORT_443_TCP=tcp://10.43.0.1:443 KUBERNETES_SERVICE_HOST=10.43.0.1 $ TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) $ curl https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}/apis/apps/v1 \\ --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \\ --header \u0026#34;Authorization: Bearer $TOKEN\u0026#34; 使用 curl 执行基本的 CRUD 操作 # 创建资源:\n$ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments \\ --cacert ./server-ca.crt \\ --cert ./client-ca.crt \\ --key ./client-ca.key \\ -X POST \\ -H \u0026#39;Content-Type: application/yaml\u0026#39; \\ -d \u0026#39;--- apiVersion: apps/v1 kind: Deployment metadata: name: sleep spec: replicas: 1 selector: matchLabels: app: sleep template: metadata: labels: app: sleep spec: containers: - name: sleep image: curlimages/curl command: [\u0026#34;/bin/sleep\u0026#34;, \u0026#34;365d\u0026#34;] \u0026#39; $ kubectl get deploy NAME READY UP-TO-DATE AVAILABLE AGE sleep 1/1 1 1 8s 获取资源:\n$ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments \\ --cacert ./server-ca.crt \\ --cert ./client-ca.crt \\ --key ./client-ca.key # 给定特定的资源名 $ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments/sleep \\ --cacert ./server-ca.crt \\ --cert ./client-ca.crt \\ --key ./client-ca.key # watch $ curl $KUBE_API\u0026#39;/apis/apps/v1/namespaces/default/deployments?watch=true\u0026#39; \\ --cacert ./server-ca.crt \\ --cert ./client-ca.crt \\ --key ./client-ca.key 更新资源:\ncurl $KUBE_API/apis/apps/v1/namespaces/default/deployments/sleep \\ --cacert ./server-ca.crt \\ --cert ./client-ca.crt \\ --key ./client-ca.key \\ -X PUT \\ -H \u0026#39;Content-Type: application/yaml\u0026#39; \\ -d \u0026#39;--- apiVersion: apps/v1 kind: Deployment metadata: name: sleep spec: replicas: 1 selector: matchLabels: app: sleep template: metadata: labels: app: sleep spec: containers: - name: sleep image: curlimages/curl command: [\u0026#34;/bin/sleep\u0026#34;, \u0026#34;730d\u0026#34;] # \u0026lt;-- Making it sleep twice longer \u0026#39; # patch 更新 curl $KUBE_API/apis/apps/v1/namespaces/default/deployments/sleep \\ --cacert ./server-ca.crt \\ --cert ./client-ca.crt \\ --key ./client-ca.key \\ -X PATCH \\ -H \u0026#39;Content-Type: application/merge-patch+json\u0026#39; \\ -d \u0026#39;{ \u0026#34;spec\u0026#34;: { \u0026#34;template\u0026#34;: { \u0026#34;spec\u0026#34;: { \u0026#34;containers\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;sleep\u0026#34;, \u0026#34;image\u0026#34;: \u0026#34;curlimages/curl\u0026#34;, \u0026#34;command\u0026#34;: [\u0026#34;/bin/sleep\u0026#34;, \u0026#34;1d\u0026#34;] } ] } } } }\u0026#39; 删除资源:\n$ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments \\ --cacert ./server-ca.crt \\ --cert ./client-ca.crt \\ --key ./client-ca.key \\ -X DELETE # 删除单个资源 $ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments/sleep \\ --cacert ./server-ca.crt \\ --cert ./client-ca.crt \\ --key ./client-ca.key \\ -X DELETE 如何使用 kubectl 调用 API # 使用 kubectl 代理 Kubernetes api-server # $ kubectl proxy --port 8080 # 启动了代理服务后,调用 Kubernetes api-server 变得更简单 $ curl localhost:8080/apis/apps/v1/deployments { \u0026#34;kind\u0026#34;: \u0026#34;DeploymentList\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;apps/v1\u0026#34;, \u0026#34;metadata\u0026#34;: { \u0026#34;resourceVersion\u0026#34;: \u0026#34;660883\u0026#34; }, \u0026#34;items\u0026#34;: [...] } kubectl 使用原始模式调用 API # # 获取 $ kubectl get --raw /api/v1/namespaces/default/pods # 创建 $ kubectl create --raw /api/v1/namespaces/default/pods -f file.yaml # 更新 $ kubectl replace --raw /api/v1/namespaces/default/pods/mypod -f file.json # 删除 $ kubectl delete --raw /api/v1/namespaces/default/pods 如何查看 kubectl 命令(如 apply)发送的 API 请求 # $ kubectl create deployment nginx --image nginx -v 6 I0215 17:07:35.188480 43870 loader.go:372] Config loaded from file: /Users/dp/.kube/config I0215 17:07:35.362580 43870 round_trippers.go:553] POST https://192.168.58.2:8443/apis/apps/v1/namespaces/default/deployments?fieldManager=kubectl-create\u0026amp;fieldValidation=Strict 201 Created in 167 milliseconds deployment.apps/nginx created 参考 # How To Call Kubernetes API using Simple HTTP Client 使用 Kubernetes API 访问集群 | Kubernetes 从 Pod 中访问 Kubernetes API | Kuberentes « Kubernetes 0-1 实现Pod自动扩缩HPA\n» Informer\n"},{"id":125,"href":"/kubernetes/informer/","title":"Informer","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Informer\nInformer # Informer是client-go中实现的一个工具包,目前已经被kubernetes中各个组件所使用,例如controller-manager。Informer本质是一个api资源的缓存。\n主要功能:\n将etcd数据同步至本地缓存,客户端通过本地缓存读取和监听资源\n注册资源Add,Update,Delete的触发事件\n目的:\n本地缓存,避免组件直接与api-server交互,减缓对api-server及etcd的访问压力。\n组件 # Reflector\nDelta FIFO Queue\nIndexer(local strorage)\n下面结合流程示意图简单介绍这些组件的角色。\n流程示意图 # 这张示意图展示了client-go类库中各个组件的工作机制,以及它们与咱们将要编写的自定义控制器的交互点(黄颜色标注的块是需要自行开发的部分)。\nReflector:\n负责监听(Watch)特定Kubernetes资源对象,监听的资源对象可以是内置的资源例如Pod,Ingress等,也可以是定制的CR对象。\nReflettor与ApiServer建立连接,第一次使用List\u0026amp;Watch机制从ApiServer中List特定资源的所有实例,这些实例附带的ResourceVersion字段可以用来区分实例是否更新。后续在使用List\u0026amp;Watch机制从ApiServer中Watch特定资源的新增,更新,删除等变化(增量Delta)。\n将监听到的资源的新增,更新,删除顺序写入到DeltaFIFO队列中。\nDeltaFIFO:\n一个增量的先进先出的队列,存储监听到的资源,以及资源事件类型,例如Added,Updated,Deleted,Replaced,Sync。\nInformer:\nIndexer:\n一个自带索引功能的本地存储,用于存储资源对象。Informer从DeltaFIFO中Pop出资源,存储到Indexer。Indexer中资源与k8s etcd数据保持一致。本地读取时直接查询本地存储,从而减少k8s apiserver和etcd的压力。\n使用示例 # 自定义控制器\nclientset, err := kubernetes.NewForConfig(config) stopCh := make(chan struct{}) defer close(stopch) sharedInformers := informers.NewSharedInformerFactory(clientset, time.Minute) informer := sharedInformer.Core().V1().Pods().Informer() informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{} { // ... }, UpdateFunc: func(obj interface{} { // ... }, DeleteFunc : func(obj interface{} { // ... }) informer.Run(stopCh) }) Informer 需要通过 ClientSet 与 Kubernetes API Server 交互; 创建 stopCh 是用于在程序进程退出前通知 Informer 提前退出,Informer 是一个持久运行的 goroutine; NewSharedInformerFactory 实例化了一个 SharedInformer 对象,用于进行本地资源存储; sharedInformer.Core().V1().Pods().Informer() 得到了具体 Pod 资源的 informer 对象; AddEventHandler 即图中的第6步,这是一个资源事件回调方法,上例中即为当创建/更新/删除 Pod 时触发事件回调方法; 一般而言,其他组件使用 Informer 机制触发资源回调方法会将资源对象推送到 WorkQueue 或其他队列中,具体推送的位置要去回调方法里自行实现。 上面这个示例,当触发了 Add,Update 或者 Delete 事件,就通知 Client-go,告知 Kubernetes 资源事件发生变更并且需要进行相应的处理。\nInfromer机制 # 资源Informer # 每个内置的k8s资源对实现了对应的Informer机制,均包含Lister和Informer方法,例如:\ntype PodInformer interface { Lister() cache.SharedIndexInformer Informer() v1.PodLister } SharedInformer共享机制 # 每实例化一个Informer对象,都需要维护一个对应的Reflector。当同一对象Informer实例被实例化多次时,运行过多的ListAndWatch,这其中包括的\n« HTTP 客户端调用 Kubernetes APIServer\n» 通过 Ingress 进行灰度发布\n"},{"id":126,"href":"/kubernetes/ingress-gray-deploy/","title":"Ingress Gray Deploy","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 通过 Ingress 进行灰度发布\n通过 Ingress 进行灰度发布 # https://start.aliyun.com/handson/Tn0HcdCZ/grap_publish_by_ingress\nStep 1 :实验介绍 # 本实验,你将运行运行一个简单的应用,部署一个新的应用用于新的发布,并通过 Ingress 能力实现灰度发布。\n灰度及蓝绿发布是为新版本创建一个与老版本完全一致的生产环境,在不影响老版本的前提下,按照一定的规则把部分流量切换到新版本,当新版本试运行一段时间没有问题后,将用户的全量流量从老版本迁移至新版本。\n通过本实验,你将学习:\n通过 Ingress 按权重进行灰度发布 通过 Ingress 按 Header 进行灰度发布 容器服务 Kubernetes 版(简称 ACK) 本节课使用的 Kubernetes(k8s) 集群就是由 ACK 提供的,本实验涵盖的都是一些基本操作。更多高级用法,可以去 ACK 的产品页面了解哦。\nStep 2 :部署 Deployment V1 应用 # 创建如下 YAML 文件(app-v1.yaml)\napiVersion: v1 kind: Service metadata: name: my-app-v1 labels: app: my-app spec: ports: - name: http port: 80 targetPort: http selector: app: my-app version: v1.0.0 --- apiVersion: apps/v1 kind: Deployment metadata: name: my-app-v1 labels: app: my-app spec: replicas: 1 selector: matchLabels: app: my-app version: v1.0.0 template: metadata: labels: app: my-app version: v1.0.0 annotations: prometheus.io/scrape: \u0026#34;true\u0026#34; prometheus.io/port: \u0026#34;9101\u0026#34; spec: containers: - name: my-app image: registry.cn-hangzhou.aliyuncs.com/containerdemo/containersol-k8s-deployment-strategies ports: - name: http containerPort: 8080 - name: probe containerPort: 8086 env: - name: VERSION value: v1.0.0 livenessProbe: httpGet: path: /live port: probe initialDelaySeconds: 5 periodSeconds: 5 readinessProbe: httpGet: path: /ready port: probe periodSeconds: 5 执行如下命令部署 Deployement V1 应用:\nkubectl apply -f app-v1.yaml 创建如下 Ingress YAML文件(ingress-v1.yaml)\napiVersion: extensions/v1beta1 kind: Ingress metadata: name: my-app labels: app: my-app spec: rules: - host: my-app-898e6352eda2ac9c5cfc5d51c155455b.ca2ef983cc3a64042a32c1a423d32021e.cn-shanghai.alicontainer.com http: paths: - backend: serviceName: my-app-v1 servicePort: 80 path: / 执行如下命令部署 Ingress 资源\nkubectl apply -f ingress-v1.yaml 部署完成后通过 curl 命令进行测试:\ncurl my-app-898e6352eda2ac9c5cfc5d51c155455b.ca2ef983cc3a64042a32c1a423d32021e.cn-shanghai.alicontainer.com 会看到如下返回:\nHost: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 Step 3 :部署 Deployment V2 应用 # 创建如下 YAML 文件(app-v2.yaml)\napiVersion: v1 kind: Service metadata: name: my-app-v2 labels: app: my-app spec: ports: - name: http port: 80 targetPort: http selector: app: my-app version: v2.0.0 --- apiVersion: apps/v1 kind: Deployment metadata: name: my-app-v2 labels: app: my-app spec: replicas: 1 selector: matchLabels: app: my-app version: v2.0.0 template: metadata: labels: app: my-app version: v2.0.0 annotations: prometheus.io/scrape: \u0026#34;true\u0026#34; prometheus.io/port: \u0026#34;9101\u0026#34; spec: containers: - name: my-app image: registry.cn-hangzhou.aliyuncs.com/containerdemo/containersol-k8s-deployment-strategies ports: - name: http containerPort: 8080 - name: probe containerPort: 8086 env: - name: VERSION value: v2.0.0 livenessProbe: httpGet: path: /live port: probe initialDelaySeconds: 5 periodSeconds: 5 readinessProbe: httpGet: path: /ready port: probe periodSeconds: 5 执行如下命令部署 Deployement V2 应用:\nkubectl apply -f app-v2.yaml Step 4 :按照权重策略灰度到 Deployment V2 应用 # 创建如下 Ingress YAML文件(ingress-v2-canary-weigth.yaml)\napiVersion: extensions/v1beta1 kind: Ingress metadata: name: my-app-canary labels: app: my-app annotations: # Enable canary and send 10% of traffic to version 2 nginx.ingress.kubernetes.io/canary: \u0026#34;true\u0026#34; nginx.ingress.kubernetes.io/canary-weight: \u0026#34;10\u0026#34; spec: rules: - host: my-app-898e6352eda2ac9c5cfc5d51c155455b.ca2ef983cc3a64042a32c1a423d32021e.cn-shanghai.alicontainer.com http: paths: - backend: serviceName: my-app-v2 servicePort: 80 path: / 执行如下命令部署 Ingress 资源\nkubectl apply -f ingress-v2-canary-weigth.yaml 执行如下命令进行测试:\nwhile sleep 0.1;do curl my-app-898e6352eda2ac9c5cfc5d51c155455b.ca2ef983cc3a64042a32c1a423d32021e.cn-shanghai.alicontainer.com; done 测试结果如下:\nHost: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 Host: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 Host: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 Host: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 Host: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 Host: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 Host: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 Host: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 Host: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 Host: my-app-v2-67c69b8857-g82gr, Version: v2.0.0 Host: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 Step 5 :按照 Header 策略灰度到 Deployment V2 应用 # 创建如下 Ingress YAML文件(ingress-v2-canary-header.yaml)\napiVersion: extensions/v1beta1 kind: Ingress metadata: name: my-app-canary labels: app: my-app annotations: # Enable canary and send traffic with headder x-app-canary to version 2 nginx.ingress.kubernetes.io/canary: \u0026#34;true\u0026#34; nginx.ingress.kubernetes.io/canary-by-header: \u0026#34;x-app-canary\u0026#34; nginx.ingress.kubernetes.io/canary-by-header-value: \u0026#34;true\u0026#34; spec: rules: - host: my-app-898e6352eda2ac9c5cfc5d51c155455b.ca2ef983cc3a64042a32c1a423d32021e.cn-shanghai.alicontainer.com http: paths: - backend: serviceName: my-app-v2 servicePort: 80 执行如下命令部署 Ingress 资源\nkubectl apply -f ingress-v2-canary-header.yaml 通过 curl 命令对应用进行测试:\ncurl my-app-898e6352eda2ac9c5cfc5d51c155455b.ca2ef983cc3a64042a32c1a423d32021e.cn-shanghai.alicontainer.com 结果如下:\nHost: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 curl my-app-898e6352eda2ac9c5cfc5d51c155455b.ca2ef983cc3a64042a32c1a423d32021e.cn-shanghai.alicontainer.com -H \u0026#34;x-app-canary: true\u0026#34; 结果如下\nHost: my-app-v2-67c69b8857-g82gr, Version: v2.0.0 « Informer\n» 安装 Kubernetes\n"},{"id":127,"href":"/kubernetes/installation/","title":"Installation","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 安装 Kubernetes\n安装 Kubernetes # « 通过 Ingress 进行灰度发布\n» K3s\n"},{"id":128,"href":"/kubernetes/k3s/","title":"K3s","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / K3s\nK3s # k3s 是一款轻量级的 Kubernetes 发行版,专为物联网及边缘计算设计。\n要求 # K3s 有望在大多数现代 Linux 系统上运行。\n规格要求:\nCPU: 1 核 Memory:512M 端口要求:\nK3s Server 节点的入站规则如下:\n协议 端口 源 描述 TCP 6443 K3s agent 节点 Kubernetes API Server UDP 8472 K3s server 和 agent 节点 仅对 Flannel VXLAN 需要 UDP 51820 K3s server 和 agent 节点 只有 Flannel Wireguard 后端需要 UDP 51821 K3s server 和 agent 节点 只有使用 IPv6 的 Flannel Wireguard 后端才需要 TCP 10250 K3s server 和 agent 节点 Kubelet metrics TCP 2379-2380 K3s server 节点 只有嵌入式 etcd 高可用才需要 启动 # curl -sfL https://rancher-mirror.oss-cn-beijing.aliyuncs.com/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn sh - # Check for Ready node, takes maybe 30 seconds k3s kubectl get node 梧桐 Region 安装 k3s # 安装前提:\ndocker\nhelm 3.0+\n80,443,6060,6443,7070,8443 端口可访问\nnfs sudo apt install nfs-common -y\ncurl -sfL https://rancher-mirror.oss-cn-beijing.aliyuncs.com/k3s/k3s-install.sh | INSTALL_K3S_VERSION=\u0026#34;v1.22.6+k3s1\u0026#34; INSTALL_K3S_MIRROR=cn sh -s - server --docker --disable traefik 使用 \u0026ndash;docker 参数指定 docker 作为容器引擎:k3s 默认使用 docker 作为容器引擎,但是目前梧桐 PaaS 还不支持;\n使用 \u0026ndash;disable traefik 关闭 k3s 默认的 ingress controller,梧桐 PaaS 默认使用 wt-gateway,避免端口冲突。\n默认生成的k3s 配置文件位置:/etc/rancher/k3s/k3s.yaml,可以将其拷贝至 ~/.kube/config,以便 helm 等工具可以默认连接到 k3s。\n自动部署清单:/var/lib/rancher/k3s/server/manifests/\n卸载 # /usr/local/bin/k3s-uninstall.sh 汇总 # k3s 默认使用的 local-path 存储不支持 RWX 模式,所以还是得搭建存储服务 nfs 或者 longhorn; « 安装 Kubernetes\n» Kubernetes 0-1 K8s部署coredns\n"},{"id":129,"href":"/kubernetes/k8s-deploy-coredns/","title":"K8s Deploy Coredns","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 K8s部署coredns\nKubernetes 0-1 K8s部署coredns # 在K8s集群未部署DNS之前,K8s中运行的Pod是无法访问外部网络的,因为无法完成域名解析。\n比如我们运行一个busybox的Pod,然后在Pod里面是无法ping通外部网络的:\n[root@k8s-master01 ~]# kubectl run -it --rm busybox --image=busybox sh If you don\u0026#39;t see a command prompt, try pressing enter. / # ping www.baidu.com ping: bad address \u0026#39;www.baidu.com\u0026#39; 我们可以通过在K8s中部署coredns解决这一问题。\n准备coredns.yaml文件,写入文件内容:\napiVersion: v1 kind: ServiceAccount metadata: name: coredns namespace: kube-system labels: kubernetes.io/cluster-service: \u0026#34;true\u0026#34; addonmanager.kubernetes.io/mode: Reconcile --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: labels: kubernetes.io/bootstrapping: rbac-defaults addonmanager.kubernetes.io/mode: Reconcile name: system:coredns rules: - apiGroups: - \u0026#34;\u0026#34; resources: - endpoints - services - pods - namespaces verbs: - list - watch - apiGroups: - \u0026#34;\u0026#34; resources: - nodes verbs: - get --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: annotations: rbac.authorization.kubernetes.io/autoupdate: \u0026#34;true\u0026#34; labels: kubernetes.io/bootstrapping: rbac-defaults addonmanager.kubernetes.io/mode: EnsureExists name: system:coredns roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:coredns subjects: - kind: ServiceAccount name: coredns namespace: kube-system --- apiVersion: v1 kind: ConfigMap metadata: name: coredns namespace: kube-system labels: addonmanager.kubernetes.io/mode: EnsureExists data: Corefile: | .:53 { errors health { lameduck 5s } ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } prometheus :9153 forward . /etc/resolv.conf cache 30 loop reload loadbalance } --- apiVersion: apps/v1 kind: Deployment metadata: name: coredns namespace: kube-system labels: k8s-app: kube-dns kubernetes.io/cluster-service: \u0026#34;true\u0026#34; addonmanager.kubernetes.io/mode: Reconcile kubernetes.io/name: \u0026#34;CoreDNS\u0026#34; spec: # replicas: not specified here: # 1. In order to make Addon Manager do not reconcile this replicas parameter. # 2. Default is 1. # 3. Will be tuned in real time if DNS horizontal auto-scaling is turned on. strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 selector: matchLabels: k8s-app: kube-dns template: metadata: labels: k8s-app: kube-dns annotations: seccomp.security.alpha.kubernetes.io/pod: \u0026#39;runtime/default\u0026#39; spec: priorityClassName: system-cluster-critical serviceAccountName: coredns tolerations: - key: \u0026#34;CriticalAddonsOnly\u0026#34; operator: \u0026#34;Exists\u0026#34; nodeSelector: kubernetes.io/os: linux containers: - name: coredns image: k8s.gcr.io/coredns:1.6.7 imagePullPolicy: IfNotPresent resources: limits: memory: 160Mi requests: cpu: 100m memory: 70Mi args: [ \u0026#34;-conf\u0026#34;, \u0026#34;/etc/coredns/Corefile\u0026#34; ] volumeMounts: - name: config-volume mountPath: /etc/coredns readOnly: true ports: - containerPort: 53 name: dns protocol: UDP - containerPort: 53 name: dns-tcp protocol: TCP - containerPort: 9153 name: metrics protocol: TCP livenessProbe: httpGet: path: /health port: 8080 scheme: HTTP initialDelaySeconds: 60 timeoutSeconds: 5 successThreshold: 1 failureThreshold: 5 readinessProbe: httpGet: path: /ready port: 8181 scheme: HTTP securityContext: allowPrivilegeEscalation: false capabilities: add: - NET_BIND_SERVICE drop: - all readOnlyRootFilesystem: true dnsPolicy: Default volumes: - name: config-volume configMap: name: coredns items: - key: Corefile path: Corefile --- apiVersion: v1 kind: Service metadata: name: kube-dns namespace: kube-system annotations: prometheus.io/port: \u0026#34;9153\u0026#34; prometheus.io/scrape: \u0026#34;true\u0026#34; labels: k8s-app: kube-dns kubernetes.io/cluster-service: \u0026#34;true\u0026#34; addonmanager.kubernetes.io/mode: Reconcile kubernetes.io/name: \u0026#34;CoreDNS\u0026#34; spec: selector: k8s-app: kube-dns clusterIP: 10.0.0.2 ports: - name: dns port: 53 protocol: UDP - name: dns-tcp port: 53 protocol: TCP - name: metrics port: 9153 protocol: TCP 运行命令:\nkubectl apply -f coredns.yaml 查看coredns部署情况:\n[root@k8s-master01 ~]# kubectl get pod -A NAMESPACE NAME READY STATUS RESTARTS AGE kube-system coredns-6879cc89dc-ngsld 1/1 Running 0 3m kube-system kube-flannel-ds-amd64-bgjt4 1/1 Running 1 6d14h kube-system kube-flannel-ds-amd64-dqlkc 1/1 Running 1 6d14h 可以看到coredns的Pod已经启动,这是我们再次测试Pod内部的网络访问情况:\n[root@k8s-master01 ~]# kubectl run -it --rm busybox --image=busybox sh If you don\u0026#39;t see a command prompt, try pressing enter. / # ping www.baidu.com PING www.baidu.com (14.215.177.39): 56 data bytes 64 bytes from 14.215.177.39: seq=0 ttl=127 time=14.441 ms 64 bytes from 14.215.177.39: seq=1 ttl=127 time=26.355 ms ... 可以正常解析。\n« K3s\n» Kubernetes 0-1 K8s部署Dashboard\n"},{"id":130,"href":"/kubernetes/k8s-deploy-dashboard/","title":"K8s Deploy Dashboard","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 K8s部署Dashboard\nKubernetes 0-1 K8s部署Dashboard # 首先下载部署的必要文件:\nwget https://raw.githubusercontent.com/kubernetes/dashboard/master/aio/deploy/recommended.yaml -O kube-dash.yaml --no-check-certificate 默认Dashboard的Service类型是ClusterIP,我们集群外面不方便访问,我们最好是将Service类型修改为NodePoart或LoadBalancer(前提是你的集群支持LoadBalancer),以LoadBalancer为例。\n修改文件kube-dash.yaml文件,将kubernetes-dashboard Service部分修改成如下:\nkind: Service apiVersion: v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard spec: type: LoadBalancer ports: - port: 443 targetPort: 8443 selector: k8s-app: kubernetes-dashboard 创建kube-dash-admin-user.yaml文件:\nvim kube-dash-admin-user.yaml 写入如下内容:\napiVersion: v1 kind: ServiceAccount metadata: name: admin-user namespace: kubernetes-dashboard --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: admin-user roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: admin-user namespace: kubernetes-dashboard 执行命令:\nkubectl apply -f kube-dash.yaml # 创建dashboard服务 kubectl apply -f kube-dash-admin-user.yaml # 创建kubernetes集群的管理员角色和账号 执行完之后,我们查看Dashboard的Service:\nkubectl get svc -n kubernetes-dashboard 输出以下内容,可以看到,kubernetes-dashboard的svc的EXTERNAL-IP为192.168.115.141,这就是LoadBalancer为我们自动分配的一个IP。\nNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE dashboard-metrics-scraper ClusterIP 10.0.0.210 \u0026lt;none\u0026gt; 8000/TCP 52s kubernetes-dashboard LoadBalancer 10.0.0.51 192.168.115.141 443:31385/TCP 52s 这时我们以https://192.168.115.141访问部署的dashboard,第一次访问可能需要点击 Advanced =\u0026gt; **Proceed to 192.168.115.141 (unsafe)**进入。\n注意:必须以https方式访问,因为dashboard是默认开启更为安全的https通信。\n需要使用Token登录,使用如下命令获取token:\nkubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep admin-user | awk \u0026#39;{print $1}\u0026#39;) 输出内容,获取到token。\nName: admin-user-token-l56hp Namespace: kubernetes-dashboard Labels: \u0026lt;none\u0026gt; Annotations: kubernetes.io/service-account.name: admin-user kubernetes.io/service-account.uid: 95db28c5-4951-4aae-bf59-b0c26c8b35c7 Type: kubernetes.io/service-account-token Data ==== ca.crt: 1375 bytes namespace: 20 bytes token: eyJhbGciOiJSUzI1NiIsImtpZCI6IjYtQW51Z2xPMi1WTmpEZEtIX3BBYXd1YWpGLVU2Y0J0S1dmZE9lR3hoYU0ifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLWw1NmhwIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI5NWRiMjhjNS00OTUxLTRhYWUtYmY1OS1iMGMyNmM4YjM1YzciLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZXJuZXRlcy1kYXNoYm9hcmQ6YWRtaW4tdXNlciJ9.Dqnk21CcBWU7SHgKRUu8uebL1djF1BJChNT5qTvk1uGLTF3AN9RMmacXlzO2xC5fP3zasmBFjcmS_JY76k7CS6DHdHKgxgB8vlEfIbh-i4YTA7cKK3_Ko5hAy7e6GhoPsfcYnV5QVec2mlvfMoozJT62UT62YkNrfUZXwFz02V4EfNgCgWVPKgiKzciMVOMNJ6-FKiiXyfhl4zprb8hSPzpc0F2Jd62Ykoltuir74UoByOazAnr7bA9ZTXSf1k8fjUaOUsBh37ap_eHg3Yh2gIcYMBxsp1tV0VVNKJDnVCN-lRBhfUciK93kvxU3I8xjWRv6JUHifCvHUiiWXjGZ8A 注意:默认token的过期时间为900秒(15分钟),为了避免频繁的因为token过期登录问题,可以修改kubernetes-dashboard的Deployment的配置,添加token-ttl参数:\n... args: - --auto-generate-certificates - --token-ttl=43200 ... 拿到token,拷贝到dashboard进行登录。登入后,可以看到K8s的资源信息。\nKubernetes-Dashboard部署完成。\n« Kubernetes 0-1 K8s部署coredns\n» Kubernetes 0-1 K8s部署EFK\n"},{"id":131,"href":"/kubernetes/k8s-deploy-efk/","title":"K8s Deploy Efk","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 K8s部署EFK\nKubernetes 0-1 K8s部署EFK # 写在前面 # 本篇目标是在K8s集群中搭建EFK。\nEFK是由ElasticSearch,Fluentd,Kibane组成的一套目前比较主流的日志监控系统,使用EFK监控应用日志,可以让开发人员在一个统一的入口查看日志然后分析应用运行情况。\nEFK简单的工作原理可以参考下图。通过fluentd的agent收集日志数据,写入es,kibana从es中读取日志数据展示到ui。\n部署ElasticSearch # 最好选择部署一个ES集群,这样你的ES可用性更高一点。\n采用StatefulSet部署ES。\n编写es-statefulSet.yaml文件如下:\napiVersion: apps/v1 kind: StatefulSet metadata: name: es-cluster namespace: dev spec: serviceName: elasticsearch replicas: 3 selector: matchLabels: app: elasticsearch template: metadata: labels: app: elasticsearch spec: containers: - name: elasticsearch image: docker.elastic.co/elasticsearch/elasticsearch:7.7.0 resources: limits: cpu: 1000m requests: cpu: 100m ports: - containerPort: 9200 name: rest protocol: TCP - containerPort: 9300 name: inter-node protocol: TCP volumeMounts: - name: data mountPath: /usr/share/elasticsearch/data env: - name: cluster.name value: k8s-logs - name: node.name valueFrom: fieldRef: fieldPath: metadata.name - name: discovery.seed_hosts value: \u0026#34;es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch\u0026#34; - name: cluster.initial_master_nodes value: \u0026#34;es-cluster-0,es-cluster-1,es-cluster-2\u0026#34; - name: ES_JAVA_OPTS value: \u0026#34;-Xms512m -Xmx512m\u0026#34; initContainers: - name: fix-permissions image: busybox command: [\u0026#34;sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;chown -R 1000:1000 /usr/share/elasticsearch/data\u0026#34;] securityContext: privileged: true volumeMounts: - name: data mountPath: /usr/share/elasticsearch/data - name: increase-vm-max-map image: busybox command: [\u0026#34;sysctl\u0026#34;, \u0026#34;-w\u0026#34;, \u0026#34;vm.max_map_count=262144\u0026#34;] securityContext: privileged: true - name: increase-fd-ulimit image: busybox command: [\u0026#34;sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;ulimit -n 65536\u0026#34;] securityContext: privileged: true volumeClaimTemplates: - metadata: name: data labels: app: elasticsearch spec: accessModes: [\u0026#34;ReadWriteOnce\u0026#34;] storageClassName: gp2 resources: requests: storage: 40Gi 编写es.service.yaml文件如下:\nkind: Service apiVersion: v1 metadata: name: elasticsearch namespace: dev spec: selector: app: elasticsearch type: NodePort ports: - name: elasticsearch-http port: 9200 targetPort: 9200 部署Kibana # 编写kibana-deployment.yaml文件如下:\napiVersion: extensions/v1beta1 kind: Deployment metadata: name: kibana namespace: dev labels: name: kibana spec: strategy: rollingUpdate: maxSurge: 1 maxUnavailable: 1 type: RollingUpdate template: metadata: labels: name: kibana spec: containers: - image: docker.elastic.co/kibana/kibana:7.7.0 imagePullPolicy: Always name: kibana resources: requests: cpu: \u0026#34;1000m\u0026#34; memory: \u0026#34;256M\u0026#34; limits: cpu: \u0026#34;1000m\u0026#34; memory: \u0026#34;1024M\u0026#34; # livenessProbe: # httpGet: # path: /_status/healthz # port: 5000 # initialDelaySeconds: 90 # timeoutSeconds: 10 # readinessProbe: # httpGet: # path: /_status/healthz # port: 5000 # initialDelaySeconds: 30 # timeoutSeconds: 10 env: - name: ELASTICSEARCH_URL value: http://elasticsearch:9200 - name: SERVER_BASEPATH value: /kibana - name: SERVER_REWRITEBASEPATH value: \u0026#34;true\u0026#34; # args: # - server.rewriteBasePath=true # - server.basePath=/kibana ports: - containerPort: 5601 name: kibana-port # volumeMounts: # - mountPath: /etc/kibana/config # name: grafana-data # volumes: # - name: grafana-data # configMap: # name: grafana-config restartPolicy: Always 编写kibana-service.yaml文件如下:\nkind: Service apiVersion: v1 metadata: name: kibana namespace: dev spec: selector: name: kibana type: NodePort ports: - name: kibana-http port: 5601 targetPort: 5601 部署Fluentd # Fluentd是一个开源的数据收集器,可以做数据的集中收集,便于做数据使用和分析,常用于日志收集。\n我们部署Fluentd来收集部署在k8s Pod中的程序的话,首先需要集群赋予它访问Pod的权限,因为我们需要为fluentd分配一个带有Pod相关权限的serviceAccount。\n编写fluentd-serviceAccount.yaml文件如下:\napiVersion: v1 kind: ServiceAccount metadata: name: fluentd namespace: dev labels: app: fluentd --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: fluentd namespace: dev labels: app: fluentd rules: - apiGroups: - \u0026#34;\u0026#34; resources: - pods - namespaces verbs: - get - list - watch --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: fluentd namespace: dev roleRef: kind: ClusterRole name: fluentd apiGroup: rbac.authorization.k8s.io subjects: - kind: ServiceAccount name: fluentd namespace: dev 我们需要在每台节点上都部署Fluent,这相当于是一个日志收集的Agent,因此我们采用DaemonSet的方式部署Fluentd。\n编写fluentd-daemonSet.yaml文件如下:\napiVersion: apps/v1 kind: DaemonSet metadata: name: fluentd namespace: dev labels: app: fluentd spec: selector: matchLabels: app: fluentd template: metadata: labels: app: fluentd spec: serviceAccount: fluentd serviceAccountName: fluentd tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule containers: - name: fluentd image: fluent/fluentd-kubernetes-daemonset:v1.10.4-debian-elasticsearch7-1.0 env: - name: FLUENT_ELASTICSEARCH_HOST value: \u0026#34;elasticsearch.dev.svc.cluster.local\u0026#34; - name: FLUENT_ELASTICSEARCH_PORT value: \u0026#34;9200\u0026#34; - name: FLUENT_ELASTICSEARCH_SCHEME value: \u0026#34;http\u0026#34; - name: FLUENTD_SYSTEMD_CONF value: disable resources: limits: memory: 512Mi requests: cpu: 100m memory: 256Mi volumeMounts: - name: varlog mountPath: /var/log - name: varlibdockercontainers mountPath: /var/lib/docker/containers readOnly: true terminationGracePeriodSeconds: 30 volumes: - name: varlog hostPath: path: /var/log - name: varlibdockercontainers hostPath: path: /var/lib/docker/containers 文件准备好之后,执行\nkubectl apply -f ./fluentd-serviceAccount.yaml kubectl apply -f ./fluentd-daemonSet.yaml 部署之后,查看fluentd daemonset的部署情况\nkubectl get ds -n kube-system 输出信息大致如下:\nNAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE fluentd 3 3 3 3 3 \u0026lt;none\u0026gt; 33s 如果DESIRED和READY列值不一致的话,说明是某个Node上的Pod启用失败了。那么可以查看Pod的启用情况:\nkubectl get pod -n kube-system # 假设Pod fluentd-8hmbd一直未成功启用,使用kubectl describe 或kubectl logs命令检查 kubectl describe pod fluentd-8hmbd -n kube-system kubectl logs fluentd-8hmbd -n kube-system 参考资料 # https://www.digitalocean.com/community/tutorials/how-to-set-up-an-elasticsearch-fluentd-and-kibana-efk-logging-stack-on-kubernetes « Kubernetes 0-1 K8s部署Dashboard\n» 可能需要运行多次以下命令,确保k8s资源都创建\n"},{"id":132,"href":"/kubernetes/k8s-deploy-prometheus-grafana/","title":"K8s Deploy Prometheus Grafana","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 可能需要运行多次以下命令,确保k8s资源都创建\nStep # 下载相关k8s资源文件 git clone https://github.com/coreos/kube-prometheus.git 修改文件kube-prometheus/manifests/prometheus-prometheus.yaml,做这一步的目的是为prometheus的访问分配子路径,访问方式为http(s)://xxx/prometheus\n在prometheus.spec下添加\nexternalUrl: prometheus routePrefix: prometheus 修改文件kube-prometheus/manifests/grafana-deployment.yaml,做这一步的目的是为grafana的访问分配子路径,访问方式为:http(s)://xxx/grafana\n在deployment.spec.template.spec.container[0]下添加\nenv: - name: GF_SERVER_ROOT_URL value: \u0026#34;http://localhost:3000/grafana\u0026#34; - name: GF_SERVER_SERVE_FROM_SUB_PATH value: \u0026#34;true\u0026#34; Apply k8s资源 # 可能需要运行多次以下命令,确保k8s资源都创建 kubectl create -f manifests/setup -f manifests # !如果要删除以上创建的k8s资源,运行以下命令 kubectl delete --ignore-not-found=true -f manifests/ -f manifests/setup Ingress转发 apiVersion: extensions/v1beta1 kind: Ingress metadata: name: prometheus namespace: monitoring spec: rules: - host: dp.example.tech http: paths: - path: /prometheus backend: serviceName: prometheus-k8s servicePort: 9090 - path: /grafana backend: serviceName: grafana servicePort: 3000 - path: /alertmanager backend: serviceName: alertmanager-main servicePort: 9093 « Kubernetes 0-1 K8s部署EFK\n» Kubernetes 0-1 K8s部署Zookeeper和Kafka\n"},{"id":133,"href":"/kubernetes/k8s-deploy-zookeeper-kafka/","title":"K8s Deploy Zookeeper Kafka","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 K8s部署Zookeeper和Kafka\nKubernetes 0-1 K8s部署Zookeeper和Kafka # 按照官方定义,Kafka是一个分布式的流处理平台。更多了解官方文档: http://kafka.apachecn.org/intro.html\n那么直接开始在K8s中部署kafka吧。\n部署kafka,首先要有一个可用的Zookeeper集群,所以我们还需要先部署一个Zookeeper集群。\n首先说明,我的K8s集群是使用的AWS的EKS服务,与自建的K8s集群的配置方面可能会有所差别。\n部署Zookeeper # 编写zookeeper-statefulSet.yaml文件:\nvim zookeeper-statefulSet.yaml 写入内容:\nkind: StatefulSet apiVersion: apps/v1beta1 metadata: name: zookeeper-1 namespace: dev spec: serviceName: zookeeper-1 replicas: 1 selector: matchLabels: app: zookeeper-1 template: metadata: labels: app: zookeeper-1 spec: containers: - name: zookeeper image: digitalwonderland/zookeeper ports: - containerPort: 2181 env: - name: ZOOKEEPER_ID value: \u0026#34;1\u0026#34; - name: ZOOKEEPER_SERVER_1 value: zookeeper-1 - name: ZOOKEEPER_SERVER_2 value: zookeeper-2 - name: ZOOKEEPER_SERVER_3 value: zookeeper-3 volumeMounts: - name: zookeeper-data mountPath: \u0026#34;/var/lib/zookeeper/data\u0026#34; subPath: zookeeper volumeClaimTemplates: - metadata: name: zookeeper-data labels: app: zookeeper-1 spec: accessModes: [\u0026#34;ReadWriteOnce\u0026#34;] storageClassName: gp2 resources: requests: storage: 30Gi --- kind: StatefulSet apiVersion: apps/v1beta1 metadata: name: zookeeper-2 namespace: dev spec: serviceName: zookeeper-2 replicas: 1 selector: matchLabels: app: zookeeper-2 template: metadata: labels: app: zookeeper-2 spec: containers: - name: zookeeper image: digitalwonderland/zookeeper ports: - containerPort: 2181 env: - name: ZOOKEEPER_ID value: \u0026#34;2\u0026#34; - name: ZOOKEEPER_SERVER_1 value: zookeeper-1 - name: ZOOKEEPER_SERVER_2 value: zookeeper-2 - name: ZOOKEEPER_SERVER_3 value: zookeeper-3 volumeMounts: - name: zookeeper-data mountPath: \u0026#34;/var/lib/zookeeper/data\u0026#34; subPath: zookeeper-data volumeClaimTemplates: - metadata: name: zookeeper-data labels: app: zookeeper-2 spec: accessModes: [\u0026#34;ReadWriteOnce\u0026#34;] storageClassName: gp2 resources: requests: storage: 30Gi --- kind: StatefulSet apiVersion: apps/v1beta1 metadata: name: zookeeper-3 namespace: dev spec: serviceName: zookeeper-3 replicas: 1 selector: matchLabels: app: zookeeper-3 template: metadata: labels: app: zookeeper-3 spec: containers: - name: zookeeper image: digitalwonderland/zookeeper ports: - containerPort: 2181 env: - name: ZOOKEEPER_ID value: \u0026#34;3\u0026#34; - name: ZOOKEEPER_SERVER_1 value: zookeeper-1 - name: ZOOKEEPER_SERVER_2 value: zookeeper-2 - name: ZOOKEEPER_SERVER_3 value: zookeeper-3 volumeMounts: - name: zookeeper-data mountPath: \u0026#34;/var/lib/zookeeper/data\u0026#34; subPath: zookeeper volumeClaimTemplates: - metadata: name: zookeeper-data labels: app: zookeeper-3 spec: accessModes: [\u0026#34;ReadWriteOnce\u0026#34;] storageClassName: gp2 resources: requests: storage: 30Gi 编写zookeeper-service.yaml文件:\nvim zookeeper-service.yaml 写入内容:\napiVersion: v1 kind: Service metadata: name: zookeeper-1 namespace: dev labels: app: zookeeper-1 spec: ports: - name: client port: 2181 protocol: TCP - name: follower port: 2888 protocol: TCP - name: leader port: 3888 protocol: TCP selector: app: zookeeper-1 --- apiVersion: v1 kind: Service metadata: name: zookeeper-2 namespace: dev labels: app: zookeeper-2 spec: ports: - name: client port: 2181 protocol: TCP - name: follower port: 2888 protocol: TCP - name: leader port: 3888 protocol: TCP selector: app: zookeeper-2 --- apiVersion: v1 kind: Service metadata: name: zookeeper-3 namespace: dev labels: app: zookeeper-3 spec: ports: - name: client port: 2181 protocol: TCP - name: follower port: 2888 protocol: TCP - name: leader port: 3888 protocol: TCP selector: app: zookeeper-3 完成以上两个文件后,执行kubectl命令即可:\nkubectl apply -f zookeeper-statefulSet.yaml kubectl apply -f zookeeper-service.yaml 部署Kafka # 部署Kafka Connect # wget https://raw.githubusercontent.com/strimzi/strimzi-kafka-operator/0.16.1/examples/kafka/kafka-persistent-single.yaml 安装集群\nwget https://github.com/strimzi/strimzi-kafka-operator/releases/download/0.18.0/strimzi-cluster-operator-0.18.0.yaml 可能该文件下载下来你需要修改其中的namespace。\n« 可能需要运行多次以下命令,确保k8s资源都创建\n» Kubernetes 定制开发 01:K8s API 概念\n"},{"id":134,"href":"/kubernetes/k8s-dev-01-api-concept/","title":"K8s Dev 01 API Concept","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 定制开发 01:K8s API 概念\nKubernetes 定制开发 01:K8s API 概念 # 在 K8s 集群中,API 是一切的基础,K8s 的所有资源对象都是通过 API 来管理的,所以我们在定制开发的时候,首先要了解 K8s 的 API 概念。\n基本概念 # Group(G):\nAPI 组,例如:apps、networking.k8s.io 等\nVersion(V):\nAPI 版本,例如:v1alpha1、v1、v2 等\nResource(R):\nAPI 资源,例如:pods,configmaps 等\nKind(K):\nAPI 类型,例如:Deployment,Service 等 通过 kubectl api-versions 获取集群中所有 API 的版本列表:\n$ kubectl api-versions acme.cert-manager.io/v1 admissionregistration.k8s.io/v1 apiextensions.k8s.io/v1 apiregistration.k8s.io/v1 apps/v1 authentication.k8s.io/v1 通过 kubectl api-resources 命令获取集群所有 API 的资源列表,并且可以看到资源的简写名称,版本以及类型:\n$ kubectl api-resources NAME SHORTNAMES APIVERSION NAMESPACED KIND bindings v1 true Binding componentstatuses cs v1 false ComponentStatus configmaps cm v1 true ConfigMap endpoints ep v1 true Endpoints events ev v1 true Event limitranges limits v1 true LimitRange namespaces ns v1 false Namespace nodes no v1 false Node API 资源端点 # GVR 端点:\n/api,只会获取到 v1 的 APIVersion(组名为空) /apis,获取到所有非核心 API 的 APIGroupList /api/v1,获取到 APIResourceList /apis/{g}/,获取到 APIGroup /apis/{g}/v1,获取到 APIResourceList 命名空间资源端点:\n核心 api:/api/{v}/namespaces/{ns}/{r},例如:/api/v1/namespaces/default/configmaps 其他 api:/apis/{g}/{v}/namespaces/{ns}/{r},例如:apis/apps/v1/namespaces/default/deployments 集群资源端点:\n核心 api:/api/{v}/{r},例如:/api/v1/nodes 其他资源:/apis/{g}/{v}/{r},例如:/apis/rbac.authorization.k8s.io/v1/clusterroles 在使用 kubectl get 命令获取集群资源实例时,可以通过添加 -v 6 查看执行命令的请求端点:\n$ k get nodes -v 6 I0915 11:32:43.715795 18197 loader.go:373] Config loaded from file: /Users/dp/.kube/config I0915 11:32:43.855325 18197 round_trippers.go:553] GET https://127.0.01:6443/api/v1/nodes?limit=500 200 OK in 135 milliseconds NAME STATUS ROLES AGE VERSION local Ready control-plane 115d v1.27.2 APIService # 每个 API 版本都对应一个 APIService:\n$ k api-versions | wc -l 30 $ k get apiservices.apiregistration.k8s.io| wc -l 30 创建一个 CRD # foo-crd.yaml:\napiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: # 固定格式 {kind_plural}.{group},其中 foos 对应 spec.names.plural,play.ketches.io 对应 spec.group name: foos.play.ketches.io spec: group: play.ketches.io # 资源组,用在 URL 标识资源所属 Group,如 /apis/play.ketches.io/v1/foos 之 foos.play.ketches.io names: kind: Foo listKind: FooList plural: foos # 资源名复数,用在 URL 标识资源类型,如 /apis/play.ketches.io/v1/foos 之 foos singular: foo # 资源名单数,可用于 kubectl 匹配资源 shortNames: # 资源简称,可用于 kubectl 匹配资源 - fo scope: Namespaced # Namespaced/Cluster versions: - name: v1 served: true # 是否启用该版本,可使用该标识启动/禁用该版本 API storage: true # 唯一落存储版本,如果 CRD 含有多个版本,只能有一个版本被标识为 true schema: openAPIV3Schema: type: object properties: spec: type: object required: [\u0026#34;msg\u0026#34;] # 必须赋值 properties: msg: type: string maxLength: 6 additionalPrinterColumns: # 声明 kubectl get 输出列,默认在 name 列之外额外输出 age 列,改为额外输出 age 列,message 列 - name: age jsonPath: .metadata.creationTimestamp type: date - name: message jsonPath: .spec.msg type: string 获取 CRD 对应的 APIService:\n~ k get apiservices.apiregistration.k8s.io v1.play.ketches.io NAME SERVICE AVAILABLE AGE v1.play.ketches.io Local True 3h26m 一般这时候 SERVICE 为 Local,表示 对该资源的 APIService 请求会在本地处理,也就是 kube-apiserver 处理,我们可以将其改为一个在集群中运行的 Service 名,例如:default/play-v1-svc。你需要修改 APIService 的声明:\napiVersion: apiregistration.k8s.io/v1 kind: APIService metadata: labels: kube-aggregator.kubernetes.io/automanaged: \u0026#34;true\u0026#34; name: v1.play.ketches.io spec: group: play.ketches.io groupPriorityMinimum: 1000 version: v1 versionPriority: 100 service: name: play-v1-svc namespace: default insecureSkipTLSVerify: true 这时候对 play.ketches.io/v1 组下的资源的请求处理会交给该服务。 即:/apis/play.ketches.io/v1/namespaces/{ns}/foos/* =\u0026gt; default/play-v1-svc,其实这就是一个 自定义的 apiserver 了。 但是一个自定义的 apiserver 需要实现以下这些处理(Handler):\nfor API Discovery /apis /apis/play.ketches.io /apis/play.ketches.io/v1 for OpenAPI Schema /openapi/v2 /openapi/v3 for Foo CRUD /apis/play.ketches.io/v1/foos /apis/play.ketches.io/v1/namespaces/{namespace}/foos /apis/play.ketches.io/v1/namespaces/{namespace}/foos/{name} OpenAPI # API 的 Schema 会 kubectl explain 命令输出内容来源于 OpenAPI Spec。\n参考 # K8s CustomResourceDefinitions (CRD) 原理 实现一个极简 K8s apiserver « Kubernetes 0-1 K8s部署Zookeeper和Kafka\n» Kubernetes 定制开发 02:CRD\n"},{"id":135,"href":"/kubernetes/k8s-dev-02-crd/","title":"K8s Dev 02 Crd","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 定制开发 02:CRD\nKubernetes 定制开发 02:CRD # « Kubernetes 定制开发 01:K8s API 概念\n» Kubernetes 定制开发 50:扩展调度器\n"},{"id":136,"href":"/kubernetes/k8s-dev-50-extend-kube-scheduler/","title":"K8s Dev 50 Extend Kube Scheduler","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 定制开发 50:扩展调度器\nKubernetes 定制开发 50:扩展调度器 # 简介 # Kubernetes Scheduler(调度器)是一个控制面进程,负责将 Pods 指派到节点上。调度器基于约束和可用资源为调度队列中每个 Pod 确定其可合法放置的节点。调度器之后对所有合法的节点进行排序,将 Pod 绑定到一个合适的节点。\nkube-scheduler 是 Kubernetes 自带的一个默认调度器,它会根据 Pod 的资源需求和节点的资源容量,将 Pod 调度到合适的节点上。\n如果默认调度器不符合你的需求,你可以实现自己的调度器,并且你的调度器可以和默认调度器或其他调度器一起运行在集群中。你可以通过声明 Pod 的 spec.schedulerName 字段来指定要使用的调度器。\n扩展调度器 # 有三种方式可以实现自定义调度器:\n修改 kube-scheduler 源码调度逻辑,然后编译成定制的调度器镜像,然后使用这个镜像部署调度进程 自定义 Pod 控制器,监听 Pod 的 spec.schedulerName 字段,在 Pod 被创建时,为其绑定节点 使用 Scheduler Extender 的方式,这种方式不需要修改默认调度器的配置文件 编译定制调度器镜像 # 克隆 kubernetes 源码,然后修改 kube-scheduler 源码,然后编译成定制的调度器镜像。\ngit clone https://github.com/kubernetes/kubernetes.git cd kubernetes # 修改源码 make 编写 Dockerfile:\nFROM alpine ADD ./_output/local/bin/linux/amd64/kube-scheduler /usr/local/bin/kube-scheduler 编译并推送镜像:\ndocker build -t poneding/my-kube-scheduler:v1.0 . docker push poneding/my-kube-scheduler:v1.0 编写部署清单文件:\ndeploy-maniest.yaml:\napiVersion: v1 kind: ServiceAccount metadata: name: my-scheduler namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: my-scheduler-as-kube-scheduler subjects: - kind: ServiceAccount name: my-scheduler namespace: kube-system roleRef: kind: ClusterRole name: system:kube-scheduler apiGroup: rbac.authorization.k8s.io --- apiVersion: v1 kind: ConfigMap metadata: name: my-scheduler-config namespace: kube-system data: my-scheduler-config.yaml: | apiVersion: kubescheduler.config.k8s.io/v1 kind: KubeSchedulerConfiguration profiles: - schedulerName: my-scheduler leaderElection: leaderElect: false --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: my-scheduler-as-volume-scheduler subjects: - kind: ServiceAccount name: my-scheduler namespace: kube-system roleRef: kind: ClusterRole name: system:volume-scheduler apiGroup: rbac.authorization.k8s.io --- apiVersion: apps/v1 kind: Deployment metadata: labels: component: scheduler tier: control-plane name: my-scheduler namespace: kube-system spec: selector: matchLabels: component: scheduler tier: control-plane replicas: 1 template: metadata: labels: component: scheduler tier: control-plane spec: serviceAccountName: my-scheduler containers: - command: - /usr/local/bin/kube-scheduler - --config=/etc/kubernetes/my-scheduler/my-scheduler-config.yaml image: poneding/my-kube-scheduler:v1.0 livenessProbe: httpGet: path: /healthz port: 10259 scheme: HTTPS initialDelaySeconds: 15 name: my-scheduler readinessProbe: httpGet: path: /healthz port: 10259 scheme: HTTPS resources: requests: cpu: \u0026#39;0.1\u0026#39; securityContext: privileged: false volumeMounts: - name: config-volume mountPath: /etc/kubernetes/my-scheduler hostNetwork: false hostPID: false volumes: - name: config-volume configMap: name: my-scheduler-config 部署:\nkubectl apply -f deploy-maniest.yaml 测试:\nkubectl run nginx-by-my-scheduler --image=nginx --overrides=\u0026#39;{\u0026#34;spec\u0026#34;:{\u0026#34;schedulerName\u0026#34;:\u0026#34;my-scheduler\u0026#34;}}\u0026#39; kubectl get pod -o wide -w 如果一切正常,将观察到 Pod 将会被正常调度到节点上。\n使用这种方式来扩展调度器,对开发者来说,需要了解调度器的源码然后修改逻辑,有一定的难度。\n自定义调度控制器 # 基于 controller-runtime 包编写一个调度控制器,原理是通过协调 Pod ,选择一个适合的节点,创建 Binding 对象,将 Pod 绑定到指定的节点上。\n创建项目:\nmkdir my-scheduler \u0026amp;\u0026amp; cd my-scheduler go mod init my-scheduler touch main.go 编写 main.go 调度器逻辑(本质是一个 Pod 的协调控制器):\npackage main import ( \u0026#34;context\u0026#34; \u0026#34;log\u0026#34; \u0026#34;math/rand\u0026#34; corev1 \u0026#34;k8s.io/api/core/v1\u0026#34; \u0026#34;k8s.io/apimachinery/pkg/runtime\u0026#34; ctrl \u0026#34;sigs.k8s.io/controller-runtime\u0026#34; \u0026#34;sigs.k8s.io/controller-runtime/pkg/client\u0026#34; \u0026#34;sigs.k8s.io/controller-runtime/pkg/event\u0026#34; \u0026#34;sigs.k8s.io/controller-runtime/pkg/manager\u0026#34; \u0026#34;sigs.k8s.io/controller-runtime/pkg/predicate\u0026#34; ) func main() { mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), manager.Options{}) if err != nil { log.Fatalf(\u0026#34;new manager err: %s\u0026#34;, err.Error()) } err = (\u0026amp;MyScheduler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr) if err != nil { log.Fatalf(\u0026#34;setup scheduler err: %s\u0026#34;, err.Error()) } err = mgr.Start(context.Background()) if err != nil { log.Fatalf(\u0026#34;start manager err: %s\u0026#34;, err.Error()) } } const mySchedulerName = \u0026#34;my-scheduler\u0026#34; type MyScheduler struct { Client client.Client Scheme *runtime.Scheme } func (s *MyScheduler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { nodes := new(corev1.NodeList) err := s.Client.List(ctx, nodes) if err != nil { return ctrl.Result{Requeue: true}, err } // 随机选择一个节点 targetNode := nodes.Items[rand.Intn(len(nodes.Items))].Name // 创建绑定关系 binding := new(corev1.Binding) binding.Name = req.Name binding.Namespace = req.Namespace binding.Target = corev1.ObjectReference{ Kind: \u0026#34;Node\u0026#34;, APIVersion: \u0026#34;v1\u0026#34;, Name: targetNode, } err = s.Client.Create(ctx, binding) if err != nil { return ctrl.Result{Requeue: true}, err } return ctrl.Result{}, nil } // SetupWithManager sets up the controller with the Manager. func (s *MyScheduler) SetupWithManager(mgr ctrl.Manager) error { // 过滤目标 Pod filter := predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { pod, ok := e.Object.(*corev1.Pod) if ok { return pod.Spec.SchedulerName == mySchedulerName \u0026amp;\u0026amp; pod.Spec.NodeName == \u0026#34;\u0026#34; } return false }, UpdateFunc: func(e event.UpdateEvent) bool { return false }, DeleteFunc: func(e event.DeleteEvent) bool { return false }, } return ctrl.NewControllerManagedBy(mgr). For(\u0026amp;corev1.Pod{}). WithEventFilter(filter). Complete(s) } 运行自定义调度器:\ngo run main.go 也可以参考前面的部署方式,先制作一个镜像,然后部署到集群中。\n运行一个 Pod,指定调度器为 my-scheduler:\nkubectl run nginx-by-my-scheduler --image=nginx --overrides=\u0026#39;{\u0026#34;spec\u0026#34;:{\u0026#34;schedulerName\u0026#34;:\u0026#34;my-scheduler\u0026#34;}}\u0026#39; 一切正常的话,将会观察到 Pod 被正常调度到节点上。\nScheduler Extender # 通过 Scheduler Extender 来扩展 Kubernetes 调度器,它将以 Webhook 的形式运行,并且在调度器框架阶段中进行干扰。\n阶段 描述 Filter 调度框架将调用过滤函数,过滤掉不适合被调度的节点。 Priority 调度框架将调用优先级函数,为每个节点计算一个优先级,优先级越高,节点越适合被调度。 Bind 调度框架将调用绑定函数,将 Pod 绑定到一个节点上。 Scheduler Extender 通过 HTTP 请求的方式,将调度框架阶段中的调度决策委托给外部的调度器,然后将调度结果返回给调度框架。我们只需要实现一个 HTTP 服务,然后将其注册到调度器中,就可以实现自定义调度器。在这个 HTTP 服务中,我们可以实现上述阶段中的任意一个或多个阶段的接口,来定制我们的调度需求。\n接口列表:\nFilter 接口 # 接口方法:POST\n接口请求参数:\ntype ExtenderArgs struct { Pod *v1.Pod Nodes *v1.NodeList NodeNames *[]string } 接口请求结果:\ntype ExtenderFilterResult struct { Nodes *v1.NodeList NodeNames *[]string FailedNodes FailedNodesMap FailedAndUnresolvableNodes FailedNodesMap Error string } Priority 接口 # 接口方法:POST\n接口请求参数:和 Filter 接口请求参数一致。\n接口请求结果:\ntype HostPriorityList []HostPriority type HostPriority struct { Host string Score int64 } Bind 接口 # 接口方法:POST\n接口请求参数:\ntype ExtenderBindingArgs struct { PodName string PodNamespace string PodUID types.UID Node string } 接口请求结果:\ntype ExtenderBindingResult struct { Error string } 实现 # 我们使用 Scheduler Extender 的方式来实现自定义调度器,供参考。\nmain.go:\npackage main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; extenderv1 \u0026#34;k8s.io/kube-scheduler/extender/v1\u0026#34; ) func Filter(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { w.WriteHeader(http.StatusMethodNotAllowed) return } var args extenderv1.ExtenderArgs var result *extenderv1.ExtenderFilterResult err := json.NewDecoder(r.Body).Decode(\u0026amp;args) if err != nil { result = \u0026amp;extenderv1.ExtenderFilterResult{ Error: err.Error(), } } else { result = filter(args) } w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(result); err != nil { log.Printf(\u0026#34;failed to encode result: %v\u0026#34;, err) } } func Prioritize(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { w.WriteHeader(http.StatusMethodNotAllowed) return } var args extenderv1.ExtenderArgs var result *extenderv1.HostPriorityList err := json.NewDecoder(r.Body).Decode(\u0026amp;args) if err != nil { result = \u0026amp;extenderv1.HostPriorityList{} } else { result = prioritize(args) } w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(result); err != nil { log.Printf(\u0026#34;failed to encode result: %v\u0026#34;, err) } } func Bind(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { w.WriteHeader(http.StatusMethodNotAllowed) return } var args extenderv1.ExtenderBindingArgs var result *extenderv1.ExtenderBindingResult err := json.NewDecoder(r.Body).Decode(\u0026amp;args) if err != nil { result = \u0026amp;extenderv1.ExtenderBindingResult{ Error: err.Error(), } } else { result = bind(args) } w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(result); err != nil { log.Printf(\u0026#34;failed to encode result: %v\u0026#34;, err) } } func main() { http.HandleFunc(\u0026#34;/filter\u0026#34;, Filter) http.HandleFunc(\u0026#34;/priority\u0026#34;, Prioritize) http.HandleFunc(\u0026#34;/bind\u0026#34;, Bind) http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil) } filter.go:没有具体实现节点过滤逻辑,直接返回所有节点。\npackage main import ( \u0026#34;log\u0026#34; extenderv1 \u0026#34;k8s.io/kube-scheduler/extender/v1\u0026#34; ) func filter(args extenderv1.ExtenderArgs) *extenderv1.ExtenderFilterResult { log.Println(\u0026#34;my-scheduler-extender filter called.\u0026#34;) return \u0026amp;extenderv1.ExtenderFilterResult{ Nodes: args.Nodes, NodeNames: args.NodeNames, } } prioritize.go:模拟打分,按照节点顺序给节点累加一个分数。\npackage main import ( \u0026#34;log\u0026#34; extenderv1 \u0026#34;k8s.io/kube-scheduler/extender/v1\u0026#34; ) func prioritize(args extenderv1.ExtenderArgs) *extenderv1.HostPriorityList { log.Println(\u0026#34;my-scheduler-extender prioritize called.\u0026#34;) var result extenderv1.HostPriorityList for i, node := range args.Nodes.Items { result = append(result, extenderv1.HostPriority{ Host: node.Name, Score: int64(i), }) } return \u0026amp;result } bind.go:没有具体实现绑定逻辑,直接返回成功。\npackage main import ( \u0026#34;context\u0026#34; \u0026#34;log\u0026#34; corev1 \u0026#34;k8s.io/api/core/v1\u0026#34; \u0026#34;k8s.io/client-go/kubernetes/scheme\u0026#34; \u0026#34;k8s.io/client-go/rest\u0026#34; extenderv1 \u0026#34;k8s.io/kube-scheduler/extender/v1\u0026#34; ctrl \u0026#34;sigs.k8s.io/controller-runtime\u0026#34; \u0026#34;sigs.k8s.io/controller-runtime/pkg/client\u0026#34; ) var kconfig *rest.Config var kruntimeclient client.Client func init() { kconfig = ctrl.GetConfigOrDie() var err error kruntimeclient, err = client.New(kconfig, client.Options{ Scheme: scheme.Scheme, }) if err != nil { log.Fatalf(\u0026#34;failed to create k8s runtime client: %v\u0026#34;, err) } } func bind(args extenderv1.ExtenderBindingArgs) *extenderv1.ExtenderBindingResult { log.Println(\u0026#34;my-scheduler-extender bind called.\u0026#34;) log.Printf(\u0026#34;pod %s/%s is bind to %s\u0026#34;, args.PodNamespace, args.PodName, args.Node) // 创建绑定关系 binding := new(corev1.Binding) binding.Name = args.PodName binding.Namespace = args.PodNamespace binding.Target = corev1.ObjectReference{ Kind: \u0026#34;Node\u0026#34;, APIVersion: \u0026#34;v1\u0026#34;, Name: args.Node, } result := new(extenderv1.ExtenderBindingResult) err := kruntimeclient.Create(context.Background(), binding) if err != nil { result.Error = err.Error() } return result } 编译成二进制文件:\nGOOS=linux GOARCH=amd64 go build -o my-scheduler-extender 编写 Dockerfile:\nFROM alpine ARG TARGETOS TARGETARCH ADD ./bin/$TARGETOS/$TARGETARCH/my-scheduler-extender /my-scheduler-extender ENTRYPOINT [\u0026#34;/my-scheduler-extender\u0026#34;] 编译并推送镜像:\nGOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o bin/linux/amd64/my-scheduler-extender GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o bin/linux/arm64/my-scheduler-extender docker buildx build --push --platform linux/amd64,linux/arm64 -t poneding/my-kube-scheduler-extender:v1.0 . 编写部署清单文件,部署清单中包括额外的调度器(参考上述编译定制调度器镜像的方式)和我们开发的 Scheduler Extender:\n注意:为了简化部署清单,给了 my-scheduler-extender 和 my-scheduler-with-extender 容器 cluster-admin 权限,实际上不需要这么高的权限。\ndeploy-manifests.yaml:\napiVersion: v1 kind: ServiceAccount metadata: name: my-scheduler-with-extender namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: my-scheduler-with-extender subjects: - kind: ServiceAccount name: my-scheduler-with-extender namespace: kube-system roleRef: kind: ClusterRole name: cluster-admin apiGroup: rbac.authorization.k8s.io --- apiVersion: v1 kind: ConfigMap metadata: name: my-scheduler-with-extender-config namespace: kube-system data: my-scheduler-with-extender-config.yaml: | apiVersion: kubescheduler.config.k8s.io/v1 kind: KubeSchedulerConfiguration profiles: - schedulerName: my-scheduler-with-extender leaderElection: leaderElect: false extenders: - urlPrefix: \u0026#34;http://my-scheduler-extender.kube-system.svc:8080\u0026#34; enableHTTPS: false filterVerb: \u0026#34;filter\u0026#34; prioritizeVerb: \u0026#34;prioritize\u0026#34; bindVerb: \u0026#34;bind\u0026#34; weight: 1 nodeCacheCapable: false --- apiVersion: apps/v1 kind: Deployment metadata: labels: component: my-scheduler-with-extender tier: control-plane name: my-scheduler-with-extender namespace: kube-system spec: selector: matchLabels: component: my-scheduler-with-extender tier: control-plane replicas: 1 template: metadata: labels: component: my-scheduler-with-extender tier: control-plane spec: serviceAccountName: my-scheduler-with-extender containers: - command: - kube-scheduler - --config=/etc/kubernetes/my-scheduler-with-extender/my-scheduler-with-extender-config.yaml image: registry.k8s.io/kube-scheduler:v1.29.0 livenessProbe: httpGet: path: /healthz port: 10259 scheme: HTTPS initialDelaySeconds: 15 name: my-scheduler-with-extender readinessProbe: httpGet: path: /healthz port: 10259 scheme: HTTPS resources: requests: cpu: \u0026#39;0.1\u0026#39; securityContext: privileged: false volumeMounts: - name: config-volume mountPath: /etc/kubernetes/my-scheduler-with-extender hostNetwork: false hostPID: false volumes: - name: config-volume configMap: name: my-scheduler-with-extender-config --- apiVersion: apps/v1 kind: Deployment metadata: labels: component: my-scheduler-extender tier: control-plane name: my-scheduler-extender namespace: kube-system spec: selector: matchLabels: component: my-scheduler-extender tier: control-plane replicas: 1 template: metadata: labels: component: my-scheduler-extender tier: control-plane spec: serviceAccountName: my-scheduler-with-extender containers: - image: poneding/my-kube-scheduler-extender:v1.0 name: my-scheduler-extender imagePullPolicy: Always --- apiVersion: v1 kind: Service metadata: name: my-scheduler-extender namespace: kube-system spec: selector: component: my-scheduler-extender tier: control-plane ports: - port: 8080 targetPort: 8080 部署:\nkubectl apply -f deploy-manifests.yaml 运行一个测试 Pod,查看 my-scheduler-extender 容器的日志:\nkubectl run nginx-by-my-scheduler-extender --image=nginx --overrides=\u0026#39;{\u0026#34;spec\u0026#34;:{\u0026#34;schedulerName\u0026#34;:\u0026#34;my-scheduler-with-extender\u0026#34;}}\u0026#39; # 查看 my-scheduler-extender 日志 kubectl logs deploy/my-scheduler-extender -n kube-system -f 代码传送门: my-scheduler-extender\n参考 # 调度器配置 Kubernetes Extender Create a custom Kubernetes scheduler « Kubernetes 定制开发 02:CRD\n» 简单介绍 K8s\n"},{"id":137,"href":"/kubernetes/k8s-get-started/","title":"K8s Get Started","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 简单介绍 K8s\n简单介绍 K8s # 简介 # 我们的应用部署趋势由大型单体应用向微服务演变,微服务应用之间解耦,形成可被独立开发、部署、升级、伸缩的软件单元。\n另一方面容器技术由于它的轻量级,资源隔离,可移植、部署高效等特性得到了迅速的发展和普及。越来越多的应用选择使用容器来部署,微服务更不例外。\n这时,便有了管理微服务+容器的需求,Kubernetes 开始大放异彩。\nKubernetes 是基于容器技术的服务器集群管理系统,通过它,可以托管数量庞大的应用集,并且内置完备的集群管理能力,它有能力帮你做到这些:\n应用的容器化部署、健康检查、自我修复、自动伸缩、滚动更新、资源分配等 服务的注册、发现、均衡负载等 Kubernetes 对于运维团队来说,是一个强大的帮手,更自动化的部署和管理应用,更高效的利用硬件资源。\n基本概念 # Kubernetes 集群,后面简称为K8s,主要是由控制节点(Master)和工作节点(Node)组成。\n在 Master 节点中运行着三大组件:kube-api-server、kube-controller-manager、kube-scheduler,通常也会将 etcd 数据库部署在 Master 节点。\n在 Node 节点中,也是需要部署三个主要组件,容器引擎(基本默认 docker 了)、kubelet、kube-proxy。\nK8s 的组成结构大致如下图:\nMaster 节点 # 负责 K8s 资源的调度管理,由 Master 向 Node 下达控制命令,并且一般运维人员使用 Master 操作和执行命令。\nMaster 节点扮演的角色相当于 K8s 的大脑,其重要性可想而知,因此建议部署 3 台 Master 节点保证 K8s 的高可用性。\nkube-api-server:http rest 接口服务,与 K8s 其他组件通信,负责 K8s 资源的 CURD 的操作入口;\nkube-controller-manager:K8s 资源的自动化控制管理中心,如跟踪资源状态,资源修复等;\nkube-scheduler:应用调度,为应用自动分配节点等;\netcd:分布式的数据库系统,存储 K8s 中的各种资源信息。\nNode 节点 # 负责 K8s 应用资源的容器化部署和运行,Node 节点配置要求一般高于 Master,当其中一个 Node 不可用时,部署在其上的 K8s 资源会自动被迁移到可用的 Node。\ndocker:部署应用的容器引擎; kubelet:与 kube-api-server 通信,向 Master 注册自己,定时汇报自身 Node 信息,Master 下达调度命令,同时负责容器生命周期的管理; kube-proxy:实现 service 代理 endpoint 和负载均衡,分配 Node 端口等。 其实了解了 K8s 的整个组成和相关组件服务的功能,我们就能知道如何来管理我们的 K8s 节点了。例如,我们要新增 Node 节点,该如何操作?\n原理上新扩 Node 只需要在一台新的机器上安装以上三个服务,然后配置 kubectl 和 kube-proxy 的启动参数,通过 kubelet 向 Master的kube-api-server 提交 Node 注册信息,就可以将 Node 纳入 Master 的调度中心了。\n工具 # kubeadm:\n使用 kubeadm 快速创建 K8s 集群,一般仅用于学习测试使用。\nkubectl:\n使用 kubectl 命令工具对 K8s 下达操作指令,一般将这个工具安装在 Master 节点。\n« Kubernetes 定制开发 50:扩展调度器\n» kubeadm 安装 Kubernetes (Docker)\n"},{"id":138,"href":"/kubernetes/kubeadm-install-k8s-docker/","title":"Kubeadm Install K8s Docker","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / kubeadm 安装 Kubernetes (Docker)\nkubeadm 安装 Kubernetes (Docker) # 使用 kubeadm 安装 k8s 集群,是社区推荐的安装方式,本文档将介绍使用 kubeadm 安装 k8s 集群(使用 Docker 作为容器运行时)的详细过程。\nNotes:\n随着 kubeadm \u0026amp; k8s 版本的更新,安装过程可能会有所不同,截至目前,本文档使用的是 kubeadm v1.28.3 \u0026amp; k8s v1.28.3 版本; 本文档使用的操作系统是 Ubuntu 22.04,其他操作系统可能会有所不同。 要求 # 至少一台物理机或虚拟机(例如:Ubuntu 22.04)作为集群节点,最少 2 核 2G 内存; 多节点之前网络互通,且节点主机名不冲突; Master 节点需要开放以下端口:6443、2379-2380、10250、10251、10252; 准备工作 # 禁用交换分区:\n# 临时禁用交换分区 sudo swapoff -a vim /etc/fstab # 注释掉 swap 分区的配置 配置系统:\ncat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter # sysctl params required by setup, params persist across reboots cat \u0026lt;\u0026lt;EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 EOF # Apply sysctl params without reboot sudo sysctl --system 安装 Docker # 设置 Docker 的 APT 源:\n# Add Docker\u0026#39;s official GPG key: sudo apt-get update sudo apt-get install ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg # Add the repository to Apt sources: echo \\ \u0026#34;deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \\ $(. /etc/os-release \u0026amp;\u0026amp; echo \u0026#34;$VERSION_CODENAME\u0026#34;) stable\u0026#34; | \\ sudo tee /etc/apt/sources.list.d/docker.list \u0026gt; /dev/null sudo apt-get update 安装 Docker:\nsudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin 官方文档: Docker 安装。\n安装 cri-dockerd # wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.8/cri-dockerd-0.3.8.amd64.tgz tar -xzvf cri-dockerd-0.3.8.amd64.tgz sudo install -m 0755 -o root -g root -t /usr/local/bin cri-dockerd/cri-dockerd wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.service wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.socket sudo install cri-docker.service /etc/systemd/system sudo install cri-docker.socket /etc/systemd/system sudo sed -i -e \u0026#39;s,/usr/bin/cri-dockerd,/usr/local/bin/cri-dockerd,\u0026#39; /etc/systemd/system/cri-docker.service sudo systemctl daemon-reload sudo systemctl enable --now cri-docker.socket sudo systemctl start cri-docker.service 社区文档: cri-dockerd 安装\n安装 kubeadm、kubelet 和 kubectl # sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg # 国内网络使用下面命令替换 # curl -fsSL https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add - echo \u0026#39;deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /\u0026#39; | sudo tee /etc/apt/sources.list.d/kubernetes.list # 国内网络使用下面命令替换 # cat \u0026lt;\u0026lt; EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list # deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main # EOF sudo apt-get update sudo apt-get install -y kubelet kubeadm kubectl sudo apt-mark hold kubelet kubeadm kubectl 早于 Debian 12 和 Ubuntu 22.04 的版本,/etc/apt/keyrings 目录默认不存在,需要手动创建:sudo mkdir -m 755 /etc/apt/keyrings\n配置 cgroup-driver # # 查看 cgroup-driver docker info | grep \u0026#34;Cgroup Driver\u0026#34; 配置使用 systemd 作为 cgroup-driver:\nvim /etc/docker/daemon.json 添加或修改配置项:\n{ ... \u0026#34;exec-opts\u0026#34;: [ \u0026#34;native.cgroupdriver=systemd\u0026#34; ] } 配置 kubelet 使用 systemd 作为 cgroup-driver(首次创建集群时,该文件并未生成,如果首次集群创建失败,那么可能已经生成该文件了,此时可以修改这个文件的配置):\nvim /etc/systemd/system/kubelet.service.d/10-kubeadm.conf 添加或修改配置项:\n... Environment=\u0026#34;KUBELET_EXTRA_ARGS=--cgroup-driver=systemd\u0026#34; ... 安装集群 # 国内网络提前拉取 registry.k8s.io/pause:3.9 镜像:\ndocker pull registry.aliyuncs.com/google_containers/pause:3.9 docker tag registry.aliyuncs.com/google_containers/pause:3.9 registry.k8s.io/pause:3.9 使用 kubeadm init 初始化集群:\nsudo kubeadm init \\ --pod-network-cidr 10.244.0.0/16 \\ --kubernetes-version 1.28.3 \\ --control-plane-endpoint=\u0026lt;EXTERNAL_IP\u0026gt;:6443 \\ --ignore-preflight-errors=Swap \\ --cri-socket=unix:///var/run/cri-dockerd.sock Notes:\n需要确保 --control-plane-endpoint 端点在执行环境是可以访问的,如果参数值为服务器的公网 IP,那么你可能需要对安全组开通 6443 端口; 国内网络拉取镜像使用代理添加命令参数:--image-repository registry.aliyuncs.com/google_containers。 初始化完成后,执行以下命令,配置集群访问环境:\nmkdir -p ~/.kube sudo cp -i /etc/kubernetes/admin.conf ~/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config 查看集群节点状态:\nkubectl get nodes 如果允许 Pod 调度到 Master 节点,那么需要去除 Master 节点的污点:\nkubectl taint nodes --all node-role.kubernetes.io/control-plane- 安装网络插件 # 安装 flanenel 网络插件:\nkubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml flannel 默认使用 CIDR 为 10.244.0.0/16,需要与 kubeadm init 时指定的 --pod-network-cidr 参数一致。\n安装 metrics-server # kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml 在多数情况下,metrics-server 由于证书问题无法正常启动,需要修改 metrics-server 的 Deployment 配置,添加 --kubelet-insecure-tls 参数:\nkubectl edit deployment metrics-server -n kube-system ... spec: ... template: ... spec: containers: - args: - --cert-dir=/tmp - --secure-port=4443 - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname - --kubelet-use-node-status-port - --metric-resolution=15s - --kubelet-insecure-tls 部署应用 # kubectl create deployment nginx --image=nginx kubectl expose deployment nginx --name=nginx --port=80 --target-port=80 --type=NodePort 参考 # Bootstrapping clusters with kubeadm Container Runtimes « 简单介绍 K8s\n» kubeadm 安装 k8s (containerd)\n"},{"id":139,"href":"/kubernetes/kubeadm-install-k8s/","title":"Kubeadm Install K8s","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / kubeadm 安装 k8s (containerd)\nkubeadm 安装 k8s (containerd) # 使用 kubeadm 安装 k8s 集群,是社区推荐的安装方式,本文档将介绍使用 kubeadm 安装 k8s 集群的详细过程。\nNotes:\n随着 kubeadm \u0026amp; k8s 版本的更新,安装过程可能会有所不同,截至目前,本文档使用的是 kubeadm v1.28.3 \u0026amp; k8s v1.28.3 版本; 本文档使用的操作系统是 Ubuntu 22.04,其他操作系统可能会有所不同。 要求 # 至少一台物理机或虚拟机(例如:Ubuntu 22.04)作为集群节点,最少 2 核 2G 内存; 多节点之前网络互通,且节点主机名不冲突; Master 节点需要开放以下端口:6443、2379-2380、10250、10251、10252; 准备工作 # 禁用交换分区:\n# 临时禁用交换分区 sudo swapoff -a vim /etc/fstab # 注释掉 swap 分区的配置 配置系统:\ncat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter # sysctl params required by setup, params persist across reboots cat \u0026lt;\u0026lt;EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 EOF # Apply sysctl params without reboot sudo sysctl --system 安装 containerd # 参照 containerd 安装。\nexport LATEST=$(curl -s https://api.github.com/repos/containerd/containerd/releases/latest | jq -r .tag_name) LATEST=${LATEST#v} wget https://github.com/containerd/containerd/releases/download/v$LATEST/containerd-$LATEST-linux-amd64.tar.gz sudo tar Cxzvf /usr/local containerd-$LATEST-linux-amd64.tar.gz # systemd 配置 sudo wget https://raw.githubusercontent.com/containerd/containerd/main/containerd.service -O /lib/systemd/system/containerd.service sudo systemctl daemon-reload sudo systemctl enable --now containerd.service sudo systemctl restart containerd.service 安装 kubeadm、kubelet 和 kubectl # sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg # 国内网络使用下面命令替换 # curl -fsSL https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add - echo \u0026#39;deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /\u0026#39; | sudo tee /etc/apt/sources.list.d/kubernetes.list # 国内网络使用下面命令替换 # cat \u0026lt;\u0026lt; EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list # deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main # EOF sudo apt-get update sudo apt-get install -y kubelet kubeadm kubectl sudo apt-mark hold kubelet kubeadm kubectl 早于 Debian 12 和 Ubuntu 22.04 的版本,/etc/apt/keyrings 目录默认不存在,需要手动创建:sudo mkdir -m 755 /etc/apt/keyrings\n安装集群 # 国内网络提前拉取 registry.k8s.io/pause:3.9 镜像:\ndocker pull registry.aliyuncs.com/google_containers/pause:3.9 docker tag registry.aliyuncs.com/google_containers/pause:3.9 registry.k8s.io/pause:3.9 使用 kubeadm init 初始化集群:\nsudo kubeadm init \\ --pod-network-cidr 10.244.0.0/16 \\ --kubernetes-version 1.29.0 \\ --control-plane-endpoint=\u0026lt;EXTERNAL_IP\u0026gt;:6443 \\ --ignore-preflight-errors=Swap Notes:\n需要确保 --control-plane-endpoint 端点在执行环境是可以访问的,如果参数值为服务器的公网 IP,那么你可能需要对安全组开通 6443 端口; 国内网络拉取镜像使用代理添加命令参数:--image-repository registry.aliyuncs.com/google_containers。 初始化完成后,执行以下命令,配置集群访问环境:\nmkdir -p ~/.kube sudo cp -i /etc/kubernetes/admin.conf ~/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config 查看集群节点状态:\nkubectl get nodes 如果允许 Pod 调度到 Master 节点,那么需要去除 Master 节点的污点:\nkubectl taint nodes --all node-role.kubernetes.io/control-plane- 安装网络插件 # 安装 flannel 网络插件:\nkubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml flannel 默认使用 CIDR 为 10.244.0.0/16,需要与 kubeadm init 时指定的 --pod-network-cidr 参数一致。\n安装 metrics-server # kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml 在多数情况下,metrics-server 由于证书问题无法正常启动,需要修改 metrics-server 的 Deployment 配置,添加 --kubelet-insecure-tls 参数:\nkubectl edit deployment metrics-server -n kube-system ... spec: ... template: ... spec: containers: - args: - --cert-dir=/tmp - --secure-port=4443 - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname - --kubelet-use-node-status-port - --metric-resolution=15s - --kubelet-insecure-tls 部署应用 # kubectl create deployment nginx --image=nginx kubectl expose deployment nginx --name=nginx --port=80 --target-port=80 --type=NodePort 参考 # Bootstrapping clusters with kubeadm Container Runtimes « kubeadm 安装 Kubernetes (Docker)\n» Kubeadm 升级 K8s\n"},{"id":140,"href":"/kubernetes/kubeadm-upgrade/","title":"Kubeadm Upgrade","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubeadm 升级 K8s\nKubeadm 升级 K8s # 本篇以升级 1.29.0(旧版本:1.28.x ) 版本为例,介绍如何通过 kubeadm 工具来升级 K8s 集群。\n注意:\n不支持跨主版本升级,如 1.27.x 升级到 1.29.x,中间必须先升级到 1.28.x 主版本更新必须先升级到最新的次版本,如 1.28.3 升级到 1.28.4,然后再升级到 1.29.x 升级步骤 # 控制节点(control plane node)升级 工作节点(worker node)升级 升级过程 # 1、升级至当前主版本的最新次版本 # sudo apt update sudo apt-cache madison kubeadm 以上命令后,将可以得到类似如下输出:\n$ sudo apt-cache madison kubeadm kubeadm | 1.28.4-1.1 | https://pkgs.k8s.io/core:/stable:/v1.28/deb Packages kubeadm | 1.28.3-1.1 | https://pkgs.k8s.io/core:/stable:/v1.28/deb Packages kubeadm | 1.28.2-1.1 | https://pkgs.k8s.io/core:/stable:/v1.28/deb Packages kubeadm | 1.28.1-1.1 | https://pkgs.k8s.io/core:/stable:/v1.28/deb Packages kubeadm | 1.28.0-1.1 | https://pkgs.k8s.io/core:/stable:/v1.28/deb Packages 确定当前主版本的最新次版本为 1.28.4-1.1,然后执行以下命令(控制节点上执行):\nexport VERSION=1.28.4-1.1 sudo apt-mark unhold kubeadm \u0026amp;\u0026amp; \\ sudo apt-get update \u0026amp;\u0026amp; sudo apt-get install -y kubeadm=$VERSION \u0026amp;\u0026amp; \\ sudo apt-mark hold kubeadm sudo kubeadm version sudo kubeadm upgrade plan sudo kubeadm upgrade apply v$(echo $VERSION | cut -d\u0026#39;-\u0026#39; -f1) 其他节点升级,最后一步执行 sudo kubeadm upgrade node 命令。\n升级 kubectl、kubelet:\n如果集群包括多个节点,那么在升级单个节点上的 kubelet 服务前,最好先将当前节点置为不可调度状态,并且将其上的 Pod 驱逐到其他节点上。\nkubectl drain [current-node] --ignore-daemonsets 等待当前节点上的 Pod 驱逐完成后,再升级 kubelet 服务。升级完成后,再将当前节点置为可调度状态。\nkubectl uncordon [current-node] 执行一下命令,升级 kubectl、kubelet 组件:\nsudo apt-mark unhold kubelet kubectl \u0026amp;\u0026amp; \\ sudo apt-get update \u0026amp;\u0026amp; sudo apt-get install -y kubelet=$VERSION kubectl=$VERSION \u0026amp;\u0026amp; \\ sudo apt-mark hold kubelet kubectl # 重启 kubelet 服务 sudo systemctl daemon-reload sudo systemctl restart kubelet.service 2、升级至下一个主版本 # 修改 /etc/apt/sources.list.d/kubernetes.list 文件,将 1.28 改为 1.29:\nsudo sed -i \u0026#39;s/1.28/1.29/g\u0026#39; /etc/apt/sources.list.d/kubernetes.list 然后参照上面的步骤,升级集群到下一个主版本 v1.29.x。\n参考 # Upgrading kubeadm clusters « kubeadm 安装 k8s (containerd)\n» kubebuilder 实战\n"},{"id":141,"href":"/kubernetes/kubebuilder-inaction/","title":"Kubebuilder Inaction","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / kubebuilder 实战\nkubebuilder 实战 # 简介 # kubebuilder 是一个构建 Operator(CRD + Controller)的框架的工具,它可以帮助我们快速的构建一个 Operator 项目,并提供了一些常用的命令,例如:创建 API、创建 Controller、Webhook 等。\n安装 # 条件 # kustomize curl -s \u0026#34;https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh\u0026#34; | bash controller-gen go install sigs.k8s.io/controller-tools/cmd/controller-gen@latest ⚠️ 注意:以上命令都是直接下载了对应命令工具最新的版本,在使用 kubebuilder 创建项目之后,在 Makefile 文件中会指定 kustomize 和 controller-gen 的版本,为了避免不兼容,推荐下载对应指定的版本。\n使用以下命令安装 kubebuilder:\n# download kubebuilder and install locally. GOOS=$(go env GOOS) GOARCH=$(go env GOARCH) curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/$GOOS/$GOARCH chmod +x kubebuilder \u0026amp;\u0026amp; mv kubebuilder /usr/local/bin/ 代码自动补全:\nsource \u0026lt;(kubebuilder completion bash) source \u0026lt;(kubebuilder completion zsh) # zsh echo \u0026#34;source \u0026lt;(kubebuilder completion zsh)\u0026#34; \u0026gt;\u0026gt; ~/.zshrc # bash echo \u0026#34;source \u0026lt;(kubebuilder completion bash)\u0026#34; \u0026gt;\u0026gt; ~/.bashrc 创建项目 # mkdir ./kube-acme \u0026amp;\u0026amp; cd ./kube-acme kubebuilder init --domain ketches.cn --repo github.com/ketches/kube-acme # 如果不开启多 Group 模式,生成的 API 文件路径为:api/{version} # 开启多 Group 模式,生成 API 的文件路径为 apis/{grouop}/{version} kubebuilder edit --multigroup 创建项目后,会在目录中生成 hack/boilerplate.go.txt 文件,用于kubebuilder 生成 go 文件的版权信息,建议在开始编写代码之前修改该文件,例如作者信息。\n创建 API # kubebuilder create api --group acme --version v1alpha1 --kind DNSProvider # 连续输入 y 确认生成 API 以及 Controller 文件 在集群中部署 # 将控制器编译成镜像并推送到镜像仓库:\nmake docker-build docker-push IMG=ketches/kube-acme:v0.0.1 将控制器部署到集群中:\nmake deploy IMG=ketches/kube-acme:v0.0.1 卸载 CRD # 从集群中删除CRD:\nmake uninstall 卸载控制器 # 从集群中卸载控制器:\nmake undeploy « Kubeadm 升级 K8s\n» kubectl\n"},{"id":142,"href":"/kubernetes/kubectl/","title":"Kubectl","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / kubectl\nkubectl # 安装 # 参考文档: kubectl 安装文档\n常用命令 # 自动补全 # source \u0026lt;(kubectl completion bash) 可以将上面的命令写入 ~/.bashrc 或 /etc/bash.bashrc 中,这样每次登录都会自动补全。\n$ vim ~/.bashrc ... source \u0026lt;(kubectl completion bash) 命令别名 # alias k=kubectl complete -F __start_kubectl k Troubleshooting # Q1. _get_comp_words_by_ref: command not found # 解决方法:\napt install bash-completion -y source /usr/share/bash-completion/bash_completion source \u0026lt;(kubectl completion bash) « kubebuilder 实战\n» Kubernetes 0-1 Kubernetes最佳实践\n"},{"id":143,"href":"/kubernetes/kubernetes-best-practice/","title":"Kubernetes Best Practice","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 Kubernetes最佳实践\nKubernetes 0-1 Kubernetes最佳实践 # https://github.com/learnk8s/kubernetes-production-best-practices\n« kubectl\n» Kubernetes Dashboard\n"},{"id":144,"href":"/kubernetes/kubernetes-dashboard/","title":"Kubernetes Dashboard","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes Dashboard\nKubernetes Dashboard # Installation # Steps # 登入Kubernetes Master机器。\nCopy最新的recommended.yaml文件内容,写入本地kubernetes-dashboard.yaml文件。recommended.yaml文件地址: kubernetes dashboard github\n![image-20191223173827866](\nC:\\Users\\dp\\AppData\\Roaming\\Typora\\typora-user-images\\image-20191223173827866.png)\n# Copyright 2017 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the \u0026#34;License\u0026#34;); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an \u0026#34;AS IS\u0026#34; BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. apiVersion: v1 kind: Namespace metadata: name: kubernetes-dashboard --- apiVersion: v1 kind: ServiceAccount metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard --- kind: Service apiVersion: v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard spec: # 新增type: NodePort以及nodePort: 32100 #type: NodePort #ports: #- port: 443 #targetPort: 8443 #nodePort: 32100 #selector: #k8s-app: kubernetes-dashboard # 或者新增type: LoadBalancer type: LoadBalancer ports: - port: 443 targetPort: 8443 selector: k8s-app: kubernetes-dashboard --- # 注释掉这段 #apiVersion: v1 #kind: Secret #metadata: # labels: # k8s-app: kubernetes-dashboard # name: kubernetes-dashboard-certs # namespace: kubernetes-dashboard #type: Opaque --- apiVersion: v1 kind: Secret metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard-csrf namespace: kubernetes-dashboard type: Opaque data: csrf: \u0026#34;\u0026#34; --- apiVersion: v1 kind: Secret metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard-key-holder namespace: kubernetes-dashboard type: Opaque --- kind: ConfigMap apiVersion: v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard-settings namespace: kubernetes-dashboard --- kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard rules: # Allow Dashboard to get, update and delete Dashboard exclusive secrets. - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;secrets\u0026#34;] resourceNames: [\u0026#34;kubernetes-dashboard-key-holder\u0026#34;, \u0026#34;kubernetes-dashboard-certs\u0026#34;, \u0026#34;kubernetes-dashboard-csrf\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;update\u0026#34;, \u0026#34;delete\u0026#34;] # Allow Dashboard to get and update \u0026#39;kubernetes-dashboard-settings\u0026#39; config map. - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;configmaps\u0026#34;] resourceNames: [\u0026#34;kubernetes-dashboard-settings\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;update\u0026#34;] # Allow Dashboard to get metrics. - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;services\u0026#34;] resourceNames: [\u0026#34;heapster\u0026#34;, \u0026#34;dashboard-metrics-scraper\u0026#34;] verbs: [\u0026#34;proxy\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;services/proxy\u0026#34;] resourceNames: [\u0026#34;heapster\u0026#34;, \u0026#34;http:heapster:\u0026#34;, \u0026#34;https:heapster:\u0026#34;, \u0026#34;dashboard-metrics-scraper\u0026#34;, \u0026#34;http:dashboard-metrics-scraper\u0026#34;] verbs: [\u0026#34;get\u0026#34;] --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard rules: # Allow Metrics Scraper to get metrics from the Metrics server - apiGroups: [\u0026#34;metrics.k8s.io\u0026#34;] resources: [\u0026#34;pods\u0026#34;, \u0026#34;nodes\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;watch\u0026#34;] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: kubernetes-dashboard subjects: - kind: ServiceAccount name: kubernetes-dashboard namespace: kubernetes-dashboard --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: kubernetes-dashboard roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: kubernetes-dashboard subjects: - kind: ServiceAccount name: kubernetes-dashboard namespace: kubernetes-dashboard --- kind: Deployment apiVersion: apps/v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard spec: replicas: 1 revisionHistoryLimit: 10 selector: matchLabels: k8s-app: kubernetes-dashboard template: metadata: labels: k8s-app: kubernetes-dashboard spec: containers: - name: kubernetes-dashboard image: kubernetesui/dashboard:v2.0.0-beta8 imagePullPolicy: Always ports: - containerPort: 8443 protocol: TCP args: - --auto-generate-certificates - --namespace=kubernetes-dashboard # Uncomment the following line to manually specify Kubernetes API server Host # If not specified, Dashboard will attempt to auto discover the API server and connect # to it. Uncomment only if the default does not work. # - --apiserver-host=http://my-address:port volumeMounts: - name: kubernetes-dashboard-certs mountPath: /certs # Create on-disk volume to store exec logs - mountPath: /tmp name: tmp-volume livenessProbe: httpGet: scheme: HTTPS path: / port: 8443 initialDelaySeconds: 30 timeoutSeconds: 30 securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsUser: 1001 runAsGroup: 2001 volumes: - name: kubernetes-dashboard-certs secret: secretName: kubernetes-dashboard-certs - name: tmp-volume emptyDir: {} serviceAccountName: kubernetes-dashboard nodeSelector: \u0026#34;beta.kubernetes.io/os\u0026#34;: linux # Comment the following tolerations if Dashboard must not be deployed on master tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule --- kind: Service apiVersion: v1 metadata: labels: k8s-app: dashboard-metrics-scraper name: dashboard-metrics-scraper namespace: kubernetes-dashboard spec: ports: - port: 8000 targetPort: 8000 selector: k8s-app: dashboard-metrics-scraper --- kind: Deployment apiVersion: apps/v1 metadata: labels: k8s-app: dashboard-metrics-scraper name: dashboard-metrics-scraper namespace: kubernetes-dashboard spec: replicas: 1 revisionHistoryLimit: 10 selector: matchLabels: k8s-app: dashboard-metrics-scraper template: metadata: labels: k8s-app: dashboard-metrics-scraper annotations: seccomp.security.alpha.kubernetes.io/pod: \u0026#39;runtime/default\u0026#39; spec: containers: - name: dashboard-metrics-scraper image: kubernetesui/metrics-scraper:v1.0.1 ports: - containerPort: 8000 protocol: TCP livenessProbe: httpGet: scheme: HTTP path: / port: 8000 initialDelaySeconds: 30 timeoutSeconds: 30 volumeMounts: - mountPath: /tmp name: tmp-volume securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsUser: 1001 runAsGroup: 2001 serviceAccountName: kubernetes-dashboard nodeSelector: \u0026#34;beta.kubernetes.io/os\u0026#34;: linux # Comment the following tolerations if Dashboard must not be deployed on master tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule volumes: - name: tmp-volume emptyDir: {} 注意::\n需要注释掉一段,因为不注释掉的话,存在证书过期问题,chrome、safari主流浏览器将无法访问,好像firefox可以;\nkubernetes dashboard service的type定义为NodePort,并指定一个节点端口32100;\n生成自签证书请求的key: openssl genrsa -out dashboard.key 2048 生成自签证书请求: openssl req -new -out dashboard.csr -key dashboard.key -subj \u0026#39;/CN=\u0026lt;k8s master ip or domain name\u0026gt;\u0026#39; 生成自签证书 # 不设置-days,则默认365天过期 openssl x509 -days 3650 -req -in dashboard.csr -signkey dashboard.key -out dashboard.crt 部署kubernetes dashboard kubectl apply -f kubernetes-dashboard.yaml 由于kubernetes-dashboard.yaml我们注释掉了kubernetes-dashboard-certs,pod跑不起来,我们需要创建certs kubectl create secret generic kubernetes-dashboard-certs --from-file=dashboard.key --from-file=dashboard.crt -n kubernetes-dashboard 创建访问用户,用于访问kubernetes-dashboard,以下文本内容写入本地admin-user.yaml文件 apiVersion: v1 kind: ServiceAccount metadata: name: admin-user namespace: kubernetes-dashboard --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: admin-user roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: admin-user namespace: kubernetes-dashboard kubectl apply -f admin-user.yaml 获取登录密钥 kubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep admin-user | awk \u0026#39;{print $1}\u0026#39;) ​ 上面命令输出,拿到token的值\nIngress转发 apiVersion: extensions/v1beta1 kind: Ingress metadata: name: kubedash namespace: kubernetes-dashboard spec: rules: - host: kubedash.example.tech http: paths: - path: / backend: serviceName: kubernetes-dashboard servicePort: 32100 访问 https://xxxx:32100 ,注意这里一定要用https 选择Token访问,输入token即可。 References # https://www.cnblogs.com/life-of-coding/p/11794993.html https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/#deploying-the-dashboard-ui « Kubernetes 0-1 Kubernetes最佳实践\n» Kubernetes 中资源名称规范\n"},{"id":145,"href":"/kubernetes/kubernetes-naming-constraints/","title":"Kubernetes Naming Constraints","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 中资源名称规范\nKubernetes 中资源名称规范 # 在Kubernetes中,同一种资源(GVR)在同一个命名空间下名称是唯一。但是名称也需要遵循命名规则。本篇主要介绍 Kubernetes 中三种资源名称的命名规范。\nDNS1123Subdomain # 不能超过 253 个字符 只能包含小写字母、数字,以及 \u0026lsquo;-\u0026rsquo; 和 \u0026lsquo;.\u0026rsquo; 必须以字母数字开头 必须以字母数字结尾 以此规范约束的资源有:\nIngress Pod ConfigMap NetworkPolicy DNS1123Label # 最多 63 个字符 只能包含小写字母、数字,以及 \u0026lsquo;-\u0026rsquo; 必须以字母数字开头 必须以字母数字结尾 以此规范约束的资源有:\nNamespace Service DNS1035Label # 最多 63 个字符 只能包含小写字母、数字,以及 \u0026lsquo;-\u0026rsquo; 必须以字母开头 必须以字母数字结尾 以此规范约束的资源有:\nDeployment StatefulSet 建议 # 如果资源命名符合 DNS1035Label 规范,那么一定符合 Kubernetes 资源命名规范。假如在容器平台开发过程中,为了命名约束更加统一,建议使用 DNS1035Label 规范来约束资源命名。\n可以使用下面的代码(Go)来检查资源名称是否符合规范:\n引入包:\ngo get k8s.io/apimachinery/pkg/util/validation 示例代码:\npackage main import ( \u0026#34;k8s.io/apimachinery/pkg/util/validation\u0026#34; ) func main() { namespace: = \u0026#34;test-ns\u0026#34; if msgs := validation.IsDNS1123Subdomain(namespace); len(msgs) \u0026gt; 0 { fmt.Println(\u0026#34;namespace name is not valid\u0026#34;) } } « Kubernetes Dashboard\n» Kuberentes\n"},{"id":146,"href":"/kubernetes/kubernetes/","title":"Kubernetes","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kuberentes\nKuberentes # « Kubernetes 中资源名称规范\n» KubeVirt 创建 Windows 虚拟机\n"},{"id":147,"href":"/kubernetes/kubevirt-create-windows-vm/","title":"Kubevirt Create Windows Vm","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / KubeVirt 创建 Windows 虚拟机\nKubeVirt 创建 Windows 虚拟机 # virtctl image-upload --image-path windows-10.iso --pvc-name=windows-10-iso --size 10G --uploadproxy-url https://\u0026lt;cdi-uploadproxy.cdi.svc\u0026gt; --insecure --wait-secs 240 apiVersion: v1 kind: PersistentVolumeClaim metadata: name: windows-10-data spec: accessModes: - ReadWriteOnce resources: requests: storage: 40G storageClassName: longhorn volumeMode: Filesystem --- apiVersion: kubevirt.io/v1 kind: VirtualMachine metadata: name: windows-10 spec: running: true template: metadata: labels: kubevirt.io/domain: windows-10 spec: domain: cpu: cores: 4 devices: networkInterfaceMultiqueue: true #开启网卡多队列模式 blockMultiQueue: true #开启磁盘多队列模式 disks: - cdrom: bus: sata name: virtiocontainerdisk - cdrom: bus: sata name: cdromiso bootOrder: 1 - disk: bus: virtio name: harddrive bootOrder: 2 interfaces: - masquerade: {} model: virtio name: default resources: requests: memory: 8G networks: - name: default pod: {} volumes: - name: cdromiso persistentVolumeClaim: claimName: windows-10-iso - name: harddrive persistentVolumeClaim: claimName: windows-10-data - containerDisk: image: kubevirt/virtio-container-disk name: virtiocontainerdisk « Kuberentes\n» Kubevirt 实践\n"},{"id":148,"href":"/kubernetes/kubevirt-practice/","title":"Kubevirt Practice","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubevirt 实践\nKubevirt 实践 # 简介 # Kubevirt 是 Redhat 开源的以容器方式运行虚拟机的项目,以 k8s add-on 方式,利用 k8s CRD 为增加资源类型 VirtualMachineInstance(VMI), 使用容器的 image registry 去创建虚拟机并提供 VM 生命周期管理。 CRD 的方式使得 kubevirt 对虚拟机的管理不局限于 pod 管理接口,但是也无法使用 pod 的 RS DS Deployment 等管理能力,也意味着 kubevirt 如果想要利用 pod 管理能力,要自主去实现,目前 kubevirt 实现了类似 RS 的功能。 kubevirt 目前支持的 runtime 是 docker 和 runc。\n安装 # 部署 K8s 资源 # 最新版本 export KUBEVIRT_VERSION=$(curl -s https://api.github.com/repos/kubevirt/kubevirt/releases/latest | jq -r .tag_name) echo $KUBEVIRT_VERSION # 安装 CRD kubectl create -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-cr.yaml # 安装控制器 kubectl create -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-operator.yaml 安装 virtctl 工具 wget -O virtctl https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/virtctl-${KUBEVIRT_VERSION}-linux-amd64 chmod +x virtctl mv virtctl /usr/local/bin 检查资源状态 # 需要等待所有 Pod 处于 Running 状态 kubectl get pods -n kubevirt # 需要等待 kubevirt 处于 Deployed 状态 kubectl -n kubevirt get kubevirt 卸载 卸载首先需要删除 CRD,然后卸载控制器。\n# 0、获取当前 Kubevirt 版本 export KUBEVIRT_VERSION=$(kubectl get kubevirts.kubevirt.io -n kubevirt kubevirt -o=jsonpath=\u0026#39;{.status.observedKubeVirtVersion}\u0026#39;) # 1、删除 CRD kubectl delete -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-cr.yaml # 2、卸载控制器 kubectl delete -f kubectl create -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-operator.yaml 概念 # 虚拟机(VirtualMachine) 虚拟机实例(VirtualMachineInstance) 创建虚拟机 # 现在你可以像创建其他任何 K8s 资源一样,声明虚拟机资源然后创建了,如下:\napiVersion: kubevirt.io/v1 kind: VirtualMachine metadata: name: testvm spec: running: false template: metadata: labels: kubevirt.io/size: small kubevirt.io/domain: testvm spec: domain: devices: disks: - name: containerdisk disk: bus: virtio - name: cloudinitdisk disk: bus: virtio interfaces: - name: default masquerade: {} resources: requests: memory: 64M networks: - name: default pod: {} volumes: - name: containerdisk containerDisk: image: quay.io/kubevirt/cirros-container-disk-demo - name: cloudinitdisk cloudInitNoCloud: userDataBase64: SGkuXG4= 创建虚拟机:\nkubectl apply -f testvm.yaml 查看虚拟机状态,虚拟机应该处于未运行的状态:\n# kubectl get vms kubectl get vms -o yaml testvm | grep -E \u0026#39;running:.*|$\u0026#39; 运行虚拟机 # 使用 virtctl start 命令运行虚拟机:\nvirtual start testvm 注意:\n如果遇到 virtctl start testvm dial tcp 127.0.0.1:8080: connect: connection refused问题,通过 kubectl proxy --port 8080 在本地代理 K8s apiserver 服务。\n等待片刻,再次查看虚拟机状态,虚拟机应该处于未运行的状态:\nkubectl get vms -o yaml testvm | grep -E \u0026#39;running:.*|$\u0026#39; kubectl get vmis kubectl get vms 访问虚拟机 # 使用 virtual console 命令访问虚拟机:\nvirtual console testvm 注意:\n使用镜像 quay.io/kubevirt/cirros-container-disk-demo 创建的虚拟机,默认账号密码:cirros/gocubsgo; 使用 ctrl+] 退出。 关闭虚拟机 # 使用 virtual stop 命令访问虚拟机:\nvirtual stop testvm 删除虚拟机:\nkubectl delete vms testvm 磁盘访问 # lun # 将卷以 lun 的方式公开给虚拟机。\npvc:\napiVersion: v1 kind: PersistentVolumeClaim metadata: name: lun-pvc namespace: default spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: longhorn volumeMode: Filesystem vm:\napiVersion: kubevirt.io/v1 kind: VirtualMachine metadata: name: testvm spec: running: false template: metadata: labels: kubevirt.io/size: small kubevirt.io/domain: testvm spec: domain: devices: disks: - name: containerdisk disk: bus: virtio - name: cloudinitdisk disk: bus: virtio - name: lundisk lun: {} interfaces: - name: default masquerade: {} resources: requests: memory: 64M networks: - name: default pod: {} volumes: - name: containerdisk containerDisk: image: quay.io/kubevirt/cirros-container-disk-demo - name: cloudinitdisk cloudInitNoCloud: userDataBase64: SGkuXG4= - name: lundisk persistentVolumeClaim: claimName: lun-pvc disk # pvc:\napiVersion: v1 kind: PersistentVolumeClaim metadata: name: disk-pvc namespace: default spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: longhorn volumeMode: Filesystem vm:\napiVersion: kubevirt.io/v1 kind: VirtualMachine metadata: name: testvm spec: running: false template: metadata: labels: kubevirt.io/size: small kubevirt.io/domain: testvm spec: domain: devices: disks: - name: containerdisk disk: bus: virtio - name: cloudinitdisk disk: bus: virtio - name: disk-pvc disk: bus: virtio interfaces: - name: default masquerade: {} resources: requests: memory: 64M networks: - name: default pod: {} volumes: - name: containerdisk containerDisk: image: quay.io/kubevirt/cirros-container-disk-demo - name: cloudinitdisk cloudInitNoCloud: userDataBase64: SGkuXG4= - name: disk-pvc persistentVolumeClaim: claimName: disk-pvc cdrom # 将卷以 cdrom 驱动器的方式公开给虚拟机。默认情况下只读,可以通过 readonly: false 设置为可写。\npvc:\napiVersion: v1 kind: PersistentVolumeClaim metadata: name: cdrom-pvc namespace: default spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: longhorn volumeMode: Filesystem vm:\napiVersion: kubevirt.io/v1 kind: VirtualMachine metadata: name: testvm spec: running: false template: metadata: labels: kubevirt.io/size: small kubevirt.io/domain: testvm spec: domain: devices: disks: - name: containerdisk disk: bus: virtio - name: cloudinitdisk disk: bus: virtio - name: disk-pvc cdrom: readonly: false bus: sata interfaces: - name: default masquerade: {} resources: requests: memory: 64M networks: - name: default pod: {} volumes: - name: containerdisk containerDisk: image: quay.io/kubevirt/cirros-container-disk-demo - name: cloudinitdisk cloudInitNoCloud: userDataBase64: SGkuXG4= - name: cdrom-pvc persistentVolumeClaim: claimName: cdrom-pvc 排错 # 使用 virtctl 工具运行虚拟机时,遇到 virtctl start testvm dial tcp 127.0.0.1:8080: connect: connection refused 错误: 解决方法:本地代理 K8s apiserver 服务。\nkubectl proxy --port 8080 « KubeVirt 创建 Windows 虚拟机\n» Kustomize\n"},{"id":149,"href":"/kubernetes/kustomize/","title":"Kustomize","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kustomize\nKustomize # Kustomize 是一个通过 kustomization 文件 定制 Kubernetes 对象的工具。它提供以下功能特性来管理应用配置文件:\n从其他来源生成资源 为资源设置贯穿性(Cross-Cutting)字段 组织和定制资源集合 从 1.14 版本开始,kubectl 也开始支持使用 kustomization 文件来管理 Kubernetes 对象。 要查看包含 kustomization 文件的目录中的资源,执行下面的命令:\nkubectl kustomize \u0026lt;kustomization_directory\u0026gt; 要应用这些资源,使用 --kustomize 或 -k 参数来执行 kubectl apply:\nkubectl apply -k \u0026lt;kustomization_directory\u0026gt; 生成资源 # ConfigMap 和 Secret 包含其他 Kubernetes 对象(如 Pod)所需要的配置或敏感数据。 ConfigMap 或 Secret 中数据的来源往往是集群外部,例如某个 .properties 文件或者 SSH 密钥文件。 Kustomize 提供 secretGenerator 和 configMapGenerator,可以基于文件或字面值来生成 Secret 和 ConfigMap。\nconfigMapGenerator # 要基于文件来生成 ConfigMap,可以在 configMapGenerator 的 files 列表中添加表项。 下面是一个根据 .properties 文件中的数据条目来生成 ConfigMap 的示例:\n# 生成一个 application.properties 文件 cat \u0026lt;\u0026lt;EOF \u0026gt;application.properties FOO=Bar EOF cat \u0026lt;\u0026lt;EOF \u0026gt;./kustomization.yaml configMapGenerator: - name: example-configmap-1 files: - application.properties EOF 所生成的 ConfigMap 可以使用下面的命令来检查:\nkubectl kustomize ./ 所生成的 ConfigMap 为:\napiVersion: v1 data: application.properties: | FOO=Bar kind: ConfigMap metadata: name: example-configmap-1-8mbdf7882g 要从 env 文件生成 ConfigMap,请在 configMapGenerator 中的 envs 列表中添加一个条目。 下面是一个用来自 .env 文件的数据生成 ConfigMap 的例子:\n# 创建一个 .env 文件 cat \u0026lt;\u0026lt;EOF \u0026gt;.env FOO=Bar EOF cat \u0026lt;\u0026lt;EOF \u0026gt;./kustomization.yaml configMapGenerator: - name: example-configmap-1 envs: - .env EOF 可以使用以下命令检查生成的 ConfigMap:\nkubectl kustomize ./ 生成的 ConfigMap 为:\napiVersion: v1 data: FOO: Bar kind: ConfigMap metadata: name: example-configmap-1-42cfbf598f 说明::\n.env 文件中的每个变量在生成的 ConfigMap 中成为一个单独的键。这与之前的示例不同, 前一个示例将一个名为 application.properties 的文件(及其所有条目)嵌入到同一个键的值中。\nConfigMap 也可基于字面的键值偶对来生成。要基于键值偶对来生成 ConfigMap, 在 configMapGenerator 的 literals 列表中添加表项。下面是一个例子, 展示如何使用键值偶对中的数据条目来生成 ConfigMap 对象:\ncat \u0026lt;\u0026lt;EOF \u0026gt;./kustomization.yaml configMapGenerator: - name: example-configmap-2 literals: - FOO=Bar EOF 可以用下面的命令检查所生成的 ConfigMap:\nkubectl kustomize ./ 所生成的 ConfigMap 为:\napiVersion: v1 data: FOO: Bar kind: ConfigMap metadata: name: example-configmap-2-g2hdhfc6tk 要在 Deployment 中使用生成的 ConfigMap,使用 configMapGenerator 的名称对其进行引用。 Kustomize 将自动使用生成的名称替换该名称。\n这是使用生成的 ConfigMap 的 deployment 示例:\n# 创建一个 application.properties 文件 cat \u0026lt;\u0026lt;EOF \u0026gt;application.properties FOO=Bar EOF cat \u0026lt;\u0026lt;EOF \u0026gt;deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-app labels: app: my-app spec: selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: app image: my-app volumeMounts: - name: config mountPath: /config volumes: - name: config configMap: name: example-configmap-1 EOF cat \u0026lt;\u0026lt;EOF \u0026gt;./kustomization.yaml resources: - deployment.yaml configMapGenerator: - name: example-configmap-1 files: - application.properties EOF 生成 ConfigMap 和 Deployment:\nkubectl kustomize ./ 生成的 Deployment 将通过名称引用生成的 ConfigMap:\napiVersion: v1 data: application.properties: | FOO=Bar kind: ConfigMap metadata: name: example-configmap-1-g4hk9g2ff8 --- apiVersion: apps/v1 kind: Deployment metadata: labels: app: my-app name: my-app spec: selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - image: my-app name: app volumeMounts: - mountPath: /config name: config volumes: - configMap: name: example-configmap-1-g4hk9g2ff8 name: config secretGenerator # 你可以基于文件或者键值偶对来生成 Secret。要使用文件内容来生成 Secret, 在 secretGenerator 下面的 files 列表中添加表项。使用和 configMapGenerator 基本一致。\n设置贯穿性字段 # 在项目中为所有 Kubernetes 对象设置贯穿性字段是一种常见操作。 贯穿性字段的一些使用场景如下:\n为所有资源设置相同的名字空间 为所有对象添加相同的前缀或后缀 为对象添加相同的标签集合 为对象添加相同的注解集合 下面是一个例子:\n# 创建一个 deployment.yaml cat \u0026lt;\u0026lt;EOF \u0026gt;./deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx EOF cat \u0026lt;\u0026lt;EOF \u0026gt;./kustomization.yaml namespace: my-namespace namePrefix: dev- nameSuffix: \u0026#34;-001\u0026#34; commonLabels: app: bingo commonAnnotations: oncallPager: 800-555-1212 resources: - deployment.yaml EOF 执行 kubectl kustomize ./ 查看这些字段都被设置到 Deployment 资源上:\napiVersion: apps/v1 kind: Deployment metadata: annotations: oncallPager: 800-555-1212 labels: app: bingo name: dev-nginx-deployment-001 namespace: my-namespace spec: selector: matchLabels: app: bingo template: metadata: annotations: oncallPager: 800-555-1212 labels: app: bingo spec: containers: - image: nginx name: nginx 组织和定制资源 # 一种常见的做法是在项目中构造资源集合并将其放到同一个文件或目录中管理。 Kustomize 提供基于不同文件来组织资源并向其应用补丁或者其他定制的能力。\n组织 # Kustomize 支持组合不同的资源。kustomization.yaml 文件的 resources 字段定义配置中要包含的资源列表。 你可以将 resources 列表中的路径设置为资源配置文件的路径。 下面是由 Deployment 和 Service 构成的 NGINX 应用的示例:\n# 创建 deployment.yaml 文件 cat \u0026lt;\u0026lt;EOF \u0026gt; deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: selector: matchLabels: run: my-nginx replicas: 2 template: metadata: labels: run: my-nginx spec: containers: - name: my-nginx image: nginx ports: - containerPort: 80 EOF # 创建 service.yaml 文件 cat \u0026lt;\u0026lt;EOF \u0026gt; service.yaml apiVersion: v1 kind: Service metadata: name: my-nginx labels: run: my-nginx spec: ports: - port: 80 protocol: TCP selector: run: my-nginx EOF # 创建 kustomization.yaml 来组织以上两个资源 cat \u0026lt;\u0026lt;EOF \u0026gt;./kustomization.yaml resources: - deployment.yaml - service.yaml EOF kubectl kustomize ./ 所得到的资源中既包含 Deployment 也包含 Service 对象。\n定制 # 补丁文件(Patches)可以用来对资源执行不同的定制。 Kustomize 通过 patchesStrategicMerge 和 patchesJson6902 支持不同的打补丁机制。 patchesStrategicMerge 的内容是一个文件路径的列表,其中每个文件都应可解析为 策略性合并补丁(Strategic Merge Patch)。 补丁文件中的名称必须与已经加载的资源的名称匹配。 建议构造规模较小的、仅做一件事情的补丁。 例如,构造一个补丁来增加 Deployment 的副本个数;构造另外一个补丁来设置内存限制。\n# 创建 deployment.yaml 文件 cat \u0026lt;\u0026lt;EOF \u0026gt; deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: selector: matchLabels: run: my-nginx replicas: 2 template: metadata: labels: run: my-nginx spec: containers: - name: my-nginx image: nginx ports: - containerPort: 80 EOF # 生成一个补丁 increase_replicas.yaml cat \u0026lt;\u0026lt;EOF \u0026gt; increase_replicas.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: replicas: 3 EOF # 生成另一个补丁 set_memory.yaml cat \u0026lt;\u0026lt;EOF \u0026gt; set_memory.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: template: spec: containers: - name: my-nginx resources: limits: memory: 512Mi EOF cat \u0026lt;\u0026lt;EOF \u0026gt;./kustomization.yaml resources: - deployment.yaml patchesStrategicMerge: - increase_replicas.yaml - set_memory.yaml EOF 执行 kubectl kustomize ./ 来查看 Deployment:\napiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: replicas: 3 selector: matchLabels: run: my-nginx template: metadata: labels: run: my-nginx spec: containers: - image: nginx name: my-nginx ports: - containerPort: 80 resources: limits: memory: 512Mi 并非所有资源或者字段都支持策略性合并补丁。为了支持对任何资源的任何字段进行修改, Kustomize 提供通过 patchesJson6902 来应用 JSON 补丁的能力。 为了给 JSON 补丁找到正确的资源,需要在 kustomization.yaml 文件中指定资源的组(group)、 版本(version)、类别(kind)和名称(name)。 例如,为某 Deployment 对象增加副本个数的操作也可以通过 patchesJson6902 来完成:\n# 创建一个 deployment.yaml 文件 cat \u0026lt;\u0026lt;EOF \u0026gt; deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: selector: matchLabels: run: my-nginx replicas: 2 template: metadata: labels: run: my-nginx spec: containers: - name: my-nginx image: nginx ports: - containerPort: 80 EOF # 创建一个 JSON 补丁文件 cat \u0026lt;\u0026lt;EOF \u0026gt; patch.yaml - op: replace path: /spec/replicas value: 3 EOF # 创建一个 kustomization.yaml cat \u0026lt;\u0026lt;EOF \u0026gt;./kustomization.yaml resources: - deployment.yaml patchesJson6902: - target: group: apps version: v1 kind: Deployment name: my-nginx path: patch.yaml EOF 执行 kubectl kustomize ./ 以查看 replicas 字段被更新:\napiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: replicas: 3 selector: matchLabels: run: my-nginx template: metadata: labels: run: my-nginx spec: containers: - image: nginx name: my-nginx ports: - containerPort: 80 除了补丁之外,Kustomize 还提供定制容器镜像或者将其他对象的字段值注入到容器中的能力,并且不需要创建补丁。 例如,你可以通过在 kustomization.yaml 文件的 images 字段设置新的镜像来更改容器中使用的镜像。\ncat \u0026lt;\u0026lt;EOF \u0026gt; deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: selector: matchLabels: run: my-nginx replicas: 2 template: metadata: labels: run: my-nginx spec: containers: - name: my-nginx image: nginx ports: - containerPort: 80 EOF cat \u0026lt;\u0026lt;EOF \u0026gt;./kustomization.yaml resources: - deployment.yaml images: - name: nginx newName: my.image.registry/nginx newTag: 1.4.0 EOF 执行 kubectl kustomize ./ 以查看所使用的镜像已被更新:\napiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: replicas: 2 selector: matchLabels: run: my-nginx template: metadata: labels: run: my-nginx spec: containers: - image: my.image.registry/nginx:1.4.0 name: my-nginx ports: - containerPort: 80 有些时候,Pod 中运行的应用可能需要使用来自其他对象的配置值。 例如,某 Deployment 对象的 Pod 需要从环境变量或命令行参数中读取读取 Service 的名称。 由于在 kustomization.yaml 文件中添加 namePrefix 或 nameSuffix 时 Service 名称可能发生变化,建议不要在命令参数中硬编码 Service 名称。 对于这种使用场景,Kustomize 可以通过 vars 将 Service 名称注入到容器中。\n# 创建一个 deployment.yaml 文件(引用此处的文档分隔符) cat \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; \u0026gt; deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: selector: matchLabels: run: my-nginx replicas: 2 template: metadata: labels: run: my-nginx spec: containers: - name: my-nginx image: nginx command: [\u0026#34;start\u0026#34;, \u0026#34;--host\u0026#34;, \u0026#34;$(MY_SERVICE_NAME)\u0026#34;] EOF # 创建一个 service.yaml 文件 cat \u0026lt;\u0026lt;EOF \u0026gt; service.yaml apiVersion: v1 kind: Service metadata: name: my-nginx labels: run: my-nginx spec: ports: - port: 80 protocol: TCP selector: run: my-nginx EOF cat \u0026lt;\u0026lt;EOF \u0026gt;./kustomization.yaml namePrefix: dev- nameSuffix: \u0026#34;-001\u0026#34; resources: - deployment.yaml - service.yaml vars: - name: MY_SERVICE_NAME objref: kind: Service name: my-nginx apiVersion: v1 EOF 执行 kubectl kustomize ./ 以查看注入到容器中的 Service 名称是 dev-my-nginx-001:\napiVersion: apps/v1 kind: Deployment metadata: name: dev-my-nginx-001 spec: replicas: 2 selector: matchLabels: run: my-nginx template: metadata: labels: run: my-nginx spec: containers: - command: - start - --host - dev-my-nginx-001 image: nginx name: my-nginx 基准(Bases)与覆盖(Overlays) # Kustomize 中有 基准(bases) 和 覆盖(overlays) 的概念区分。 基准 是包含 kustomization.yaml 文件的一个目录,其中包含一组资源及其相关的定制。 基准可以是本地目录或者来自远程仓库的目录,只要其中存在 kustomization.yaml 文件即可。 覆盖 也是一个目录,其中包含将其他 kustomization 目录当做 bases 来引用的 kustomization.yaml 文件。 基准不了解覆盖的存在,且可被多个覆盖所使用。 覆盖则可以有多个基准,且可针对所有基准中的资源执行组织操作,还可以在其上执行定制。\n# 创建一个包含基准的目录 mkdir base # 创建 base/deployment.yaml cat \u0026lt;\u0026lt;EOF \u0026gt; base/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: selector: matchLabels: run: my-nginx replicas: 2 template: metadata: labels: run: my-nginx spec: containers: - name: my-nginx image: nginx EOF # 创建 base/service.yaml 文件 cat \u0026lt;\u0026lt;EOF \u0026gt; base/service.yaml apiVersion: v1 kind: Service metadata: name: my-nginx labels: run: my-nginx spec: ports: - port: 80 protocol: TCP selector: run: my-nginx EOF # 创建 base/kustomization.yaml cat \u0026lt;\u0026lt;EOF \u0026gt; base/kustomization.yaml resources: - deployment.yaml - service.yaml EOF 此基准可在多个覆盖中使用。你可以在不同的覆盖中添加不同的 namePrefix 或其他贯穿性字段。 下面是两个使用同一基准的覆盖:\nmkdir dev cat \u0026lt;\u0026lt;EOF \u0026gt; dev/kustomization.yaml resources: - ../base namePrefix: dev- EOF mkdir prod cat \u0026lt;\u0026lt;EOF \u0026gt; prod/kustomization.yaml resources: - ../base namePrefix: prod- EOF 如何使用 Kustomize 来应用、查看和删除对象 # 在 kubectl 命令中使用 --kustomize 或 -k 参数来识别被 kustomization.yaml 所管理的资源。 注意 -k 要指向一个 kustomization 目录。例如:\nkubectl apply -k \u0026lt;kustomization 目录\u0026gt;/ 假定使用下面的 kustomization.yaml:\n# 创建 deployment.yaml 文件 cat \u0026lt;\u0026lt;EOF \u0026gt; deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: selector: matchLabels: run: my-nginx replicas: 2 template: metadata: labels: run: my-nginx spec: containers: - name: my-nginx image: nginx ports: - containerPort: 80 EOF # 创建 kustomization.yaml cat \u0026lt;\u0026lt;EOF \u0026gt;./kustomization.yaml namePrefix: dev- commonLabels: app: my-nginx resources: - deployment.yaml EOF 执行下面的命令来应用 Deployment 对象 dev-my-nginx:\n\u0026gt; kubectl apply -k ./ deployment.apps/dev-my-nginx created 运行下面的命令之一来查看 Deployment 对象 dev-my-nginx:\nkubectl get -k ./ kubectl describe -k ./ 执行下面的命令来比较 Deployment 对象 dev-my-nginx 与清单被应用之后集群将处于的状态:\nkubectl diff -k ./ 执行下面的命令删除 Deployment 对象 dev-my-nginx:\n\u0026gt; kubectl delete -k ./ deployment.apps \u0026#34;dev-my-nginx\u0026#34; deleted Kustomize 功能特性列表 # 字段 类型 解释 namespace string 为所有资源添加名字空间 namePrefix string 此字段的值将被添加到所有资源名称前面 nameSuffix string 此字段的值将被添加到所有资源名称后面 commonLabels map[string]string 要添加到所有资源和选择算符的标签 commonAnnotations map[string]string 要添加到所有资源的注解 resources []string 列表中的每个条目都必须能够解析为现有的资源配置文件 configMapGenerator [] ConfigMapArgs 列表中的每个条目都会生成一个 ConfigMap secretGenerator []SecretArgs[] SecretArgs 列表中的每个条目都会生成一个 Secret generatorOptions GeneratorOptions 更改所有 ConfigMap 和 Secret 生成器的行为 bases []string 列表中每个条目都应能解析为一个包含 kustomization.yaml 文件的目录 patchesStrategicMerge []string 列表中每个条目都能解析为某 Kubernetes 对象的策略性合并补丁 patchesJson6902 []Patch[] Patch 列表中每个条目都能解析为一个 Kubernetes 对象和一个 JSON 补丁 vars [] Var 每个条目用来从某资源的字段来析取文字 images []Image[] images 每个条目都用来更改镜像的名称、标记与/或摘要,不必生成补丁 configurations []string 列表中每个条目都应能解析为一个包含 Kustomize 转换器配置 的文件 crds []string 列表中每个条目都应能够解析为 Kubernetes 类别的 OpenAPI 定义文件 « Kubevirt 实践\n» Kubernetes 0-1 Pod中的livenessProbe和readinessProbe解读\n"},{"id":150,"href":"/kubernetes/liveness-readiness-probe/","title":"Liveness Readiness Probe","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 Pod中的livenessProbe和readinessProbe解读\nKubernetes 0-1 Pod中的livenessProbe和readinessProbe解读 # 写在前面 # K8s对Pod的健康状态可以通过两类探针来检查:livenessProbe和readinessProbe,kubelet通过定期执行这两类探针来诊断容器的健康状况。\nlivenessProbe简介 # 存活指针,判断Pod(中的应用容器)是否健康,可以理解为健康检查。我们使用livenessProbe来定期的去探测,如果探测成功,则Pod状态可以判定为Running;如果探测失败,可kubectl会根据Pod的重启策略来重启容器。\n如果未给Pod设置livenessProbe,则默认探针永远返回Success。\n当我们执行kubectl get pods命令,输出信息中STATUS一列我们可以看到Pod是否处于Running状态。\nreadinessProbe简介 # 就绪指针,就绪的意思是已经准备好了,Pod的就绪我们可以理解为这个Pod可以接受请求和访问。我们使用readinessProbe来定期的去探测,如果探测成功,则Pod 的Ready状态判定为True;如果探测失败,Pod的Ready状态判定为False。\n与livenessProbe不同的是,kubelet不会对readinessProbe的探测情况有重启操作。\n当我们执行kubectl get pods命令,输出信息中READY一列我们可以看到Pod的READY状态是否为True。\n定义参数 # livenessProbe和readinessProbe的定义参数是一致的,可以通过kubectl explain pods.spec.containers.readinessProbe或kubectl explain pods.spec.containers.livenessProbe命令了解:\n就绪探针的几种类型:\nhttpGet:\n向容器发送Http Get请求,调用成功(通过Http状态码判断)则确定Pod就绪;\n使用方式:\nlivenessProbe: httpGet: path: /app/healthz port: 80 exec:\n在容器内执行某命令,命令执行成功(通过命令退出状态码为0判断)则确定Pod就绪;\n使用方式:\nlivenessProbe: exec: command: - cat - /app/healthz tcpSocket:\n打开一个TCP连接到容器的指定端口,连接成功建立则确定Pod就绪。\n使用方式:\nlivenessProbe: tcpSocket: port: 80 一般就绪探针会在启动容器一段时间后才开始第一次的就绪探测,之后做周期性探测。所以在定义就绪指针时,会给以下几个参数:\ninitialDelaySeconds:在初始化容器多少秒后开始第一次就绪探测; timeoutSeconds:如果该次就绪探测超过多少秒后还未成功,判定为超时,该次探测失败,Pod不就绪。默认值1,最小值1; periodSeconds:如果Pod未就绪,则每隔多少秒周期性的做就绪探测。默认值10,最小值1; failureThreshold:如果容器之前探测成功,后续连续几次探测失败,则确定容器未就绪。默认值3,最小值1; successThreshold:如果容器之前探测失败,后续连续几次探测成功,则确定容器就绪。默认值1,最小值1。 使用示例 # 目前我在docker hub有一个测试镜像:poneding/helloweb:v1,容器启动后,有一个健康检查路由/healthz/return200,访问该路由状态码返回200;有一个检查路由/health/return404,访问该路由状态码返回404。\nreadinessProbe示例 # 在实验之前先了解一下Pod和Service的负载均衡关系:在K8s中,Service作为Pod的负载均衡器,是通过Label Selector匹配Pod的。但是这句话没有说完整,因为还有一个必要条件:Pod当前已经就绪。也就是说,Service通过Label Selector匹配当前就绪的Pod,还未就绪的Pod就算labelSelector匹配上了,也不会出现在Service的endpoints中,请求是不会被转发过去的,如下图示例。\n示例说明:我们使用poneding/helloweb:v1镜像启动三个Pod,三个Pod的Label都设置成一样,为了使Service匹配到;三个Pod其中两个readinessProbe使用httpGet探测/health/return200,模拟探测成功,一个readinessProbe使用httpGet探测/health/return404,模拟探测失败。\n编写我们的helloweb-readinessProbe.yaml文件如下:\napiVersion: v1 kind: Pod metadata: name: helloweb1 labels: app: helloweb spec: containers: - name: helloweb image: poneding/helloweb:v1 readinessProbe: httpGet: path: /healthz/return200 port: 80 initialDelaySeconds: 30 timeoutSeconds: 10 ports: - containerPort: 80 --- apiVersion: v1 kind: Pod metadata: name: helloweb2 labels: app: helloweb spec: containers: - name: helloweb image: poneding/helloweb:v1 readinessProbe: httpGet: path: /healthz/return200 port: 80 initialDelaySeconds: 30 timeoutSeconds: 10 ports: - containerPort: 80 --- apiVersion: v1 kind: Pod metadata: name: helloweb3 labels: app: helloweb spec: containers: - name: helloweb image: poneding/helloweb:v1 readinessProbe: httpGet: path: /healthz/return404 port: 80 initialDelaySeconds: 30 timeoutSeconds: 10 ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: helloweb spec: selector: app: helloweb type: ClusterIP ports: - name: http port: 80 targetPort: 80 运行命令部署Pod和Service:\nkubectl apply -f helloweb-readinessProbe.yaml 之后,我们查看Pod的就绪情况:\n可以看到Pod只有helloweb1和helloweb2当前使处于READY(就绪)的状态,helloweb3尚未Ready,我们接着查看helloweb service的endpoints:\n可以看到Service的EndPoints只将helloweb1、helloweb2 pod的IP负载上了。\n查看日志访问情况:\n可以看到每隔10秒(readniessProbe.periodSeconds默认10s)就会做一次就绪探测。\nlivenessProbe示例 # 编写我们的helloweb-livenessProbe.yaml文件如下:\napiVersion: v1 kind: Pod metadata: name: helloweb4 labels: app: helloweb spec: containers: - name: helloweb image: poneding/helloweb:v1 livenessProbe: httpGet: path: /healthz/return200 port: 80 initialDelaySeconds: 30 timeoutSeconds: 10 ports: - containerPort: 80 --- apiVersion: v1 kind: Pod metadata: name: helloweb5 labels: app: helloweb spec: containers: - name: helloweb image: poneding/helloweb:v1 livenessProbe: httpGet: path: /healthz/return404 port: 80 initialDelaySeconds: 30 timeoutSeconds: 10 ports: - containerPort: 80 运行命令部署Pod和Service:\nkubectl apply -f helloweb-livenessProbe.yaml 之后,我们查看Pod的就绪情况:\n可以看到helloweb4的STATUS状态为Running,而helloweb5的STATUS状态最终变为CrashLoopBackOff,并且一直在重启。\n相信到这里,你已经对readniessProbe和livenessProbe有一个清晰的了解了。\n« Kustomize\n» local 存储卷实践\n"},{"id":151,"href":"/kubernetes/local-storageclass/","title":"Local Storageclass","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / local 存储卷实践\nlocal 存储卷实践 # 在 Kubernetes 中有一种存储卷类型为 local。\nlocal 卷所代表的是某个被挂载的本地存储设备,例如磁盘、分区或者目录。\nlocal 卷只能用作静态创建的持久卷。不支持动态配置。\n与 hostPath 卷相比,local 卷能够以持久和可移植的方式使用,而无需手动将 Pod 调度到节点。系统通过查看 PersistentVolume 的节点亲和性配置,就能了解卷的节点约束。\n然而,local 卷仍然取决于底层节点的可用性,并不适合所有应用程序。 如果节点变得不健康,那么 local 卷也将变得不可被 Pod 访问。使用它的 Pod 将不能运行。 使用 local 卷的应用程序必须能够容忍这种可用性的降低,以及因底层磁盘的耐用性特征而带来的潜在的数据丢失风险。\n创建 local-storage 存储类 # apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: local-storage provisioner: kubernetes.io/no-provisioner volumeBindingMode: WaitForFirstConsumer 手动创建 PV/PVC # 1、使用 local 卷时,你需要设置 PersistentVolume 对象的 nodeAffinity 字段。 Kubernetes 调度器使用 PersistentVolume 的 nodeAffinity 信息来将使用 local 卷的 Pod 调度到正确的节点;\n2、PersistentVolume 对象的 volumeMode 字段可被设置为 \u0026ldquo;Block\u0026rdquo; (而不是默认值 \u0026ldquo;Filesystem\u0026rdquo;),以将 local 卷作为原始块设备暴露出来。\napiVersion: v1 kind: PersistentVolumeClaim metadata: name: mysql-local-pvc namespace: default spec: accessModes: - ReadWriteOnce resources: requests: storage: 128Mi storageClassName: local-storage volumeMode: Filesystem volumeName: mysql-local-pv --- apiVersion: v1 kind: PersistentVolume metadata: name: mysql-local-pv spec: capacity: storage: 128Mi volumeMode: Filesystem accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Delete storageClassName: local-storage local: path: /mysql-data nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - worker-01 ⚠ 注意:\n1、指定亲和节点名称,示例中为 worker-01;\n2、指定的节点上需要确保本地目录是存在的,示例中为 /mysql-data。\n创建 Pod # apiVersion: apps/v1 kind: StatefulSet metadata: name: mysql labels: app: mysql spec: selector: matchLabels: app: mysql serviceName: mysql replicas: 1 template: metadata: labels: app: mysql spec: containers: - name: mysql image: mysql:8.0 env: - name: MYSQL_ROOT_PASSWORD value: \u0026#34;123456\u0026#34; volumeMounts: - name: data mountPath: /var/lib/mysql restartPolicy: Always volumes: - name: data persistentVolumeClaim: claimName: mysql-local-pvc 参考 # https://kubernetes.io/zh-cn/docs/concepts/storage/volumes/#local lovo 项目 # 基于 local 存储卷实现一个控制器动态管理 PV。\n« Kubernetes 0-1 Pod中的livenessProbe和readinessProbe解读\n» Kubernetes 0-1 K8s自建LoadBalancer\n"},{"id":152,"href":"/kubernetes/metallb/","title":"Metallb","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 K8s自建LoadBalancer\nKubernetes 0-1 K8s自建LoadBalancer # Metallb介绍 # 一般只有云平台支持LoadBalancer,如果脱离云平台,自己搭建的K8s集群,Service的类型使用LoadBalancer是没有任何效果的。为了让私有网络中的K8s集群也能体验到LoadBalabcer,Metallb成为了解决方案。\nMetallb运行在K8s集群中,监视集群内LoadBalancer类型的服务,然后从配置的IP池中为其分配一个可用IP,以ARP/NDP或BGP的方式将其广播出去,这个可用IP成为了LoadBalancer的Url,可供集群外访问。\nMetallb搭建过程 # 创建命名空间 metallb-system:\nvim metallb-namespace.yaml 写入文件内容:\napiVersion: v1 kind: Namespace metadata: name: metallb-system 下载metallb.yaml文件\nwget https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/metallb.yaml -O metallb.yaml --no-check-certificate 定义LoadBalancer的IP池,先创建configmap\nvim metallb-configMap.yaml 写入文件内容:\napiVersion: v1 kind: ConfigMap metadata: namespace: metallb-system name: config data: config: | address-pools: - name: default protocol: layer2 addresses: - 192.168.115.140-192.168.115.199 注意:IP池的网络需要和K8s集群的IP处于同一网段,我的K8s集群网络是192.168.115.13x,这里IP池则是给到192.168.115.140-192.168.115.199的范围。\n执行命令:\nkubectl apply -f metallb-namespace.yaml kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey=\u0026#34;$(openssl rand -base64 128)\u0026#34; kubectl apply -f metallb.yaml kubectl apply -f metallb-configMap.yaml LoadBalancer测试 # 我们使用类型为LoadBalancer的Service进行测试,以nginx服务为例。\nkubectl create deployment nginx --image=nginx kubectl expose deployment nginx --port=80 --type=LoadBalancer 查看nginx服务:\n[root@k8s-master01 test]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.0.0.1 \u0026lt;none\u0026gt; 443/TCP 11h nginx LoadBalancer 10.0.0.82 192.168.115.140 80:31610/TCP 8s 可以看到,已经为nginx服务分配了一个192.168.115.140 的IP,直接在浏览器中访问,一切正常。\n« local 存储卷实践\n» 使用 nfs 持久化存储\n"},{"id":153,"href":"/kubernetes/nfs-as-pvc/","title":"Nfs as Pvc","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 使用 nfs 持久化存储\n使用 nfs 持久化存储 # 一般云平台都会提供云存储服务,如 AWS EBS 服务,K8s 可以直接使用云存储服务创建 PV 和 PVC 作为 Volume 的存储后端。假设你没有使用到云存储,那么 NFS 可能会适合你。\nNFS(Network File System),网络文件系统,允许计算机之间共享存储资源,这里也就不具体介绍了。\n部署 nfs # 以下命令需要root权限,示例中机器IP为192.168.115.137。\n安装 nfs: # Ubuntu \u0026amp; Debian apt install nfs-kernel-server -y # CentOS yum install nfs-util -y 创建共享目录: mkdir /nfs/data -p 修改 nfs 的默认配置,在文末添加配置: vim /etc/exports /nfs/data *(rw,sync,no_root_squash) 其中:\n/nfs/data:共享目录 *:对所有开放访问,可以配置成网段,IP,域名等 rw:读写权限 sync:文件同时写入磁盘和内存 no_root_squash:当登录 NFS 主机使用共享目录的使用者是 root 时,其权限将被转换成为匿名使用者,通常它的 UID 与 GID,都会变成 nobody 身份 重启 rpc,nfs 需要向 rpc 注册: systemctl restart rpcbind.service systemctl enable rpcbind.service 重启 nfs 服务: systemctl restart nfs-kernel-server.service systemctl enable nfs-kernel-server.service 挂载共享,在 fstab 文件中添加配置: vim /etc/fstab 192.168.115.137:/nfs/data /nfs/data nfs rw,tcp,soft 0 0 创建 PV # PersistentVolume 作为 K8s 的存储资源,我们需要为它定义 apacity(存储能力),accessModes(访问模式)persistentVolumeReclaimPolicy(回收策略),存储媒介等信息。\n下面定义了一个 pv 资源,文件名为 pv1.yaml\napiVersion: v1 kind: PersistentVolume metadata: name: pv1 spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Recycle nfs: path: /nfs/data server: 192.168.115.137 需要对 AccessModes 和 PersistentVolumeReclaimPolicy 做简单的枚举介绍:\nAccessModes 访问模式 # 设置对 PV 存储资源的访问权限:\nReadWriteOnce(RWO):读写权限,只能被单个实例挂载\nReadOnlyMany(ROX):只读权限,可以被多个实例挂载\nReadWriteMany(RWX):读写权限,可以被多个实例挂载\nPersistentVolumeReclaimPolicy 回收策略 # 当挂载实例被删除时,设置对 PV 存储资源的回收策略:\nRetain:保留 Recycle:清除 Delete:删除,一般用于云存储服务删除对应的资源,如 AWS 的 EBS 现在创建 pv:\nkubectl apply -f pv1.yaml 查看 pv,可以看到当前 pv1 的 STATUS 为 Available:\nkubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv1 1Gi RWO Recycle Available 48s 这里也需要对 PV 的状态作下简单介绍:\nStatus # PV 的生命周期包含了四个阶段:\nAvaliable:当前未绑定 PVC,处于可用状态\nBound:已经被 PVC 绑定,不可用\nReleased:之前被 PVC 绑定,PVC 删除时,PV 会被重新声明,很短暂的一段时间内处于 Released 状态\nFailed:PV 自动回收失败\n创建 PVC # 下面定义了一个 pvc 资源,文件名为 pvc1.yaml\napiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc1 spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi 现在创建 pvc\nkubectl apply -f pvc1.yaml 查看 PVC,可以看到 pvc1 已经处于 Bound 的状态,说明它已经绑定上了一个 PV,而我们目前只创建了一个名为 pv1 的 PV\nNAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE pvc1 Bound pv1 1Gi RWO 3s 再次查看 PV,看看是不是被 pvc1 绑定了。\nNAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv1 1Gi RWO Recycle Bound default/pvc1 2m 确实如我们所想,pv1 被 pvc1 绑定,并且 pv1 的状态也更新成了 Bound。其实这是集群自动为我们的 PVC 寻找到的符合条件的 PV:\n当前状态处于 Avaliable 的 PV PV 容量 \u0026gt;= PVC 申请容量 pvc 和 pv 的 AccessMode 需要一致 当然,我们也可以通过标签选择器为 PVC 指定绑定的 PV。\n定义 PV:\n... kind: PersistentVolume metadata: labels: app: pv1 .... 定义 PVC:\n... kind: PersistentVolumeClaim spec: selector: matchLabels: app: pv1 ... Volume 使用 PVC # ... volumes: - name: www persistentVolumeClaim: claimName: pvc1 ... « Kubernetes 0-1 K8s自建LoadBalancer\n» Kubernetes 0-1 了解 Pod\n"},{"id":154,"href":"/kubernetes/pod-understood/","title":"Pod Understood","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 了解 Pod\nKubernetes 0-1 了解 Pod # Pod 介绍 # Pod,是 K8s 对象模型中的最小单元,Pod 里面包含着一组容器(单个容器或多个紧密耦合的容器),这时候 Pod 可以理解为一个机器,而 Pod 里面的容器则理解为该机器里面的进程。\nPod 的容器运行时由容器引擎提供,默认的容器引擎是 Docker;并且 K8s 管理的是 Pod,而不是容器。\n一个 Pod 内部的容器共享:\n存储:一个 Pod 可以指定一组共享存储卷。 网络:每个 Pod 分配一个唯一 IP(集群内 IP),共享网络命名空间,包括 IP 地址和网络端口。Pod 内的容器可以使用 localhost 互相通信,集群内 Pod 与 Pod通信可以使用 Pod 分配的 IP,但是由于 Pod 的 IP 是随机分配的,这种互通信的方式不太适合使用。 尽管一个 Pod 内可以包含多个 Pod,但我们在部署应用容器时的最佳实践是一个 Pod 里面只包含一个应用容器作为主容器,其他容器为主容器服务,称之为辅助容器。例如主容器崩溃了,会有一个辅助容器去重启主容器。辅助容器可以有也可以没有,因为 Pod 里面容器的生命周期可以被 Pod 的生命周期取代,而 Pod 的生命周期可以通过 Pod 管理器来管理维护。\n将我们应用服务隔离单独部署在 Pod 的好处可以罗列以下:\nPod 可以分别的调度到各个 K8s 节点,充分利用了节点的计算资源; 方便我们单独为某个应用服务做扩缩操作。 Pod 创建 # 在K8s集群中一般不会直接单独创建 Pod,而是通过 Pod 管理器。如果单独创建 Pod,Pod的进程被结束的话,Pod 就永远被删除;使用 Pod 管理器创建出来的Pod,Pod 管理器会负责保证Pod按期调度,即使 Pod 被删除,也会重新被调度起来。简而言之,Pod 的生存由 Pod 管理器全权负责。\nPod 管理器包含很多种,由很早的 ReplicationController 过渡到 ReplicaSet 再过渡到当前普遍使用的 Deployment,其实这三者能做的事情是类似的,都是调度和监视 Pod 列表,保证 Pod 列表与声明的数量和其他期望相符。\n除此之外还有其他的管理器:\nStatefuleSet:带状态的 Pod 管理器,需要持久存储数据,一般用于创建数据库类型的应用实例,如 mysql,redis; DaemonSet:每个符合条件的Node都分配一个 Pod,一般用于创建 agent 服务,如日志收集组件,指标数据收集组件等。 一般通过以下方式创建 Pod\n单行命令创建 Pod kubectl run nginx --image=nginx:latest --replicas=2 以上命令实际上是创建了一个 Deployment 资源和由其管理的 2 个 Pod。\n定义资源清单,创建 Pod 以yaml或者json格式定义 Pod 资源,大都选择 yaml。如果你使用VSCode的话,那么 Kubernetes Support 插件会成为你的利器。\n我们先定义个一个 Pod 的资源文件 pod-sample.yaml:\napiVersion: v1 kind: Pod metadata: name: nginx labels: name: nginx spec: containers: - name: nginx image: nginx imagePullPolicy: Always restartPolicy: Never resources: requests: memory: \u0026#34;128Mi\u0026#34; cpu: \u0026#34;256m\u0026#34; limits: memory: \u0026#34;256Mi\u0026#34; cpu: \u0026#34;512m\u0026#34; ports: - containerPort: 80 以上是一个简单的Pod定义文件,我们可以从这个文件得到这些信息:\n运行nginx镜像,作为Pod的主容器,向外暴露80端口; 每次启动这个Pod都会从网络拉取镜像; 如果主容器不小心挂了,则不会被重启; 容器启动需要的最小资源和运行最大资源。 然后通过 kubectl 命令创建(下面这行命令也适用于更新 Pod):\nkubectl apply -f pod-sample.yaml 查看Pod:\nkubectl get pod -o wide 通过查看 Pod 的描述、Pod 里面容器的运行日志,或直接进入 Pod 容器分析定位问题:\n# 描述 Pod 详情 kubectl describe pod \u0026lt;POD_NAME\u0026gt; # 查看 Pod 容器控制台运行日志 kubectl logs \u0026lt;POD_NAME\u0026gt; # 进入 Pod kubectl exec -it \u0026lt;POD_NAME\u0026gt; -c \u0026lt;CONTAINER_NAME\u0026gt; -- \u0026lt;COMMAND\u0026gt; 删除 Pod:\nkubectl delete -f pod-sample.yaml kubectl delete pod \u0026lt;POD_NAME\u0026gt; Pod 字段 # 通过以下命令查看定义 Pod 资源的字段即作用:\nkubectl explain pod kubectl explain pod.spec 对一些字段简单介绍一下:\nimagePullPolicy # 镜像拉取策略,有三种,Always、IfNotPresent、Never\nAlways:每次都拉取最新镜像,默认策略; IfNotPresent:如果 Pod 被调度的Node上已经存在镜像了则直接使用镜像,不存在在拉取; Never:只使用 Node 上的镜像,即使不存在也不拉取。 restartPolicy # Pod 重启策略,有三种:Always、OnFaliure、Never\nAlways:Pod 只要终止运行,kubelet 就会重启它; OnFaliure:Pod非正常终止,退出码不为零,kubelet 就会重启它,正常退出不会重启; Never:退出了就不重启。 nodeSelector # 定义Lable对,选择调度到拥有该 Label 对的 Node 节点。\nlivenessProbe # 存活指针,可以理解为 Pod 内容器运行的健康检查,如果健康检查没通过,则重启 Pod 内容器,这里面的内容有点多,有机会详细讲。\nreadinessProbe # 就绪指针,也是通过健康检查机制,对外呈现 Pod 的就绪状态,如果健康检查通过,Pod 状态为就绪,可以接受外部流量请求。流量无法转发到非就绪状态的 Pod。\ncommand # 容器启动时的命令列表,和 Dockerfile 中定义的 CMD 作用一样。\nargs # 容器启动命令的参数。\nenv # 容器内的环境变量列表,和 Dockerfile 中定义的 ENV 作用一样。\nresource # 可以定义容器启动的最小字段和运行最大分配资源,对 Pod 的资源使用的控制。\nPod 日志 # kubelet 定义了 pod 的日志路径,宿主机目录:\n/var/log/pods/\u0026lt;pod-namespace\u0026gt;_\u0026lt;pod-name\u0026gt;_\u0026lt;pod-uid\u0026gt;/\u0026lt;contianer name\u0026gt;/\u0026lt;restart count\u0026gt;.log 日志文件名为 0.log,1.log\u0026hellip;,这里数字使用的是容器重启的次数。\n« 使用 nfs 持久化存储\n» Kubernetes 编程\n"},{"id":155,"href":"/kubernetes/prgramming-kubernetes/","title":"Prgramming Kubernetes","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 编程\nKubernetes 编程 # 开始 Kuberntes 编程之旅~\nmkdir programming-kubernetes \u0026amp;\u0026amp; cd programming-kubernetes git mod init programming-kubernetes 常用包:\nkubeconfig 对应的结构 clientcmdapi \u0026#34;k8s.io/client-go/tools/clientcmd/api\u0026#34; clientcmdapi.Config 编写自定义 API # 随机生成字符 \u0026#34;k8s.io/apimachinery/pkg/util/rand\u0026#34; rand.String(5) 参考:\nkubernetes/code-generator: Generators for kube-like API types (github.com)\ncode-generator 使用\nB站 code-generator 介绍\nmkdir hack vim hack/boilerplate.go.txt /* Copyright 2022 programming-kubernetes. Licensed under the Apache License, Version 2.0 (the \u0026#34;License\u0026#34;); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \u0026#34;AS IS\u0026#34; BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ mkdir -p pkg/apis/demo/v1alpha vim pkg/apis/demo/v1alpha/doc.go /* Copyright 2022 programming-kubernetes. Licensed under the Apache License, Version 2.0 (the \u0026#34;License\u0026#34;); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \u0026#34;AS IS\u0026#34; BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // +k8s:deepcopy-gen=package // +groupName=demo.pk.io package v1alpha1 文件名称一定要是 doc.go,要不然无法成功生成 deepcopy 代码;\n// +groupName=demo.pk.io,将根据该注释在 client 中生成对应的组\nvim pkg/apis/demo/v1alpha/types.go /* Copyright 2022 programming-kubernetes. Licensed under the Apache License, Version 2.0 (the \u0026#34;License\u0026#34;); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \u0026#34;AS IS\u0026#34; BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1alpha1 import ( corev1 \u0026#34;k8s.io/api/core/v1\u0026#34; metav1 \u0026#34;k8s.io/apimachinery/pkg/apis/meta/v1\u0026#34; ) // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // Foo - type Foo struct { metav1.TypeMeta `json:\u0026#34;,inline\u0026#34;` metav1.ObjectMeta `json:\u0026#34;metadata,omitempty\u0026#34; protobuf:\u0026#34;bytes,1,opt,name=metadata\u0026#34;` Spec FooSpec `json:\u0026#34;spec,omitempty\u0026#34; protobuf:\u0026#34;bytes,2,opt,name=spec\u0026#34;` Status FooStatus `json:\u0026#34;status,omitempty\u0026#34; protobuf:\u0026#34;bytes,3,opt,name=status\u0026#34;` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type FooList struct { metav1.TypeMeta `json:\u0026#34;,inline\u0026#34;` metav1.ListMeta `json:\u0026#34;metadata,omitempty\u0026#34; protobuf:\u0026#34;bytes,1,opt,name=metadata\u0026#34;` Items []Foo `json:\u0026#34;items\u0026#34; protobuf:\u0026#34;bytes,2,rep,name=items\u0026#34;` } type FooSpec struct { Name string `json:\u0026#34;name\u0026#34; protobuf:\u0026#34;bytes,1,opt,name=name\u0026#34;` Desc string `json:\u0026#34;desc\u0026#34; protobuf:\u0026#34;bytes,2,opt,name=desc\u0026#34;` } type FooStatus struct { Conditions []FooCondition `json:\u0026#34;conditions,omitempty\u0026#34; protobuf:\u0026#34;bytes,1,rep,name=conditions\u0026#34;` } type FooCondition struct { // Type of team condition. Type FooConditionType `json:\u0026#34;type\u0026#34; protobuf:\u0026#34;bytes,1,opt,name=type,casttype=TeamConditionType\u0026#34;` // Status of the condition, one of True, False, Unknown. Status corev1.ConditionStatus `json:\u0026#34;status\u0026#34; protobuf:\u0026#34;bytes,2,opt,name=status,casttype=k8s.io/api/core/v1.ConditionStatus\u0026#34;` // Last time the condition transitioned from one status to another. // +optional LastTransitionTime metav1.Time `json:\u0026#34;lastTransitionTime,omitempty\u0026#34; protobuf:\u0026#34;bytes,3,opt,name=lastTransitionTime\u0026#34;` // The reason for the condition\u0026#39;s last transition. // +optional Reason string `json:\u0026#34;reason,omitempty\u0026#34; protobuf:\u0026#34;bytes,4,opt,name=reason\u0026#34;` // A human readable message indicating details about the transition. // +optional Message string `json:\u0026#34;message,omitempty\u0026#34; protobuf:\u0026#34;bytes,5,opt,name=message\u0026#34;` } type FooConditionType string vim pkg/apis/demo/v1alpha/register.go /* Copyright 2022 Ketches. Licensed under the Apache License, Version 2.0 (the \u0026#34;License\u0026#34;); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \u0026#34;AS IS\u0026#34; BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1alpha1 import ( metav1 \u0026#34;k8s.io/apimachinery/pkg/apis/meta/v1\u0026#34; \u0026#34;k8s.io/apimachinery/pkg/runtime\u0026#34; \u0026#34;k8s.io/apimachinery/pkg/runtime/schema\u0026#34; ) var SchemeGroupVersion = schema.GroupVersion{Group: \u0026#34;demo.pk.io\u0026#34;, Version: \u0026#34;v1alpha1\u0026#34;} var ( SchemeBuilder runtime.SchemeBuilder localSchemeBuilder = \u0026amp;SchemeBuilder AddToScheme = localSchemeBuilder.AddToScheme ) func init() { // We only register manually written functions here. The registration of the // generated functions takes place in the generated files. The separation // makes the code compile even when the generated files are missing. localSchemeBuilder.Register(addKnownTypes) } // Resource takes an unqualified resource and returns a Group qualified GroupResource func Resource(resource string) schema.GroupResource { return SchemeGroupVersion.WithResource(resource).GroupResource() } // Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, \u0026amp;Foo{}, \u0026amp;FooList{}, ) scheme.AddKnownTypes(SchemeGroupVersion, \u0026amp;metav1.Status{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil } 需要在机器上克隆下 k8s.io/code-generator 仓库:\nmkdir -p $GOPATH/src/k8s.io \u0026amp;\u0026amp; cd $GOPATH/src/k8s.io git clone https://github.com/kubernetes/code-generator.git 回到 programming-kubernetes/hack 项目目录:\nmkdir hack \u0026amp;\u0026amp; cd hack vim update-codegen.sh #!/usr/bin/env bash # Copyright 2022 Ketches. # Licensed under the Apache License, Version 2.0 (the \u0026#34;License\u0026#34;); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an \u0026#34;AS IS\u0026#34; BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit set -o nounset set -o pipefail SCRIPT_ROOT=$(dirname \u0026#34;${BASH_SOURCE[0]}\u0026#34;)/.. # generate the code with: # - --output-base because this script should also be able to run inside the vendor dir of # k8s.io/kubernetes. The output-base is needed for the generators to output into the vendor dir # instead of the $GOPATH directly. For normal projects this can be dropped. ~/go/src/k8s.io/code-generator/generate-groups.sh \\ all \\ programming-kubernetes/pkg/client \\ programming-kubernetes/pkg/apis \\ \u0026#34;demo.dev:v1alpha1\u0026#34; \\ --output-base ${SCRIPT_ROOT}/.. \\ --go-header-file ${SCRIPT_ROOT}/hack/boilerplate.go.txt \u0026ldquo;demo:v1alpha1\u0026rdquo; 需要保持 api 的目录一致,而不是 groupname\n如果项目 mod 名称中包含 “/”,那么项目也需要放在完整的目录下,例如 mod 名称为 github.com/poneding/programming-kubernetes,那么项目一定要放在 [..]/github.com/poneding/programming-kubernetes 下,此时 --output-base 也需要调整,需要增加目录查找长度 ${SCRIPT_ROOT}/../../..。\n测试使用自定义 API 的 client,infromer,lister 等:\n# programming-kubernets 项目目录 mkdir -p manifests/crd vim manitests/crd/demo-foo.yaml 安装 controller-gen 工具\ngo install sigs.k8s.io/controller-tools/cmd/controller-gen@latest 生成 crd yaml 文件\ncontroller-gen « Kubernetes 0-1 了解 Pod\n» Prometheus-监控Kong完整操作\n"},{"id":156,"href":"/kubernetes/prometheus-collect-kong-metrics/","title":"Prometheus Collect Kong Metrics","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Prometheus-监控Kong完整操作\nPrometheus-监控Kong完整操作 # 本篇记录使用Prometheus收集Kong暴露的/metrics接口,收集指标数据,从而实现对Kong的监控。\n先决条件 # Prometheus部署完成; Kong(Kong 服务,端口8000)部署完成; Kong 的Admin Api(端口8001)部署完成 Konga(Kong的WebUI,端口1337)部署完成。 Kong添加Prometheus插件 # 登录进入Konga; 点击右边菜单栏”PLUGINS“,进入Plugins管理,点击“Analytics \u0026amp; Monitoring”,选择添加Promethus插件 Kong添加metrics接口 # 我们知道Prometheus主要通过读取 http://host/metrics接口, 来收集相关服务的性能数据,但是Kong的metrics接口服务默认是没有开启的,所以需要先为Kong添加/metrics。\n登录进入Konga; 点击右边菜单栏”SERVICES“,进入Services管理,点击“ADD NEW SERVICE” 添加页面输入“Name”和“Url”参数即可,例如“Name”=“prometheusService”,“Url”=“ http://localhost:8001/metrics” 添加完Prometheus Service之后,Service列表选中并点击进入prometheusService,选择”Routes“菜单,点击“ADD ROUTE” 添加页面输入“Paths”参数即可,例如“Paths”=[“/metrics”](Path必须以“/”为首) 这时候访问“ http://localhost:8000/metrics”,看到页面如下显示,说明已经成功的添加了metrics接口 Prometheus添加Kong指标收集 # 修改Prometheus配置文件,prometheus.yml\nscrape_configs配置项下添加如下配置\n- job_name: \u0026#39;kong\u0026#39; scrape_interval: 5s static_configs: - targets: [\u0026#39;localhost:8000\u0026#39;] 配置完之后重启Prometheus,访问“ http://localhost:9090/graph”\n可以看到一已经生成了很多kong的指标项,如http访问,nginx当前访问量等指标\n« Kubernetes 编程\n» Prometheus\n"},{"id":157,"href":"/kubernetes/prometheus/","title":"Prometheus","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Prometheus\nPrometheus # Intro # 是一个面向云原生应用程序的开源的监控\u0026amp;报警工具。\n开源:继Kubernetes之后的第二个CNCF开源项目,Go语言开发 监控:通过HTTP服务定时从配置的目标收集指标数据,识别数据展示规则,展示监控系统和服务指标数据 报警:监控到的指标数据达到某一设定好的条件时,触发报警机制 时间序列数据库: 时间序列是由唯一的指标名称(Metrics)和一组标签(key=value)的形式组成 PromeQL:基于Prometheus时间序列数据库的一个灵活的查询语言 Prometheus的监控对象 # 资源监控:\n服务器的资源使用情况,在Kubernetes集群中,则可以做到对Kubernetes Node、 Deployment 、Pod的资源利用以及apiserver,controller-manager,etcd等组件的监控。\n应用监控:\n业务应用暴露端口供Prometheus调用实现监测,比如实时的用户在线人数,数据库的当前连接数等。\nPrometheus优势 # 支持机器资源和动态配置的应用监控; 多维数据收集和查询; 服务独立,少依赖。 Prometheus组件 # Prometheus Server:采集监控数据,存储时序指标,提供数据查询; Prometheus Client SDK:对接开发工具包,提供自定义的指标数据等; Push Gateway:推送指标数据的网关组件; Third-part Exporter:外部指标采集系统,暴露接口供Prometheus采集; AlertManager:指标数据的分析告警管理器; Architecture overview # 上图来源于官网:\n处理流程: 配置资源目标或应用抓取; 抓取资源或应用指标数据; 制定报警规则,推送报警; 灵活查询语言,结合Grafana展示 Installation \u0026amp; Start Up # 1. 以服务进程运行Prometheus # ​ 在ubuntu系统上安装Prometheus,一般有两种方式\n第一种,安装命令如下:\nwget https://github.com/prometheus/prometheus/releases/download/v2.13.1/prometheus-2.13.1.linux-amd64.tar.gz tar xvfz prometheus-2.13.1.linux-amd64.tar.gz # 启动Prometheus cd prometheus-2.13.1.linux-amd64 ./prometheus # 停止Prometheus ps -ef | grep prometheus # 定位Prometheus进程的pid kill -s 9 [pid] 第二种,安装命令如下(ubuntu系统):\nwget https://s3-eu-west-1.amazonaws.com/deb.robustperception.io/41EFC99D.gpg | sudo apt-key add - sudo apt-get update -y sudo apt-get install prometheus prometheus-node-exporter prometheus-pushgateway prometheus-alertmanager -y # 启动Prometheus sudo systemctl start prometheus sudo systemctl enable prometheus sudo systemctl status prometheus Service Proxy # 默认Prometheus的访问方式是http://{ip/domain-name}:9090,而服务器一般的是不会暴露9090端口的,而是暴露80端口,其他端口以nginx代理访问。\n下面的步骤便是将http://{ip/domain-name}:9090代理为http://{ip/domain-name}/prometheus\n​ Step 1 配置prometheus:\n配置\u0026ndash;web.external-url\n如果是第一种方式安装的Prometheus,则启动Prometheus的命令行需要附带\u0026ndash;web.external-url=prometheus,完整命令如下:\n./prometheus --web.external-url=prometheus 如果是第二种方式安装的Prometheus,则需要在/etc/default/prometheus文件在ARGS=\u0026quot;\u0026quot;中添加参数--web.external-url=prometheus,添加完之后,文件应该是像下面这样的:\n重启prometheus\nsudo systemctl restart prometheus 重启之后,http://{ip/domain-name}:9090/prometheus可以访问。\n​ Step 2 配置nginx:\n默认服务器已经安装了nginx。\nnginx.conf文件,注释掉以下行:\n#include /etc/nginx/sites-enabled/*;\n执行以下命令:\nsudo vim /etc/nginx/conf.d/prometheus.conf 在文件中写入以下内容:\nserver { location /prometheus { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; proxy_pass http://172.31.23.19:9090/prometheus; break; } } 重启nginx\nsudo systemctl restart nginx.service 重启之后,http://{ip/domain-name}/prometheus可以访问。\n停止Prometheus # sudo systemctl stop prometheus 2.Docker启动Prometheus # docker run --name prometheus -d -p 127.0.0.1:9090:9090 prom/prometheus 3. Kubernetes运行Prometheus # ConfigMap # Prometheus的配置文件\napiVersion: v1 kind: ConfigMap metadata: name: prometheus-config namespace: ns-prometheus data: prometheus.yml: | global: scrape_interval: 15s scrape_timeout: 15s scrape_configs: - job_name: \u0026#39;prometheus\u0026#39; static_configs: - targets: [\u0026#39;localhost:9090\u0026#39;] Deployment # apiVersion: app/v1 kind: Deployment metadata: name: prometheus namespace: ns-prometheus labels: app: prometheus spec: template: metadata: labels: app: prometheus spec: containers: - image: prom/prometheus name: prometheus imagePullPolicy: IfNotPresent args: - \u0026#34;--config.file=/etc/prometheus/prometheus.yml\u0026#34; - \u0026#34;--storage.tsdb.path=/prometheus\u0026#34; - \u0026#34;--storage.tsdb.retention=7d\u0026#34; - \u0026#34;--web.enable-admin-api\u0026#34; - \u0026#34;--web.enable-lifecycle\u0026#34; ports: - containerPort: 9090 name: http volumeMounts: - mountPath: \u0026#34;/prometheus\u0026#34; subPath: prometheus name: data - mountPath: \u0026#34;/etc/prometheus\u0026#34; name: config resources: requests: cpu: 1000m memory: 2Gi limits: cpu: 1000m memory: 2Gi securityContext: runAsUser: 0 volumes: - name: config configMap: name: prometheus-config - name: data persistentVolumeClaim: claimName: prometheus PersistentVolumeClaim # apiVersion: v1 kind: PersistentVolumeClaim metadata: name: prometheus namespace: ns-prometheus spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi storageClassName: \u0026#34;rook-ceph-block\u0026#34; ServiceAccount # apiVersion: v1 kind: ServiceAccount metadata: name: prometheus namespace: ns-prometheus --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: prometheus rules: - apiGroups: - \u0026#34;\u0026#34; resources: - nodes - services - endpoints - pods - nodes/proxy verbs: - get - list - watch - apiGroups: - \u0026#34;\u0026#34; resources: - configmaps - nodes/metrics verbs: - get - nonResourceURLs: - /metrics verbs: - get --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: prometheus roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: prometheus subjects: - kind: ServiceAccount name: prometheus namespace: kube-ops Service # apiVersion: v1 kind: Service metadata: name: prometheus namespace: ns-prometheus labels: app: prometheus spec: selector: app: prometheus type: NodePort ports: - name: web port: 9090 targetPort: http Apply # kubectl apply -f . kubectl get svc -o wide -n ns-prometheus 4.官方kubernets运行Prometheus # 参考: https://github.com/coreos/kube-prometheus\n5.Kubernetes部署Prometheus+Grafana # 下载相关k8s资源文件 git clone https://github.com/coreos/kube-prometheus.git 修改文件kube-prometheus/manifests/prometheus-prometheus.yaml,做这一步的目的是为prometheus的访问分配子路径,访问方式为http(s)://xxx/prometheus\n在prometheus.spec下添加\nexternalUrl: prometheus routePrefix: prometheus 修改文件kube-prometheus/manifests/grafana-deployment.yaml,做这一步的目的是为grafana的访问分配子路径,访问方式为:http(s)://xxx/grafana\n在deployment.spec.template.spec.container[0]下添加\nenv: - name: GF_SERVER_ROOT_URL value: \u0026#34;http://localhost:3000/grafana\u0026#34; - name: GF_SERVER_SERVE_FROM_SUB_PATH value: \u0026#34;true\u0026#34; Apply k8s资源 # 可能需要运行多次以下命令,确保k8s资源都创建 kubectl create -f manifests/setup -f manifests # !如果要删除以上创建的k8s资源,运行以下命令 kubectl delete --ignore-not-found=true -f manifests/ -f manifests/setup Ingress转发 apiVersion: extensions/v1beta1 kind: Ingress metadata: name: prometheus namespace: monitoring spec: rules: - host: dp.example.tech http: paths: - path: /prometheus backend: serviceName: prometheus-k8s servicePort: 9090 - path: /grafana backend: serviceName: grafana servicePort: 3000 - path: /alertmanager backend: serviceName: alertmanager-main servicePort: 9093 Configuring Prometheus # ​ 配置文件为prometheus.yml,文件格式为yaml。\nConcepts # Metrics # 存储 # Prometheus 2.x 默认将时间序列数据库保存在本地磁盘中,同时也可以将数据保存到任意第三方的存储服务中。\n本地存储 # Prometheus 采用自定义的存储格式将样本数据保存在本地磁盘当中。\n存储格式 # Prometheus 按照两个小时为一个时间窗口,将两小时内产生的数据存储在一个块(Block)中。每个块都是一个单独的目录,里面含该时间窗口内的所有样本数据(chunks),元数据文件(meta.json)以及索引文件(index)。其中索引文件会将指标名称和标签索引到样板数据的时间序列中。此期间如果通过 API 删除时间序列,删除记录会保存在单独的逻辑文件 tombstone 当中。\n当前样本数据所在的块会被直接保存在内存中,不会持久化到磁盘中。为了确保 Prometheus 发生崩溃或重启时能够恢复数据,Prometheus 启动时会通过预写日志(write-ahead-log(WAL))重新记录,从而恢复数据。预写日志文件保存在 wal 目录中,每个文件大小为 128MB。wal 文件包括还没有被压缩的原始数据,所以比常规的块文件大得多。一般情况下,Prometheus 会保留三个 wal 文件,但如果有些高负载服务器需要保存两个小时以上的原始数据,wal 文件的数量就会大于 3 个。\nPrometheus保存块数据的目录结构如下所示:\nrometheus 提供了几个参数来修改本地存储的配置,最主要的有: 启动参数 默认值 含义 --storage.tsdb.path /data 数据存储路径 --storage.tsdb.retention.time 15d 样本数据在存储中保存的时间。超过该时间限制的数据就会被删除。 --storage.tsdb.retention.size 0 每个块的最大字节数(不包括 wal 文件)。如果超过限制,最早的样本数据会被优先删除。支持的单位有 KB, MB, GB, PB,例如:“512MB”。该参数只是试验性的,可能会在未来的版本中被移除。 --storage.tsdb.retention 该参数从 2.7 版本开始已经被弃用,使用 --storage.tsdb.retention.time 参数替代 在一般情况下,Prometheus 中存储的每一个样本大概占用1-2字节大小。如果需要对 Prometheus Server 的本地磁盘空间做容量规划时,可以通过以下公式计算:./data |- 01BKGV7JBM69T2G1BGBGM6KB12 # 块 |- meta.json # 元数据 |- wal # 写入日志 |- 000002 |- 000001 |- 01BKGTZQ1SYQJTR4PB43C8PD98 # 块 |- meta.json #元数据 |- index # 索引文件 |- chunks # 样本数据 |- 000001 |- tombstones # 逻辑数据 |- 01BKGTZQ1HHWHV8FBJXW1Y3W0K |- meta.json |- wal |-000001 最初两个小时的块最终会在后台被压缩成更长的块。\n[info] 注意:\n本地存储不可复制,无法构建集群,如果本地磁盘或节点出现故障,存储将无法扩展和迁移。因此我们只能把本地存储视为近期数据的短暂滑动窗口。如果你对数据持久化的要求不是很严格,可以使用本地磁盘存储多达数年的数据。\n关于存储格式的详细信息,请参考 TSDB 格式\n本地存储配置 # rometheus 提供了几个参数来修改本地存储的配置,最主要的有:\n启动参数 默认值 含义 \u0026ndash;storage.tsdb.path /data 数据存储路径 \u0026ndash;storage.tsdb.retention.time 15d 样本数据在存储中保存的时间。超过该时间限制的数据就会被删除。 \u0026ndash;storage.tsdb.retention.size 0 每个块的最大字节数(不包括 wal 文件)。如果超过限制,最早的样本数据会被优先删除。支持的单位有 KB, MB, GB, PB,例如:“512MB”。该参数只是试验性的,可能会在未来的版本中被移除。 \u0026ndash;storage.tsdb.retention 该参数从 2.7 版本开始已经被弃用,使用 \u0026ndash;storage.tsdb.retention.time 参数替代 在一般情况下,Prometheus 中存储的每一个样本大概占用1-2字节大小。如果需要对 Prometheus Server 的本地磁盘空间做容量规划时,可以通过以下公式计算:\nneeded_disk_space = retention_time_seconds * ingested_samples_per_second * bytes_per_sample 从上面公式中可以看出在保留时间(retention_time_seconds)和样本大小(bytes_per_sample)不变的情况下,如果想减少本地磁盘的容量需求,只能通过减少每秒获取样本数(ingested_samples_per_second)的方式。因此有两种手段,一是减少时间序列的数量,二是增加采集样本的时间间隔。考虑到 Prometheus 会对时间序列进行压缩效率,减少时间序列的数量效果更明显。\n如果你的本地存储出现故障,最好的办法是停止运行 Prometheus 并删除整个存储目录。因为 Prometheus 的本地存储不支持非 POSIX 兼容的文件系统,一旦发生损坏,将无法恢复。NFS 只有部分兼容 POSIX,大部分实现都不兼容 POSIX。\n除了删除整个目录之外,你也可以尝试删除个别块目录来解决问题。删除每个块目录将会丢失大约两个小时时间窗口的样本数据。所以,Prometheus 的本地存储并不能实现长期的持久化存储。:\n如果同时指定了样本数据在存储中保存的时间和大小,则哪一个参数的限制先触发,就执行哪个参数的策略。\n远程存储 # Prometheus 的本地存储无法持久化数据,无法灵活扩展。为了保持Prometheus的简单性,Prometheus并没有尝试在自身中解决以上问题,而是通过定义两个标准接口(remote_write/remote_read),让用户可以基于这两个接口对接将数据保存到任意第三方的存储服务中,这种方式在 Promthues 中称为 Remote Storage。\nPrometheus 可以通过两种方式来集成远程存储。\nRemote Write # 用户可以在 Prometheus 配置文件中指定 Remote Write(远程写)的 URL 地址,一旦设置了该配置项,Prometheus 将采集到的样本数据通过 HTTP 的形式发送给适配器(Adaptor)。而用户则可以在适配器中对接外部任意的服务。外部服务可以是真正的存储系统,公有云的存储服务,也可以是消息队列等任意形式。\nRemote Read # 如下图所示,Promthues 的 Remote Read(远程读)也通过了一个适配器实现。在远程读的流程当中,当用户发起查询请求后,Promthues 将向 remote_read 中配置的 URL 发起查询请求(matchers,ranges),Adaptor 根据请求条件从第三方存储服务中获取响应的数据。同时将数据转换为 Promthues 的原始样本数据返回给 Prometheus Server。\n当获取到样本数据后,Promthues 在本地使用 PromQL 对样本数据进行二次处理。\n[info] 注意:\n启用远程读设置后,Prometheus 仅从远程存储读取一组时序样本数据(根据标签选择器和时间范围),对于规则文件的处理,以及 Metadata API 的处理都只基于 Prometheus 本地存储完成。这也就意味着远程读在扩展性上有一定的限制,因为所有的样本数据都要首先加载到 Prometheus Server,然后再进行处理。所以 Prometheus 暂时不支持完全分布式处理。\n远程读和远程写协议都使用了基于 HTTP 的 snappy 压缩协议的缓冲区编码,目前还不稳定,在以后的版本中可能会被替换成基于 HTTP/2 的 gRPC 协议,前提是 Prometheus 和远程存储之间的所有通信都支持 HTTP/2。\n配置文件 # 想知道如何在 Prometheus 中添加远程存储的配置,请参考前面的章节: 配置远程写 和 配置远程读。\n关于请求与响应消息的详细信息,可以查看远程存储相关协议的 proto 文件:\nsyntax = \u0026#34;proto3\u0026#34;; package prometheus; option go_package = \u0026#34;prompb\u0026#34;; import \u0026#34;types.proto\u0026#34;; import \u0026#34;gogoproto/gogo.proto\u0026#34;; message WriteRequest { repeated prometheus.TimeSeries timeseries = 1 [(gogoproto.nullable) = false]; } message ReadRequest { repeated Query queries = 1; } message ReadResponse { // In same order as the request\u0026#39;s queries. repeated QueryResult results = 1; } message Query { int64 start_timestamp_ms = 1; int64 end_timestamp_ms = 2; repeated prometheus.LabelMatcher matchers = 3; prometheus.ReadHints hints = 4; } message QueryResult { // Samples within a time series must be ordered by time. repeated prometheus.TimeSeries timeseries = 1; } 支持的远程存储 # 目前 Prometheus 社区也提供了部分对于第三方数据库的 Remote Storage 支持:\n存储服务 支持模式 AppOptics write Chronix write Cortex read/write CrateDB read/write Elasticsearch write Gnocchi write Graphite write InfluxDB read/write IRONdb read/write Kafka write M3DB read/write OpenTSDB write PostgreSQL/TimescaleDB read/write SignalFx write Splunk write TiKV read/write VictoriaMetrics write Wavefront write 联邦集群 # 联邦使得一个 Prometheus 服务器可以从另一个 Prometheus 服务器提取选定的时序。\n使用场景 # Prometheus 联邦有不同的使用场景。通常,联邦被用来实现可扩展的 Prometheus 监控设置,或者将相关的指标从一个服务的 Prometheus 拉取到另一个 Prometheus 中。\n分层联邦 # 分层联邦允许 Prometheus 能够扩展到十几个数据中心和上百万的节点。在此场景下,联邦拓扑类似一个树形拓扑结构,上层的 Prometheus 服务器从大量的下层 Prometheus 服务器中收集和汇聚的时序数据。\n例如,一个联邦设置可能由多个数据中心中的 Prometheus 服务器和一套全局 Prometheus 服务器组成。每个数据中心中部署的 Prometheus 服务器负责收集本区域内细粒度的数据(实例级别),全局 Prometheus 服务器从这些下层 Prometheus 服务器中收集和汇聚数据(任务级别),并存储聚合后的数据。这样就提供了一个聚合的全局视角和详细的本地视角。\n跨服务联邦 # 在跨服务联邦中,一个服务的 Prometheus 服务器被配置来提取来自其他服务的 Prometheus 服务器的指定的数据,以便在一个 Prometheus 服务器中对两个数据集启用告警和查询。\n例如,一个运行多种服务的集群调度器可以暴露在集群上运行的服务实例的资源使用信息(例如内存和 CPU 使用率)。另一方面,运行在集群上的服务只需要暴露指定应用程序级别的服务指标。通常,这两种指标集分别被不同的 Prometheus 服务器抓取。利用联邦,监控服务级别指标的 Prometheus 服务器也可以从集群中 Prometheus 服务器拉取其特定服务的集群资源使用率指标,以便可以在该 Prometheus 服务器中使用这两组指标集。\n配置联邦 # 在 Prometheus 服务器中,/federate 节点允许获取服务中被选中的时间序列集合的值。至少一个 match[] URL 参数必须被指定为要暴露的序列。每个 match[] 变量需要被指定为一个 不变的维度选择器像 up 或者 {job=\u0026quot;api-server\u0026quot;}。如果有多个 match[] 参数,则所有符合的时序数据的集合都会被选择。\n从一个 Prometheus 服务器联邦指标到另一个 Prometheus 服务器,配置你的目标 Prometheus 服务器从源服务器的 /federate 节点抓取指标数据,同时也使用 honor_lables 抓取选项(不重写源 Prometheus 服务暴露的标签)并且传递需要的 match[] 参数。例如,下面的 scrape_configs 联邦 source-prometheus-{1,2,3}:9090 三台 Prometheus 服务器,上层 Prometheus 抓取并汇总他们暴露的任何带 job=\u0026quot;prometheus\u0026quot; 标签的序列或名称以 job: 开头的指标。\nscrape_configs: - job_name: \u0026#39;federate\u0026#39; scrape_interval: 15s honor_labels: true metrics_path: \u0026#39;/federate\u0026#39; params: \u0026#39;match[]\u0026#39;: - \u0026#39;{job=\u0026#34;prometheus\u0026#34;}\u0026#39; - \u0026#39;{__name__=~\u0026#34;job:.*\u0026#34;}\u0026#39; static_configs: - targets: - \u0026#39;source-prometheus-1:9090\u0026#39; - \u0026#39;source-prometheus-2:9090\u0026#39; - \u0026#39;source-prometheus-3:9090\u0026#39; Monitor App # MSSQL # 参考\n监控Mssql应用\n在一台安装了Docker的服务器上运行mssql-exporter\nexport MSSQL_PASS=\u0026#39;xxx\u0026#39; sudo docker run -e SERVER=db-dev-ms01.example.com -e USERNAME=starlightdba -e PASSWORD=$DEV_MSSQL_PASS -e DEBUG=app --rm -d -p 4000:4000 --name dev-mssql awaragi/prometheus-mssql-exporter 注意:Mssql账号需要有管理员权限。\nprometheus.yaml新增监控 的配置\n监控Mssql服务器 solved # « Prometheus-监控Kong完整操作\n» PVC 扩容\n"},{"id":158,"href":"/kubernetes/pvc-expansion/","title":"Pvc Expansion","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / PVC 扩容\nPVC 扩容 # K8s 部署的 Kafka 程序突然挂了,查看相关日志发现原来是挂日志的磁盘空间不足,那么现在需要对磁盘进行扩容。\n使用以下命令执行 PVC 扩容的操作:\nkubectl edit pvc \u0026lt;pvc-name\u0026gt; -n \u0026lt;namespace\u0026gt; 执行过程中发现,无法对该 PVC 进行动态扩容,需要分配 PVC 存储的 StorageClass 支持动态扩容。\n那么怎么是的 StorageClass 支持动态扩容呢,很简单,更新 StorageClass 即可。\nkubectl edit storageclass \u0026lt;storageclass-name\u0026gt; 添加属性:\nallowVolumeExpansion: true # 允许卷扩充 之后再次执行 PVC 扩容的操作即可。\n« Prometheus\n» 了解 Secret\n"},{"id":159,"href":"/kubernetes/secret-understood/","title":"Secret Understood","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 了解 Secret\n了解 Secret # 通常我们的应用程序的配置都会包含一些敏感信息,例如数据库连接字符串,证书,私钥等,为了保证其安全性,K8s 提供了 Secret 资源对象来保存敏感数据,它和 CongfigMap 类似,也是键值对的映射,并且使用方式也几乎一样。\n介绍 Secret # Secret 中存储着键值对数据,可以\n作为环境变量传递给容器 作为文件挂载到容器的 Volume Secret 会存储在 Pod 所调度的节点的内存中,而不是写入磁盘。\nPod 默认生成的 Secret # 每个 Pod 都会被自动挂载一个 Secret 卷,只需要使用 kubectl desribe pod 命令就能看到一个名称类似 default-token-n4q6m 的 Secret,Secret 也是一种 K8s 资源,所以,可以使用 kubectl get secret 或 kubectl describe secret 获取查看。\n从上面图例可以看出,Pod 默认生成的 Secret 会包含三个配置项:ca.crt、namespace、token。其实这三个配置项是 Pod 内部安全访问Kubernetes API 服务的所有信息,而在 kubectl describe pod 的时候,你可以看到 Secret 所挂载的具体目录在 /var/run/secrets/kubernetes.io/serviceaccount.\n每个 Pod 会默认生成 default-token-xxxxx 的 Secret,可以通过在Pod 中定义 pod.spec.automountServiceAccountToken 为 false 来关闭这种默认行为。\n创建 Secret # 可以直接通过 kubectl create secret 命令创建,也可以先编写 secret 的 yaml 文件再使用 kubectl apply -f \u0026lt;filename\u0026gt; 创建,推荐使用后者。\n单行命令创建 Secret # 创建一个键值对的 secret: kubectl create secret generic first-secret --from-literal=user=admin --from-literal=password=admin123 创建完成之后,使用 kubectl describe secret first-secret 查看,可以看到这个 secret 的键值内容并不会直接打印出来,而是只显示了占用了多少个字节。\n创建一个文件内容的 Secret 假如我当前有一个配置文件 secret.json,文件内容如下:\n{ \u0026#34;User\u0026#34;: \u0026#34;admin\u0026#34;, \u0026#34;Password\u0026#34;: \u0026#34;admin123\u0026#34; } 使用以下命令创建 Secret:\nkubectl create secret generic second-secret --from-file=secret.json 创建完成之后,使用 kubectl describe secret second-secret 查看 secret 的键值内容,同样也不会将文件内容显示出来:\n默认使用文件名称 secret.json 作为键值对的 key,也可以通过 --from-file=second_secret=app.json 指定 key 为 second_secret;\n可以使用多组 --from-file=\u0026lt;key\u0026gt;=\u0026lt;filename\u0026gt; 参数,在 secret 中定义多组文件;\n--from-file= 后面可以直接跟某个文件路径,这样会将目录下的所有文件引入到 Secret;\n--from-literal 和 --from-file 可以共同使用,键值合并。\n删除创建的 first-secret 和 second-secret:\nkubectl delete secret first-secret kubectl delete secret second-secret 基于资源清单文件创建 Secret # 创建一个键值对的 Secret: 首先定义 Secret 的资源文件 first-secret.yaml,定义如下:\n先使用 base64 对 secret 资源文件中要保存的键值编码\necho \u0026#34;admin\u0026#34; | base64 # 得到 YWRtaW4K echo \u0026#34;admin123\u0026#34; | base64 # 得到 YWRtaW4xMjMK vim first-secret.yaml apiVersion: v1 kind: Secret metadata: name: first-secret data: user: \u0026#34;YWRtaW4K\u0026#34; password: \u0026#34;YWRtaW4xMjMK\u0026#34; 使用 kubectl apply 命令创建 Secret 资源:\nkubectl apply -f first-secret.yaml 创建完成之后,使用 kubectl describe secret first-secret 查看。\n可以在 data 下定义多组键值对。\n创建一个文件内容的 Secret 首先定义 Secret 的资源文件 second-secret.yaml,定义如下:\n先使用 base64 对上文中的 secret.json 文件内容编码:\necho $(cat secret.json) | base64 # 得到 eyAiVXNlciI6ICJhZG1pbiIsICJQYXNzd29yZCI6ICJhZG1pbjEyMyIgfQo= vim second-secret.yaml apiVersion: v1 kind: Secret metadata: name: second-secret data: secret.json: eyAiVXNlciI6ICJhZG1pbiIsICJQYXNzd29yZCI6ICJhZG1pbjEyMyIgfQo= 使用 kubectl apply 命令创建 Secret 资源:\nkubectl apply -f second-secret.yaml 创建完成之后,使用 kubectl describe secret second-secret 查看。\n可以在 data 下定义多组文件,也可以和键值对一起定义;\n删除创建的 first-secret 和 second-secret:\nkubectl delete secret first-secret kubectl delete secret second-secret 使用 Secret # Secret 的用途也与 ConfigMap 相差无几:\n使用 Secret 作为容器的环境变量 使用 Secret 作为 Volume 向容器提供文件 使用 Secret 作为容器的环境变量 # 假如有一个名为 first-secret 的 Secret,里面包含了一个键为 user,我想将这个 Secret 中 user 键用到我的环境变量 USER_NAME 中,可以使用如下方式:\n... env: - name: USER_NAME valueFrom: secretKeyRef: name: first-secret key: user ... 如果有一个名为 second-secret Secret 中包含多个键如 USER_NAME,PASSWORD,我想将这个 Secret 中所有的键都用到我的环境变量中,可以使用如下方式:\n... spec: container: - image: \u0026lt;some-image\u0026gt; envFrom: - prefix: MYSQL_ secretRef: name: second-secret ... 容器将会生成 DB_USER_NAME,DB_PASSWORD 环境变量,prefix 也可以不配置,则直接使用 Secret 的键。\n注意:\nsecretRef 与上面 secretKeyRef 的区别; 如果Secret中有一个为 USER-NAME 键,那么将不会生成 MYSQL_USER-NAME 的环境变量,因为MYSQL_USER-NAME 不是一个合法的环境变量名称。 使用 Secret 为容器的 Volume 提供文件 # 上次的文章——《 了解 ConfigMap》中,使用 ConfigMap 向容器提供文件,这次使用 Secret 来实际使用一下。\n我们现在有一个文件 secret.json 要传递到容器中,文件内容如下:\n{ \u0026#34;User\u0026#34;: \u0026#34;admin\u0026#34;, \u0026#34;Password\u0026#34;: \u0026#34;admin123\u0026#34; } 创建 Secret\nkubectl create secret generic mockapi-secret --from-file=secret.json 定义 mockapi-pod.yaml 文件如下:\napiVersion: v1 kind: Pod metadata: name: mockapi spec: containers: - name: mockapi image: poneding/mockapi:v1 ports: - containerPort: 80 volumeMounts: - mountPath: /app/mysettings/secret.json name: mockapi-secret subPath: secret.json volumes: - name: mockapi-secret secret: secretName: mockapi-secret 创建 Pod:\nkubectl apply -f mockapi-pod.yaml 一段时间后,可以验证文件是否挂载到容器:\nYeah!没毛病。\n使用 Secret 拉取私有镜像 # 当我们要访问拉取私有仓库或者私有镜像时,我们需要可能需要使用到 Secret。\n比如我现在将我 docker 仓库中的镜像 mockapi 设置成私有镜像,这是我使用该镜像创建 Pod 是会显示镜像拉取失败的,很明显,我需要登录 docker。\n创建镜像仓库 Secret kubectl create secret docker-registry docker-hub-secret --docker-username=\u0026lt;my_username\u0026gt; --docker-password=\u0026lt;my_password\u0026gt; 私有仓库的话使用 --docker-server 指定;\n更多使用 kubectl create secret docker-registry --help 查看\nPod 中使用 imagePullSecrets: ... kind: Pod spec: imagePullSecrets: - name: docker-hub-secret containers: - name: mockapi image: poneding/mockapi:v1 ... 使用 ServiceAccount 如果很多镜像都要从私有仓库拉取,那最好将 secret 添加到一个固定的 ServiceAccount 中,一个 ServiceAccount 可以包含多个镜像仓库 Secret:\napiVersion: v1 kind: ServiceAccount metadata: name: docker-service-account imagePullSecrets: - name: docker-hub-secret - name: harbor-secret 这时 Pod 使用 ServiceAccount 即可:\n... kind: Pod spec: serviceAccountName: docker-service-account ... « PVC 扩容\n» 了解 Service\n"},{"id":160,"href":"/kubernetes/service-understood/","title":"Service Understood","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 了解 Service\n了解 Service # Service 介绍 # 按照官方文档的说法,在 K8s 中,Service 是将运行在集群中的一组 Pod 的应用公开为网络服务的抽象方法,是 K8s 的核心概念之一,Service 的主要作用是使客户端发现 Pod 并与之通信。\n简单理解起来就是,由 Service 提供统一的入口地址,然后将请求负载分发到后端 Pod 的容器应用。\n为什么有 Service # 集群中部署了 Pod,应用是成功的部署起来了,但是只是至此的话,Pod 提供服务访问存在以下一些问题。\nPod 是短暂的,可能会被销毁或重新调度,这使得 Pod 的 IP 是随时变动和更新的; 部署多个 Pod 的伸缩问题,流量分配问题; 集群外部客户端无法直接访问 Pod。 这时候就需要 Service,Pod 作为 Service 的后端提供服务。所以我们可以想象,Service 需要完成的事情:\n服务发现,通过 Pod 的 lable 查找目标 Pod,将查找的 Pod 的注册到自己的后端列表,Pod 的 IP 信息发生更改,后端列表也同步更新; 负载均衡,请求到达 Service 之后,将请求均衡转发的后端列表; 服务暴露:对外提供统一的请求地址。 创建 Service # 在创建 Sercvice 之前我们首先创建 service 代理的 Pod,nginx-pod.yaml:\napiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx ports: - containerPort: 80 先给到一个简单的 Service 定义实例 nginx-service.yaml:\napiVersion: v1 kind: Service metadata: name: nginx spec: ports: - name: http port: 80 protocol: TCP targetPort: 80 selector: app: nginx type: LoadBalancer spec.ports.port 是 Service 对外提供服务的端口,spec.ports.targetPort 是转发到 Pod 内的访问端口;\n通过 spec.selector 发现同一命名空间下带有 app=nginx 的 label 的 Pod 作为后端。\n创建命令:\nkubectl apply -f nginx-service.yaml Service作为Pod的负载均衡器,使用 service.spec.selector 字段去匹配Pod,上面示例中,nginx Service将通过 app: nginx 的 label 查找 Pod 作为后端。\n创建 Service 之后,查看 Service 的后端:\nkubectl get svc -o wide kubectl describe svc nginx 在创建 Sercvice 之前,我已经创建了一个带有 app: nginx label 的 Pod,所以可以看到 Service 的 EndPoints 中已经有了一个后端(10.244.1.25)了,Endpoint 也是一种 K8s 资源,可以使用 kubectl get ep 命令查看。\n除了以上使用 yaml 定义 Service 之外,还可以使用以下命令创建 Service:\n# 暴露 Pod kubectl expose pod nginx --name=nginx --port=80 --target-port=80 --protocol=TCP --type=NodePort # 或者 Deployment 管理的 Pod kubectl expose deploy nginx --name=nginx --port=80 --target-port=80 --protocol=TCP --type=LoadBalancer Service 类型 # 通过 Service.spec.type 定义,最常用的三种:ClusterIP(默认),NodePort、LoadBalancer。\nCLusterIP # 指定 type 为 ClusterIP 时,它将被分配一个集群内部的 IP,在集群内部通过访问它来访问后端 Pod,这种 Service 一般只供集群内部访问。\nNodePort # 指定 type 为 NodePort 时,会在所有节点分配一个端口(默认从 30000-32767 )作为 service 的入口,访问方式:\u0026lt;protocol\u0026gt;://\u0026lt;node-ip\u0026gt;:\u0026lt;port\u0026gt;,使用 NodePort 类型时,也会默认分配一个 ClusterIP,除非使用 ClusterIP: None 免除分配。\n特点::\n所有节点都是同一个端口; 端口是有限的,30000-32767 范围内分配,也可以使用 service.spec.ports.nodePort 指定端口,但指定的端口必须在范围内且尚未被占用; 外部访问需要使用到 Node 的 IP。 LoadBalancer # 随机分配一个 LoadBalancer 作为 Service 的入口,一般需要云提供商的支持。请求到达 LoadBalancer 地址后,均衡负载到后端 Pod,使用 LoadBalancer 类型时,也会默认分配一个 ClusterIP,并且也会分配一个 NodePort 端口,实际上来说,LoadBalancer 是基于 NodePort 实现的。\n特点:\n外部网络可以通过 LoadBalancer 的地址和端口访问到集群内 Service 服务。 ExternalName # 创建一个新的 Service 代理到已经存在另外一个 Service,允许跨命名空间。如下,在命令空间 ns-b 下创建 service 转发访问到 ns-a 下的 service。\nkind: Service apiVersion: v1 metadata: name: nginx namespace: ns-b spec: selector: app: nginx type: ExternalName externalName: nginx.ns-a.svc.cluster.local ports: - name: http port: 80 targetPort: 80 如果只是用来创建一个 Service,使用意义不是很大,因为本来就可以直接通过 nginx.ns-a.svc.cluster.local 跨命名空间访问,但是在结合 Ingress 时有一定的使用意义,因为 ingress 无法跨命名空间转发 Service。\n访问 Service # 通过 ClusterIP 访问,限集群内部访问; 通过 Service Name 访问:如果在同一命名空间,可以直接使用服务名称 \u0026lt;service-name\u0026gt; 访问,如果不在同一命名空间,使用服务名称和命名空间名称 \u0026lt;service-name\u0026gt;.\u0026lt;namespace-name\u0026gt; 或 \u0026lt;service-name\u0026gt;.\u0026lt;namespace-name\u0026gt;.svc.cluster.local 访问,集群外可访问; 通过节点端口(NodePort)访问,集群外可访问; 通过 ExternalIP(一般是 LoadBalancer 的地址)访问,集群外可访问。 « 了解 Secret\n» Telepresence\n"},{"id":161,"href":"/kubernetes/telepresence/","title":"Telepresence","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Telepresence\nTelepresence # Telepresence是一款\n« 了解 Service\n» Kubernetes 0-1 使用preStop优雅终止Pod\n"},{"id":162,"href":"/kubernetes/terminate-pod-gracefully/","title":"Terminate Pod Gracefully","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 使用preStop优雅终止Pod\nKubernetes 0-1 使用preStop优雅终止Pod # Kubernetes允许Pod终止之前,执行自定义逻辑。\n字段定义 # 字段定义:pod.spec.containers.lifecycle.preStop\n$ kubectl explain pod.spec.containers.lifecycle.preStop KIND: Pod VERSION: v1 RESOURCE: preStop \u0026lt;Object\u0026gt; DESCRIPTION: PreStop is called immediately before a container is terminated due to an API request or management event such as liveness/startup probe failure, preemption, resource contention, etc. The handler is not called if the container crashes or exits. The reason for termination is passed to the handler. The Pod\u0026#39;s termination grace period countdown begins before the PreStop hooked is executed. Regardless of the outcome of the handler, the container will eventually terminate within the Pod\u0026#39;s termination grace period. Other management of the container blocks until the hook completes or until the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks Handler defines a specific action that should be taken FIELDS: exec \u0026lt;Object\u0026gt; One and only one of the following should be specified. Exec specifies the action to take. httpGet \u0026lt;Object\u0026gt; HTTPGet specifies the http request to perform. tcpSocket \u0026lt;Object\u0026gt; TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported 有三种preStop方式:\nexec: httpGet: tcpSocket: 示例 # 使用最简单的exec作示例,详细查看一下exec下需要定义的字段:\n$ kubectl explain pod.spec.containers.lifecycle.preStop.exec KIND: Pod VERSION: v1 RESOURCE: exec \u0026lt;Object\u0026gt; DESCRIPTION: ... FIELDS: command \u0026lt;[]string\u0026gt; Command is the command line to execute inside the container, the working directory for the command is root (\u0026#39;/\u0026#39;) in the container\u0026#39;s filesystem. The command is simply exec\u0026#39;d, it is not run inside a shell, so traditional shell instructions (\u0026#39;|\u0026#39;, etc) won\u0026#39;t work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. 接下来按照字段释义,直接定义一个Pod:\n$ kubectl apply -f pod.yaml # pod.yaml文件内容: apiVersion: v1 kind: Pod metadata: name: busybox namespace: default spec: containers: - name: busybox image: busybox command: [\u0026#34;/bin/sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;sleep 10m\u0026#34;] lifecycle: preStop: exec: command: [ \u0026#34;/bin/sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;echo this pod is stopping. \u0026gt; /stop.log \u0026amp;\u0026amp; sleep 10s\u0026#34;, ] 删除pod:\nkubectl delete pod busybox 在新终端窗口(因为删除pod会占用终端窗口)获取pod内文件内容,需要在pod完全删除之前(10s内,也可以将该值设置稍长一点):\n$ kubectl exec busybox -c busybox -- cat /stop.log # 可以得到日志内容 this pod is stopping. 这说明,preStop确实生效了。\n使用场景 # 你的请求已经到达了当前Pod,硬终止会导致请求失败,我们希望已经到达了当前Pod的请求处理完成再将其停止掉,尽可能避免请求失败; Pod已经本身已经注册到了服务中心,我们希望在Pod停止之前,主动向服务注册中心通知下线; 结束语 # 与preStop相对应,有一个postStart的概念,在容器创建成功后执行,可用于初始化资源,准备环境等;\n如果preStop与postStart执行失败,将会杀死容器。所以作为钩子函数,应该尽量保证它们是轻量的;\nPod的终止过程:\n删除Pod =\u0026gt; Pod被标记为Terminating状态 =\u0026gt; Service移除该Pod的endpoint =\u0026gt; kubelet甄别Terminating状态的pod,执行pod的preStop钩子 =\u0026gt; 如果执行preStop超时(grace period) ,kubelet发送SIGTERM并等待2秒 =\u0026gt; \u0026hellip;\n« Telepresence\n» Terraform\n"},{"id":163,"href":"/kubernetes/terraform/","title":"Terraform","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Terraform\nTerraform # 创建ec2同时安装应用的三种方式 # Mode 1: userdata # 需要shell脚本文件install_nginx.sh resource \u0026#34;aws_instance\u0026#34; \u0026#34;demo\u0026#34; { # ... # Mode 1: userdata user_data = \u0026#34;${file(\u0026#34;../templates/install_nginx.sh\u0026#34;)}\u0026#34; # ... } Mode 2: remote-exec # 需要连接主机,connection; 密钥文件xxx.pem resource \u0026#34;aws_instance\u0026#34; \u0026#34;demo\u0026#34; { # ... # Mode 2: remote-exec connection { host = \u0026#34;${self.private_ip}\u0026#34; private_key = \u0026#34;${file(\u0026#34;xxx.pem\u0026#34;)}\u0026#34; user = \u0026#34;${var.ansible_user}\u0026#34; } provisioner \u0026#34;remote-exec\u0026#34; { inline = [ \u0026#34;sudo apt-get update\u0026#34;, \u0026#34;sudo apt-get install -y nginx\u0026#34;, \u0026#34;sudo service nginx start\u0026#34; ] } # ... } Mode 3: local-exec with Ansible # 需要执行Terraform命令的主机安装Ansible 密钥文件xxx.pem 额外的ansible-playbook文件,目录../playbooks/install_nginx.yaml 实战过程中发现,使用Ansible在主机远程为ec2安装nginx时,需要等待一定时间(sleep 30)才会成功,猜测可能是等待ec2完全创建成功并运行(安装python,毕竟Ansible对host的唯一要求就是python)之后才可以使用Ansible,这可能会成为一个坑。: resource \u0026#34;aws_instance\u0026#34; \u0026#34;demo\u0026#34; { # ... # Mode 3: local-exec with ansible-playbook provisioner \u0026#34;local-exec\u0026#34; { command = \u0026lt;\u0026lt;EOT sleep 30; \u0026gt;nginx.ini; echo \u0026#34;[nginx]\u0026#34; | tee -a nginx.ini; chmod 600 xxx.pem; echo \u0026#34;${self.private_ip} ansible_user=${var.ansible_user} ansible_ssh_private_key_file=xxx.pem\u0026#34; | tee -a nginx.ini; export ANSIBLE_HOST_KEY_CHECKING=False; ansible-playbook -u ${var.ansible_user} --private-key xxx.pem -i nginx.ini ../playbooks/install_nginx.yaml EOT } # ... } « Kubernetes 0-1 使用preStop优雅终止Pod\n» Velero + Minio 备份与恢复\n"},{"id":164,"href":"/kubernetes/velero-minio-backup-restore-volume/","title":"Velero Minio Backup Restore Volume","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Velero + Minio 备份与恢复\nVelero + Minio 备份与恢复 # 安装 Minio # docker run -d --name minio \\\\ -p 9000:9000 \\\\ -p 9001:9001 \\\\ -e MINIO_ROOT_USER=minio \\\\ -e MINIO_ROOT_PASSWORD=minio \\\\ -v /minio-data:/data \\\\ quay.io/minio/minio:latest server /data --console-address \u0026#34;:9001\u0026#34; 创建 Bucket # 设置 Region # 点击保存后,会出现一个横幅,点击横幅上的 Restart 即可。\n创建 AccessKey # 保存 AccessKey 和 SecretKey 到文件 credentials-velero:\n[default] aws_access_key_id = \u0026lt;access_key\u0026gt; aws_secret_access_key = \u0026lt;secret_key\u0026gt; 安装 Velero CLI # # linux wget \u0026lt;https://github.com/vmware-tanzu/velero/releases/download/v1.11.1/velero-v1.11.1-linux-amd64.tar.gz\u0026gt; tar -xvf velero-v1.11.1-linux-amd64.tar.gz mv velero-v1.11.1-linux-amd64/velero /usr/local/bin # completion bash source /usr/share/bash-completion/bash_completion echo \u0026#39;source \u0026lt;(velero completion bash)\u0026#39; \u0026gt;\u0026gt;~/.bashrc velero completion bash \u0026gt;/etc/bash_completion.d/velero echo \u0026#39;alias v=velero\u0026#39; \u0026gt;\u0026gt;~/.bashrc echo \u0026#39;complete -F __start_velero v\u0026#39; \u0026gt;\u0026gt;~/.bashrc # completion zsh source \u0026lt;(velero completion zsh) echo \u0026#39;alias v=velero\u0026#39; \u0026gt;\u0026gt;~/.zshrc echo \u0026#39;complete -F __start_velero v\u0026#39; \u0026gt;\u0026gt;~/.zshrc 在集群中安装 Velero # velero install \\ --provider aws \\ --plugins velero/velero-plugin-for-aws:main \\ --use-node-agent=true \\ --use-volume-snapshots=false \\ --bucket \u0026lt;your_minio_bucket\u0026gt; \\ --secret-file ./credentials-velero \\ --backup-location-config \\ region=\u0026lt;your_minio_region\u0026gt;,s3ForcePathStyle=\u0026#34;true\u0026#34;,s3Url=https://\u0026lt;your_minio_server\u0026gt;:9000 1、./credentials-velero 文件中保存 minio 的 AccessKey 和 SecretKey 内容;\n2、修改 bucket、region、和 minio 的服务地址。\n备份 # velero backup create mysql-backup --selector app=mysql --default-volumes-to-fs-backup 还原 # velero restore create --from-backup mysql-backup « Terraform\n» 了解 Volume\n"},{"id":165,"href":"/kubernetes/volume-understood/","title":"Volume Understood","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 了解 Volume\n了解 Volume # 我们知道容器与容器之间是隔离的,有独立的文件系统。并且存储的文件性质时临时的,当容器被销毁时,容器内的文件一并被清除。\n在 Pod 内可能运行着多个容器,这可能需要容器共享文件。在 K8s 中,抽象除了 Volume 的概念来满足这种需求。\nVolume 介绍 # 在 Docker 中,也有 Volume 的概念,它是将容器内某文件目录挂载到宿主机的目录。\n在 K8s 中,Volume 供 Pod 内的容器使用,一个容器可以使用多个 Volume,同一 Pod 内的多个容器可以同时使用一个 Volume,实现文件共享,或数据持久存储。\n容器与 Volume 的简单关系:\nVolume 定义 # 定义在 pod.spec.container 属性下:\nkind: Pod ... spec: container: ... volumeMounts: - mountPath: \u0026lt;path\u0026gt; name: \u0026lt;volume-name\u0026gt; subPath: \u0026lt;volume-path\u0026gt; volumes: - name: \u0026lt;volume-name\u0026gt; \u0026lt;volume-type\u0026gt;: ... mountPath: 容器内的目录,如果不存在则创建该目录 subPath:默认会将 mountPath 直接映射到 volume 的根目录,使用 subpath 映射到 volume 特定的目录。 Volume 类型 # Volume 有多种类型,有的可以直接在集群中使用,有的则需要第三方服务或云平台的支持。简单罗列几种常见类型,更多了类型参考: https://kubernetes.io/zh/docs/concepts/storage/volumes\nemptyDir # 如果指定 Volume 类型为 emptyDir,它会在 Pod 刚被调度时,在被调度到的节点上创建起来。卷初始时时空的,里面没有文件,Pod 内的容器可以在卷中写入文件。多个容器绑定的 Volume 如果存在目录相同,则目录可以被共享读写。\n如果 Pod 在该节点被销毁,那么 emptyDir 也将被永久删除。Pod 内的容器崩溃或重启是不会影响 emptyDir 的生命周期的。\n由于 emptyDir 的生命周期受Pod影响,emptyDir 适合的使用场景也会受限,emptyDir 适合使用在数据临时计算,临时缓存等,不适合存储配置信息或持久化数据。\n使用示例:\napiVersion: v1 kind: Pod metadata: name: nginx-redis spec: containers: - image: nginx name: nginx volumeMounts: - name: dir mountPath: /shareDir - image: redis name: redis volumeMounts: - name: dir mountPath: /shareDir volumes: - name: dir emptyDir: {} 如果在 K8s 中创建上面这个 Pod,容器都运行起来后,分别进入 nginx 容器和 redis 容器,都可以在根目录下看到 shareDir 目录,并且在一个容器中写入了文件,另一个容器也可以读取到。当删除这个 Pod,再重新创建后,之前写入的文件会消失。\nemptyDir 可以指定存储介质,默认是使用节点的磁盘创建起来的,也可以指定介质为内存,这种读写更快:\nvolumes: - name: dir emptyDir: medium: Memory hostPath # hostPath 卷指向节点文件系统上的特定文件或目录,如果多个 Pod 运行在同一节点,并且指向相同的卷路径,则他们看的文件是相同的。\nhostPath 类型的 Volume 的生命周期不受Pod限制,Pod 被删除,hostPath 下的文件仍然保留。由于 hostPath 数据只会保留在节点上,当 Pod 被重新调度到其他节点时,相对来说数据是丢失的。一般可以使用 hostPath 作为 DaemonSet(每个匹配节点都调度一个 Pod)管理的 Pod 的 Volume。\n使用示例:\napiVersion: v1 kind: Pod metadata: name: redis spec: containers: - image: redis name: redis volumeMounts: - name: dir mountPath: /data volumes: - name: dir hostPath: path: /vol/redis/data type: DirectoryOrCreate 当我们创建以上 Pod 资源,容器运行起来之后,则会在 Pod 所调度的宿主机上创建 /vol/redis/data 目录。如果我们删除Pod,宿主机上的 /vol/redis/data 目录不会被删除。\nhostPath 的 type 类型,默认为空,直接使用 path,其他 type 使用时会检查和初始化 path:\nDirectoryOrCreate,如果 hostPath 定义的 path 目录不存在,则创建目录 Directory,hostPath 定义的 path 目录已经存在,否则会异常 FileOrCreate,如果 hostPath 定义 的path 文件不存在,则创建文件,如果前缀目录不存在也会报错,需要先使用 DirectoryOrCreate 创建目录 File,hostPath 定义的 path 文件已经存在,否则会异常 persistentVolumeClaim # awsElasticBlockStore # 使用 aws 的存储卷作为 Pod 的 Volume,它可以被多个 Pod 共同使用,并且它的生命周期不受 Pod 限制,当 Pod 被销毁,只是取消了绑定关系,存储不会被删除。\n使用 awsElasticBlockStore 作为 Volume,需要提前在 aws 中创 volume,获取到 volumeID,然后 Pod 的 Volume 指定awsElasticBlockStore 的 volumeID 即可。\n使用 aws-cli 创建 aws 中的 volume:\naws ec2 create-volume --availability-zone=ap-southeast-1a --size=2 --volume-type=gp2 以上命令完成会输出 volumeID。\n使用 awsElasticBlockStore 作为 Pod 的 Volume 简单示例,前提我们已经获取到 了volumeID 为 vol-07afc8d24f8f08d2a:\napiVersion: v1 kind: Pod metadata: name: redis labels: name: redis spec: containers: - name: redis image: redis volumeMounts: - mountPath: /data name: redis-data volumes: - name: redis-data awsElasticBlockStore: volumeID: vol-07afc8d24f8f08d2a 当我们创建以上 Pod 资源,容器运行起来之后,我们进入 redis 容器,使用 redis-cli往redis 中写入一个key-value后,立即退出容器,删除 Pod 并重新创建后,再次进入容器,是依然可以获取 key-value 的,这说明 Volume 数据没有随 Pod 被删除。\nconfigMap # 存储配置项信息,供 Volume 使用,后续可能会单独介绍它的使用。\nSecret # 存储敏感信息的配置,供 Volume 使用,后续可能会单独介绍它的使用。\n« Velero + Minio 备份与恢复\n» VPA\n"},{"id":166,"href":"/kubernetes/vpa/","title":"Vpa","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / VPA\nVPA # 安装 # git clone https://github.com/kubernetes/autoscaler.git cd ./autoscaler/vertical-pod-autoscaler ./hack/vpa-up.sh 卸载:\n./hack/vpa-down.sh VPA运行模式 # Auto(默认模式):VPA在pod创建时分配资源请求,并使用首选的更新机制在现有的pod上更新它们。目前,这相当于“重建”(见下文)。一旦pod请求的免费重启(“原位”)更新可用,它就可以被“Auto”模式用作首选的更新机制。注意:VPA的这个特性是实验性的,可能会导致您的应用程序停机。\nRecreate:VPA在pod创建时分配资源请求,并在现有pod上更新它们,当请求的资源与新建议有显著差异时(如果定义了pod中断预算,则考虑到它们),将它们赶出现有pod。这种模式应该很少使用,只有当您需要确保在资源请求更改时重新启动pods时才会使用。否则更喜欢“自动”模式,这可能会利用重新启动免费更新,一旦他们可用。注意:VPA的这个特性是实验性的,可能会导致您的应用程序停机。\nInitial:VPA只在pod创建时分配资源请求,以后不会更改它们。\nOff:VPA不会自动更改pods的资源需求。计算并可以在VPA对象中检查建议。\n示例 # « 了解 Volume\n"},{"id":167,"href":"/linux/","title":"Linux","section":"","content":" 🏠 首页 / Linux\nLinux # certbot-auto 生成证书\nLinux-history 输出附带日期\nLinux 命令\nLinux常用命令\nLinux 启用 crontab 日志\nLinux-安全登录\nshell 命令间隔符\nshell 基础\n使用 SSH Tunnel 连接中间件\ntee 保存 stderr 到文件\nvim 使用\n"},{"id":168,"href":"/linux/certbot-auto-gen-cert/","title":"Certbot Auto Gen Cert","section":"Linux","content":" 🏠 首页 / Linux / certbot-auto 生成证书\ncertbot-auto 生成证书 # 安装 # wget https://dl.eff.org/certbot-auto chmod a+x ./certbot-auto cp ./certbot-auto /usr/local/bin 生成证书 # 条件:\n提前已经将域名解析到本服务器; 本服务器端口 80、443 处于未被占用的状态,如果 web 服务占用了 80 端口,需要临时关闭。 certbot-auto certonly --standalone --email poneding@gmail.com -d test.poneding.com 以上命令执行完成后,将会在 /etc/letsencrypt/live 目录下生成域名证书文件。默认证书有效期为 3 个月。\nnginx 配置证书 # 参考示例:\nserver { listen 80; server_name abc.com; rewrite ^(.*) https://test.poneding.com permanent; } server{ listen 443 ssl default_server; listen [::]:443 ssl default_server; ssl_certificate /etc/letsencrypt/live/test.poneding.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/test.poneding.com/privkey.pem; server_name test.poneding.com; root /web/test.poneding.com/; } 证书续期 # 配置定时任务\n0 3 1 * * certbot-auto renew --pre-hook \u0026#34;systemctl stop nginx\u0026#34; --renew-hook \u0026#34;systemctl start nginx\u0026#34; 参考 # certbot 文档: https://eff-certbot.readthedocs.io/en/stable/index.html » Linux-history 输出附带日期\n"},{"id":169,"href":"/linux/history-with-date/","title":"History With Date","section":"Linux","content":" 🏠 首页 / Linux / Linux-history 输出附带日期\nLinux-history 输出附带日期 # 如果我们在 linux 系统中想看历史的命令记录,我们可以通过 command 命令来获取。\nhistory 输出大概会是下面这种样子,只有简单的 command 列表。\n1 ls 2 top 4 docker ps 5 df 6 ls 那么,如果想知道历史执行的 command 的时间该怎么做呢。\n按照如下步骤,一步一步来。\n首先设置 HISTTIMEFORMAT 变量 $ HISTTIMEFORMAT=\u0026#34;%d/%m/%y %T \u0026#34; # OR $ echo \u0026#39;export HISTTIMEFORMAT=\u0026#34;%d/%m/%y %T \u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.bash_profile 使用 source 命令加载 HISTTIMEFORMAT 变量到当前 shell 命令窗 $ . ~/.bash_profile # OR $ source ~/.bash_profile 再次运行 history 命令,已经可以输出附带执行时间的 history 了。 1 root 2020/02/18 11:28:19 ls 2 root 2020/02/18 11:28:21 top 4 root 2020/02/18 11:28:58 docker ps 5 root 2020/02/18 11:34:09 df 6 root 2020/02/18 11:34:15 ls « certbot-auto 生成证书\n» Linux 命令\n"},{"id":170,"href":"/linux/linux-commands/","title":"Linux Commands","section":"Linux","content":" 🏠 首页 / Linux / Linux 命令\nLinux 命令 # Linux 命令大全\ncat # cat命令用于把档案串连接后传到基本输出(萤幕或加 \u0026gt; fileName 到另一个档案)\n使用权限 # 所有使用者\n语法格式 # cat [-AbeEnstTuv] [--help] [--version] fileName 参数说明 # -n 或 \u0026ndash;number 由 1 开始对所有输出的行数编号\n-b 或 \u0026ndash;number-nonblank 和 -n 相似,只不过对于空白行不编号\n-s 或 \u0026ndash;squeeze-blank 当遇到有连续两行以上的空白行,就代换为一行的空白行\n-v 或 \u0026ndash;show-nonprinting\n实例 # 把 textfile1 的档案内容加上行号后输入 textfile2 这个档案里\ncat -n textfile1 \u0026gt; textfile2 把 textfile1 和 textfile2 的档案内容加上行号(空白行不加)之后将内容附加到 textfile3 里。\ncat -b textfile1 textfile2 \u0026gt;\u0026gt; textfile3 清空/etc/test.txt档案内容\ncat /dev/null \u0026gt; /etc/test.txt cat 也可以用来制作镜像文件。例如要制作软碟的像文件,将软碟放好后打\ncat /dev/fd0 \u0026gt; OUTFILE 相反的,如果想把 image file 写到软碟,请打\ncat IMG_FILE \u0026gt; /dev/fd0 写入多行到文件:\ncat \u0026lt;\u0026lt; EOF \u0026gt; hello.txt Hello World EOF 注:\n\\1. OUTFILE 指输出的镜像文件名。 \\2. IMG_FILE 指镜像文件。 \\3. 若从镜像文件写回 device 时,device 容量需与相当。 \\4. 通常用在制作开机磁片。 chattr # Linux chattr命令用于改变文件属性。\n这项指令可改变存放在ext2文件系统上的文件或目录属性,这些属性共有以下8种模式:\na:让文件或目录仅供附加用途。 b:不更新文件或目录的最后存取时间。 c:将文件或目录压缩后存放。 d:将文件或目录排除在倾倒操作之外。 i:不得任意更动文件或目录。 s:保密性删除文件或目录。 S:即时更新文件或目录。 u:预防以外删除。 语法 # chattr [-RV][-v\u0026lt;版本编号\u0026gt;][+/-/=\u0026lt;属性\u0026gt;][文件或目录...] 参数 # -R 递归处理,将指定目录下的所有文件及子目录一并处理。\n-v\u0026lt;版本编号\u0026gt; 设置文件或目录版本。\n-V 显示指令执行过程。\n+\u0026lt;属性\u0026gt; 开启文件或目录的该项属性。\n-\u0026lt;属性\u0026gt; 关闭文件或目录的该项属性。\n=\u0026lt;属性\u0026gt; 指定文件或目录的该项属性。\n实例 # 用chattr命令防止系统中某个关键文件被修改:\nchattr +i /etc/resolv.conf lsattr /etc/resolv.conf 会显示如下属性\n----i-------- /etc/resolv.conf 让某个文件只能往里面追加数据,但不能删除,适用于各种日志文件:\nchattr +a /var/log/messages chgrp # Linux chgrp命令用于变更文件或目录的所属群组。\n在UNIX系统家族里,文件或目录权限的掌控以拥有者及所属群组来管理。您可以使用chgrp指令去变更文件与目录的所属群组,设置方式采用群组名称或群组识别码皆可。\n语法 # chgrp [-cfhRv][--help][--version][所属群组][文件或目录...] 或 chgrp [-cfhRv][--help][--reference=\u0026lt;参考文件或目录\u0026gt;][--version][文件或目录...] 参数说明 # -c或\u0026ndash;changes 效果类似\u0026quot;-v\u0026quot;参数,但仅回报更改的部分。\n-f或\u0026ndash;quiet或\u0026ndash;silent 不显示错误信息。\n-h或\u0026ndash;no-dereference 只对符号连接的文件作修改,而不更动其他任何相关文件。\n-R或\u0026ndash;recursive 递归处理,将指定目录下的所有文件及子目录一并处理。\n-v或\u0026ndash;verbose 显示指令执行过程。\n\u0026ndash;help 在线帮助。\n\u0026ndash;reference=\u0026lt;参考文件或目录\u0026gt; 把指定文件或目录的所属群组全部设成和参考文件或目录的所属群组相同。\n\u0026ndash;version 显示版本信息。\n实例 # 实例1:改变文件的群组属性:\nchgrp -v bin log2012.log 输出:\n[root@localhost test]# ll ---xrw-r-- 1 root root 302108 11-13 06:03 log2012.log [root@localhost test]# chgrp -v bin log2012.log \u0026ldquo;log2012.log\u0026rdquo; 的所属组已更改为 bin\n[root@localhost test]# ll ---xrw-r-- 1 root bin 302108 11-13 06:03 log2012.log 说明: 将log2012.log文件由root群组改为bin群组\n实例2:根据指定文件改变文件的群组属性\nchgrp --reference=log2012.log log2013.log 输出:\n[root@localhost test]# ll ---xrw-r-- 1 root bin 302108 11-13 06:03 log2012.log -rw-r--r-- 1 root root 61 11-13 06:03 log2013.log [root@localhost test]# chgrp --reference=log2012.log log2013.log [root@localhost test]# ll ---xrw-r-- 1 root bin 302108 11-13 06:03 log2012.log -rw-r--r-- 1 root bin 61 11-13 06:03 log2013.log 说明: 改变文件log2013.log 的群组属性,使得文件log2013.log的群组属性和参考文件log2012.log的群组属性相同\nchmod # Linux/Unix 的文件调用权限分为三级 : 文件拥有者、群组、其他。利用 chmod 可以藉以控制文件如何被他人所调用。\n使用权限 : 所有使用者\n语法 # chmod [-cfvR] [--help] [--version] mode file... 参数说明 # mode : 权限设定字串,格式如下 :\n[ugoa...][[+-=][rwxX]...][,...] 其中:\nu 表示该文件的拥有者,g 表示与该文件的拥有者属于同一个群体(group)者,o 表示其他以外的人,a 表示这三者皆是。 + 表示增加权限、- 表示取消权限、= 表示唯一设定权限。 r 表示可读取,w 表示可写入,x 表示可执行,X 表示只有当该文件是个子目录或者该文件已经被设定过为可执行。 -c : 若该文件权限确实已经更改,才显示其更改动作\n-f : 若该文件权限无法被更改也不要显示错误讯息\n-v : 显示权限变更的详细资料\n-R : 对目前目录下的所有文件与子目录进行相同的权限变更(即以递回的方式逐个变更)\n\u0026ndash;help : 显示辅助说明\n\u0026ndash;version : 显示版本\n实例 # 将文件 file1.txt 设为所有人皆可读取 :\nchmod ugo+r file1.txt 将文件 file1.txt 设为所有人皆可读取 :\nchmod a+r file1.txt 将文件 file1.txt 与 file2.txt 设为该文件拥有者,与其所属同一个群体者可写入,但其他以外的人则不可写入 :\nchmod ug+w,o-w file1.txt file2.txt 将 ex1.py 设定为只有该文件拥有者可以执行 :\nchmod u+x ex1.py 将目前目录下的所有文件与子目录皆设为任何人可读取 :\nchmod -R a+r * 此外chmod也可以用数字来表示权限如 :\nchmod 777 file 语法为:\nchmod abc file 其中a,b,c各为一个数字,分别表示User、Group、及Other的权限。\nr=4,w=2,x=1\n若要rwx属性则4+2+1=7; 若要rw-属性则4+2=6; 若要r-x属性则4+1=5。 chmod a=rwx file 和\nchmod 777 file 效果相同\nchmod ug=rwx,o=x file 和\nchmod 771 file 效果相同\n若用chmod 4755 filename可使此程序具有root的权限\nfile # Linux file命令用于辨识文件类型。\n通过file指令,我们得以辨识该文件的类型。\n语法 # file [-beLvz][-f \u0026lt;名称文件\u0026gt;][-m \u0026lt;魔法数字文件\u0026gt;...][文件或目录...] 参数:\n-b 列出辨识结果时,不显示文件名称。 -c 详细显示指令执行过程,便于排错或分析程序执行的情形。 -f\u0026lt;名称文件\u0026gt; 指定名称文件,其内容有一个或多个文件名称呢感,让file依序辨识这些文件,格式为每列一个文件名称。 -L 直接显示符号连接所指向的文件的类别。 -m\u0026lt;魔法数字文件\u0026gt; 指定魔法数字文件。 -v 显示版本信息。 -z 尝试去解读压缩文件的内容。 [文件或目录\u0026hellip;] 要确定类型的文件列表,多个文件之间使用空格分开,可以使用shell通配符匹配多个文件。 实例 # 显示文件类型:\n[root@localhost ~]# file install.log install.log: UTF-8 Unicode text [root@localhost ~]# file -b install.log \u0026lt;== 不显示文件名称 UTF-8 Unicode text [root@localhost ~]# file -i install.log \u0026lt;== 显示MIME类别。 install.log: text/plain; charset=utf-8 [root@localhost ~]# file -b -i install.log text/plain; charset=utf-8 显示符号链接的文件类型\n[root@localhost ~]# ls -l /var/mail lrwxrwxrwx 1 root root 10 08-13 00:11 /var/mail -\u0026gt; spool/mail [root@localhost ~]# file /var/mail /var/mail: symbolic link to `spool/mail\u0026#39; [root@localhost ~]# file -L /var/mail /var/mail: directory [root@localhost ~]# file /var/spool/mail /var/spool/mail: directory [root@localhost ~]# file -L /var/spool/mail /var/spool/mail: directory find # Linux find 命令用来在指定目录下查找文件。任何位于参数之前的字符串都将被视为欲查找的目录名。如果使用该命令时,不设置任何参数,则 find 命令将在当前目录下查找子目录与文件。并且将查找到的子目录和文件全部进行显示。\n语法 # find path -option [ -print ] [ -exec -ok command ] {} ; 参数说明 :\nfind 根据下列规则判断 path 和 expression,在命令列上第一个 - ( ) , ! 之前的部份为 path,之后的是 expression。如果 path 是空字串则使用目前路径,如果 expression 是空字串则使用 -print 为预设 expression。\nexpression 中可使用的选项有二三十个之多,在此只介绍最常用的部份。\n-mount, -xdev : 只检查和指定目录在同一个文件系统下的文件,避免列出其它文件系统中的文件\n-amin n : 在过去 n 分钟内被读取过\n-anewer file : 比文件 file 更晚被读取过的文件\n-atime n : 在过去 n 天过读取过的文件\n-cmin n : 在过去 n 分钟内被修改过\n-cnewer file :比文件 file 更新的文件\n-ctime n : 在过去 n 天过修改过的文件\n-empty : 空的文件-gid n or -group name : gid 是 n 或是 group 名称是 name\n-ipath p, -path p : 路径名称符合 p 的文件,ipath 会忽略大小写\n-name name, -iname name : 文件名称符合 name 的文件。iname 会忽略大小写\n-size n : 文件大小 是 n 单位,b 代表 512 位元组的区块,c 表示字元数,k 表示 kilo bytes,w 是二个位元组。-type c : 文件类型是 c 的文件。\nd: 目录\nc: 字型装置文件\nb: 区块装置文件\np: 具名贮列\nf: 一般文件\nl: 符号连结\ns: socket\n-pid n : process id 是 n 的文件\n你可以使用 ( ) 将运算式分隔,并使用下列运算。\nexp1 -and exp2\n! expr\n-not expr\nexp1 -or exp2\nexp1, exp2\n实例 # 列出给定目录(base_path)下所有的文件和子目录:\nfind base_path -print 补充:根据文件名和正则表达式进行搜索,使用选项 -name 或 -iname (忽略大小写):\nfind base_path -name ‘xxx’ -print find base_path -iname ’xxx‘ -print 否定参数,可以用 !排除所指定到的模式:\n此处将打印出除 txt 文本文件外的的所有文件。\n基于目录深度的搜索:\nfind 命令指定遍历完所有的子目录。使用 -maxdepth 和-mindefth 可以限制 find 命令遍历的目录深度,并且 find 命令默认不搜索符号链接,可以用 -L 选项改变这种行为。\n例如-maxdepth的参数为1时,只匹配当前目录下。\n-mindepth 的参数代表了开始进行匹配的目录到 base_path 的最短距离。\n基于文件类型搜索:\n使用 -type 可以指定搜索的文件类型,linux/unix 将所有的的一切都视为文件(文件类型有:普通文件 f,目录 d,符号链接 l,字符设备 c,块设备 b,套接字 s,FIFO-p),使用 -type 选项我们能够对文件类型进行过滤。\n此处就会只匹配出特定项下的所有普通文件,和目录。\n根据文件的时间戳进行搜索:\nLinux/Unix 文件系统中的每一个文件都有三种时间戳,访问时间(-atime),修改时间(-mtime),变化时间(-ctime),单位为天数,用整数指定,数字前加上+,表示大于这个时间;加上-,表示小于这个天数;不加表示刚好这个天数。\n此处的文件是我在进行截图之前才创建的,访问,修改,变化时间均小于一天。\n当然相应的用分钟作为单位就可以用选项 (-amin)(-mmin)(-cmin),如下我们测试修改时间\n基于文件大小的搜索:\nfind 提供了指定文件大小的单位选项进而搜索符合大小文件的功能,这个搜索也常常会让用户感到非常舒服(b:块, c:字节, w:字, k:千字节, M:兆字节, G:吉字节)。\n在搜索之前我们先用 ls(list)指令来查看下当前目录下的文件信息:\n信息的第五列就是各文件目录的大小(字节),我们通过指定匹配条件来搜索:\n经过测试,在开始目录下,文件类型为普通目录,文件大小大于30个字节的文件就是zl.txt了\n基于文件权限和所有权的匹配:\n-perm 选项指定了 find 指匹配指定权限的文件,参数为文件对应的权限码。\n我们仍然可参考⑥中的所有文件信息的第一列,此处需要掌握一定关于文件权限的知识。如下我们查找权限为 644 的普通文件,即用户可读写,组用户可读,其他可读。\n也可以用选项 -user,匹配指定用户所拥有的文件,参数为用户名或者UID\n利用find执行相应操作:\n比如删除文件,使用 -delete 选项;删除测试目录下所有的 .txt 普通文件\n还可以利用 -exec 选项结合其他命令对文件进行更高效的操作,更改文件的所属权,复制文件等,find 命令使用一对花括号 {} 代表文件名,对于每一个匹配到的文件,find 命令会将{}替换成相应的文件名; 如果 -exec 的命令有多个参数时,需要注意结尾使用 \u0026quot; ; \u0026quot; 或者 \u0026ldquo;+\u0026quot;,前者表示进行转义,不然系统会以为是 find 命令的结尾。\n我们将测试目录下的所有的 .txt 文件由用户 lihongbo 转换到用户 litao999,我们必须以 root 用户进行此操作,chown 用于更改权限:\n指定 find 跳过特定的目录:\n使用 -prune 选项可以跳过我们在搜寻的的一些明显我们不需要的目录\n跳过了 ./test1 目录\n需要指出的是:选项出现的先后次序我们也应该考虑到内,因为它会影响到整条命令的执行效率。\necho # curl # 返回httpStatusCode\ncurl -I -m 10 -o /dev/null -s -w %{http_code} http://localhost/version -I 仅测试HTTP头 -m 10 最多查询10s -o /dev/null 屏蔽原有输出信息 -s silent 模式,不输出任何东西 -w %{http_code} 控制额外输出 date # 查看今天是当年中的第几天\ndate \u0026#39;+%j\u0026#39; 按照“年-月-日 小时:分钟:秒”的格式查看当前系统时间\ndate \u0026#39;+%Y-%m-%d %H:%M:%S\u0026#39; 将系统的当前时间设置为2017年9月1日8点30分\ndate -s \u0026#39;20170901 8:30:00\u0026#39; source 命令 # source命令将指定函数文件加载到某个shell脚本文件或当前命令窗口。\n语法 # 基本使用语法如下:\nsource \u0026lt;filename\u0026gt; [arguments] source functions.sh source /path/to/functions.sh arg1 arg2 source functions.sh WWWROOT=/apache.jail PHPROOT=/fastcgi.php_jail 示例 # 创建一个shell脚本文件mylib.sh,如下:\n#!/bin/bash JAIL_ROOT=/www/httpd is_root(){ [$(id -u) -eq 0] \u0026amp;\u0026amp; return $TRUE || return $FALSE } function to_lower() { local str=\u0026#34;$@\u0026#34; local output output=$(tr \u0026#39;[A-Z]\u0026#39; \u0026#39;[a-z]\u0026#39;\u0026lt;\u0026lt;\u0026lt;\u0026#34;${str}\u0026#34;) echo $output } 再创建一个shell脚本文件test.sh,在该文件中,可以通过使用如下的语法使用mylib.sh的JAIL_ROOT变量,调用is_root函数:\n#!/bin/bash # 使用source命令加载mylib.sh source mylib.sh echo \u0026#34;JAIL_ROOT is set to $JAIL_ROOT\u0026#34; # 调用is_root函数,并打印结果 is_root \u0026amp;\u0026amp; echo \u0026#34;You are logged in as root.\u0026#34; || echo \u0026#34;You are not logged in as root.\u0026#34; 运行该文件:\nchmod +x test.sh ./test.sh 运行结果如下:\nJAIL_ROOT is set to /www/httpd You are not logged in as root. 在当前命令直接运行\nsource mylib.sh echo $JAIL_ROOT 运行结果:\n/www/httpd « Linux-history 输出附带日期\n» Linux常用命令\n"},{"id":171,"href":"/linux/linux-common-commands/","title":"Linux Common Commands","section":"Linux","content":" 🏠 首页 / Linux / Linux常用命令\nLinux常用命令 # 1.文件和目录相关 # cd .. 切换到上一级目录 cd ../.. 切换到上两级目录 cd [dir] 进入 dir 目录 cd 进入个人的主目录 cd ~[username] 进入个人的主目录 cd - 进入上次目录 pwd 查看目录路径 ls 查看当前目录下的目录和文件(不包含隐藏目录或文件) ls -a 查看当前目录下的所有目录和文件 ls -F 查看当前目录下的文件 (不包含目录和隐藏文件) ls -l 查看文件和目录的详细资料 ls *[0-9]* 查看包含字符的文件和目录 mkdir dir1 创建目录 mkdir dir1 dir2 mkdir -p dir1/dir1/dir1 创建一个目录树 rm -f file1 删除文件 rmdir dir1 删除空 rm -rf dir1 删除包含内容的目录 rm -rf dir1 dir2 删除多个目录 mv dir1 dir2 重命名或移动一个目录(看 dir2 是否存在) cp file1 file2 复制文件 cp dir/* . 复制某目录下所有内容到当前目录 cp -a dir1/dir2 . 复制目录下所有内容到当前目录 cp -a dir1 dir2 复制一个目录 ln -s dir1|file1 link1 创建文件或目录 link 的软件链接 ln dir1|file1 link1 创建文件或目录 link 的物理链接,也叫硬链接 [ 软链接和物理链接的区别: 1. 软链接可以用于目录和文件,物理链接只能用于文件; 2. 源文件删除后,物理链接仍然可以打开访问(更像复制),软链接则已经损坏 ] find /home/[user] -name 某目录下搜索文件 find ~ -iname \u0026#39;*test*\u0026#39; # iname 忽略大小写 zip file1.zip file1 file2 dir1 将文件和目录压缩成 zip 文件(dir 不包含内容) zip -r file1.zip file1 file2 dir1 将文件和目录压缩成 zip 文件(dir 包含内容) unzip file1.zip 解压 zip 文件 tar -zxvf ***.tar.gz cat file1 查看一个文件 tac file1 从最后一行开始反向查看一个文件的内容 more file1 查看一个长文件的内容 less file1 类似于 \u0026#39;more\u0026#39; 命令,但是它允许在文件中和正向操作一样的反向操作 head -2 file1 查看一个文件的前两行 tail -2 file1 查看一个文件的最后两行 grep hello file1 查询关键字 ‘hello’ dump -0aj -f /tmp/home0.bak /home 制作一个 \u0026#39;/home\u0026#39; 目录的完整备份 dump -1aj -f /tmp/home0.bak /home 制作一个 \u0026#39;/home\u0026#39; 目录的交互式备份 vi file1 //编辑文件 # lsof (https://www.cnblogs.com/sparkbj/p/7161669.html) lsof # 列出所有打开的文件 lsof /path/file # 查看谁在使用某个文件 losf -u username # 查看某用户打开的文件 2.系统 # date 显示当前时间 shutdown -h now 关闭系统 reboot 重启 logout 注销 groupadd group_name 创建一个新用户组 groupdel group_name 删除一个用户组 groupmod -n new_group_name old_group_name 重命名一个用户组 useradd -c \u0026#34;Name Surname \u0026#34; -g admin -d /home/user1 -s /bin/bash user1 创建一个属于 \u0026#34;admin\u0026#34; 用户组的用户 usermod -a -G sudo dp 将用户添加到sudo组 useradd user1 创建一个新用户 userdel -r user1 删除一个用户 ( \u0026#39;-r\u0026#39; 排除主目录) usermod -c \u0026#34;User FTP\u0026#34; -g system -d /ftp/user1 -s /bin/nologin user1 修改用户属性 passwd 修改密码 passwd user1 修改一个用户的口令 (只允许root执行) # 以下两个命令可用于查看user属于哪个组 id user1 groups user1 # screen screen -dmS dp screen -ls screen -rd dp # 查看linux系统 cat /proc/version uname -a lsb_release -a (首选) 3.技巧 # # 上行命令的最后一个参数 !$ $ mv /path/to/wrongfile /some/other/place mv: cannot stat \u0026#39;/path/to/wrongfile\u0026#39;: No such file or directory $ mv /path/to/rightfile !$ # 上行命令的第 n 个参数,0,1,2,..,n !:n $ tar -cvf afolder afolder.tar # 写反参数 tar: failed to open $ !:0 !:1 !:3 !:2 tar -cvf afolder.tar afolder # 上行命令参数范围 $ grep \u0026#39;(ping|pong)\u0026#39; afile # 写错 egrep # Linux 下 grep 显示前后几行信息 # 标准 unix/linux 下的 grep 通过下面參数控制上下文 grep -C 5 foo file 显示 file 文件里匹配 foo 字串那行以及上下 5 行 grep -B 5 foo file 显示 foo 及前 5 行 grep -A 5 foo file 显示 foo 及后 5 行 $ egrep !:1-$ egrep \u0026#39;(ping|pong)\u0026#39; afile ping # 前一个命令结果过滤 $ ps -ef | grep nginx # 查看端口 lsof -i:[port] netstat -tunlp |grep [port] # 远程 sudo ssh -i xxx.pem ubuntu@192.168.1.1 # 远程直接进入某目录 sudo ssh -i xxx.pem ubuntu@192.168.1.1 -t \u0026#39;/some/path; bash --login\u0026#39; # linux 系统间文件复制 sudo scp -i ~/dp/k8s.dp.io/.ssh/id_rsa /home/ubuntu/hello.txt admin@bastion-k8s-dp-com-qcojf8-1198362181.ap-southeast-1.elb.amazonaws.com:~/ # shell 文件出错即退出 #!/usr/bin/env bash set -e -o pipefail # 查看系统是 32 位还是 64 位 uname -a # 退出 telnet ctrl+],然后输入 quit # base64 编码\u0026amp;反编码 echo -n \u0026#34;JayChou\u0026#34; | base64 echo -n SmF5Q2hvdQ== | base64 --decode apt # apt upgrade apt update [-y] apt install [pkg] [-y] apt remove/autoremove [pkg] [-y] apt purge CentOS # sudo dhclient sudo yum update -y su root vi /etc/sudoers # root ALL=(ALL) ALL 后添加 xxx ALL=(ALL) ALL # 或者 xxx ALL=(ALL) NOPASSWD: ALL (不用密码) # wq!退出 ip addr cd /etc/sysconfig/network-scripts/ ls sudo vi ifcfg-ens33 # ONBOOT=no 改成yes sudo ifup ens33 # 查看文件大小 ls -ll ls -lh # 查看目录大小 du -sh * du -sh /path/* 虚拟机镜像文件:\nCentOS7\n下载地址: http://mirrors.aliyun.com/centos/7/isos/x86_64/CentOS-7-x86_64-DVD-2003.iso\nKitematic # Download: https://github.com/docker/kitematic/releases\ninstall on ubutnu:\nsudo dpkg -i Kitematic-0.17.11_amd64.deb sudo groupadd docker sudo usermod -aG docker $USER reboot ubutnu, then using Kitematic.\nsed替换文本 # 比如 hello.txt 文本中内容如下:\nhello, Name! 现使用真实 Name 替换掉 #Name#。\nexport Name=JayChou sed -i \u0026#34;s/Name/$Name/g\u0026#34; hello.txt 但是如果替换的文本中包含 / 的话,那么上面这个命令会失败:sed: -e expression #1, char 12: unknown option to\n例如\nexport Name=Jay/Chou sed -i \u0026#34;s/Name/$Name/g\u0026#34; hello.txt 这里为了保留被替换文本的 /,有两种方法\n在被替换文本中使用转义符号:\nexport Name=Jay\\/Chou sed -i \u0026#34;s/Name/$Name/g\u0026#34; hello.txt 使用 # 代替 / 作为 sed 中替换与被替换字符的分割符:\nexport Name=Jay/Chou sed -i \u0026#34;s#Name#$Name#g\u0026#34; hello.txt 修改命令行文件路径显示 # 文件路径太长,命令行很难看,只显示当前目录:\nvim ~/.bashrc # 将下行的w改成W PS1=\u0026#39;${debian_chroot:+($debian_chroot)}\\u@\\h:\\W\\$\u0026#39; 网络命名空间 # sudo ip netns add newnetns sudo ip netns exec newnetns bash # 进入命名空间后 使用exit退出 sudo ip netns exec newnetns bash -rcfile \u0026lt;(echo \u0026#34;PS1=\\\u0026#34;greenland\u0026gt; \\\u0026#34;\u0026#34;) sudo ip netns delete newnetns 清理工作 # 一、删除缓存\n1,非常有用的清理命令:\nsudo apt-get autoclean 清理旧版本的软件缓存 sudo apt-get clean 清理所有软件缓存 sudo apt-get autoremove 删除系统不再使用的孤立软件 这三个命令主要清理升级缓存以及无用包的。\n2,清理 opera firefox 的缓存文件:\nls ~/.opera/cache4 ls ~/.mozilla/firefox/*.default/Cache 3,清理 Linux 下孤立的包: 终端命令下我们可以用:\nsudo apt-get install deborphan -y 4,卸载:tracker 这个东西一般我只要安装 ubuntu 就会第一删掉 tracker 他不仅会产生大量的 cache 文件而且还会影响开机速度。再软件中心删除。\n附录: 包管理的临时文件目录: 包在 /var/cache/apt/archives 没有下载完的在 /var/cache/apt/archives/partial\n二、删除软件\nubuntu 软件的删除一般用“ubuntu 软件中心”或“新立得”就能搞定,但有时用命令似乎更快更好~~\nsudo apt-get remove --purge 软件名 sudo apt-get autoremove 删除系统不再使用的孤立软件 sudo apt-get autoclean 清理旧版本的软件缓存 dpkg -l |grep ^rc|awk \u0026#39;{print $2}\u0026#39; |sudo xargs dpkg -P 清除残余的配置文件 保证干净。\n三、删除多余内核\n1,首先要使用这个命令查看当前 Ubuntu 系统使用的内核\nuname -a 2,再查看所有内核\ndpkg --get-selections|grep linux 3,最后小心翼翼地删除吧\nsudo apt-get remove linux-image-2.6.32-22-generic ps:linux-image-xxxxxx-generic 就是要删除的内核版本 还有 linux-headers-xxxxxx linux-headers-xxxxxx-generic 总之中间有“xxxxxx”那段的旧内核都能删,注意一般选内核号较小的删除。\n« Linux 命令\n» Linux 启用 crontab 日志\n"},{"id":172,"href":"/linux/linux-enable-crontab-log/","title":"Linux Enable Crontab Log","section":"Linux","content":" 🏠 首页 / Linux / Linux 启用 crontab 日志\nLinux 启用 crontab 日志 # You can enable logging for cron jobs in order to track problems.\nYou need to edit the /etc/rsyslog.conf or /etc/rsyslog.d/50-default.conf (on Ubuntu) file and make sure you have the following line uncommented or add it if it is missing:\ncron.* /var/log/cron.log Then restart rsyslog and cron:\nsudo service rsyslog restart sudo service cron restart Cron jobs will log to /var/log/cron.log .\n« Linux常用命令\n» Linux-安全登录\n"},{"id":173,"href":"/linux/linux-secure-login/","title":"Linux Secure Login","section":"Linux","content":" 🏠 首页 / Linux / Linux-安全登录\nLinux-安全登录 # 我们都知道\nroot 是 linux 系统默认的最高权限账号 linux 系统默认的 ssh 端口是 22 大多数人习惯使用 user/password 来登录 linux 系统 很遗憾,如果你的系统没有做特殊等登陆配置,那么其他人便可以利用 ssh ip:22 root/暴力密码 来破解登入你 的 linux 系统,一旦被他破解,你的系统就可以为他所用了。\n但是,我们可以通过以下三种方式来避免发生这类安全问题。\n1. 禁用 root 账号登录 # 禁用 root 账号,那么我们就必须创建其他登录账号,这里建议账号名不要为 admin 这类常见用户名。\n创建用户(以下都基于 ubuntu 系统操作) adduser dp 用户赋权 此时创建的用户不能使用 sudo 权限,考虑将用户加入 sudo 组\nusermod -a -G sudo dp 并且,为了避免使用 sudo 权限需要时不时的输入密码的麻烦,进行免密设置。在 /etc/sudoers 文件中新增行。\ndp ALL=(ALL) NOPASSWD: ALL 到了这一步,应该尝试使用新用户登录系统,如果成功登录再往下继续。 禁用 root 登录 打开 /etc/ssh/sshd_config 文件,找到 PermitRootLogin 项,修改该项成如下:\nPermitRootLogin no 保存配置后,重启 ssh 服务:\nsystemctl restart ssh 至此,root 账号已经被禁用远程登录了。\n2. 更改 ssh 远程端口 # 弃用 22 端口,使用 0~65535 范围内随机端口作为 ssh 端口。\n要做的是修改 /etc/ssh/sshd_conf 文件,找到 Port 项,修改该项成如下:\nPort 39855 保存配置后,重启 ssh 服务:\nservice ssh restart 至此,服务器 ssh 远程端口已经修改为 39855,不能再使用 22 端口登陆系统了。\n3. 使用密钥文件登录 # 本地创建密钥文件:\nssh-keygen -t rsa -C poneding@gmail.com -f ~/.ssh/id_rsa_poneding 以上命令会默认在 ~/.ssh 目录下生成 id_rsa_poneding 私钥文件和 id_rsa_poneding.pub 文件,将 id_rsa_poneding.pub 文件内容追加到服务器 ~/.ssh/authorized_keys 文件中即可。\n« Linux 启用 crontab 日志\n» shell 命令间隔符\n"},{"id":174,"href":"/linux/shell-command-interval-character/","title":"Shell Command Interval Character","section":"Linux","content":" 🏠 首页 / Linux / shell 命令间隔符\nshell 命令间隔符 # 我们经常能看到Shell命令间有很多中间隔符:|,||,\u0026amp;\u0026amp; 等,它们到底有着什么样的作用呢?一一来看:\n1. |:\n间隔符 | 起着管道的作用,是将上一条命令的 stdout 作为下一条命令的 stdin:\n示例:\necho hello world! | tee hello.txt 2. ||:\n命令被 || 分割,只有当前面的命令发生错误,才会执行后面的命令。\n示例:\n# 如果创业失败,那么就继续打工 sh chuangye.sh || sh dagong.sh 3. \u0026amp;\u0026amp;:\n命令被 \u0026amp;\u0026amp; 分割,命令会连续执行,但是如果前面的命令发生错误,会影响后面的命令继续执行。\n示例:\n# 洗了手才能吃饭 sh wash_hand.sh \u0026amp;\u0026amp; sh eat.sh 4. ;:\n命令被 ; 分割,命令会连续执行,即使前面的命令发生错误,也不影响后面的命令继续执行。\n示例:\n# 不管有没有赚到钱,都要回家过年 sh earn_money.sh; sh go_home.sh 5. \u0026gt;:\n输出到指定文件(文件不存在则创建文件,文件存在则会覆盖文件内容)\necho \u0026#34;Hello World\u0026#34; \u0026gt; hello.txt 6. \u0026raquo;:\n追加到指定文件(文件不存在则创建文件,文件存在则追加文件内容)\necho \u0026#34;Hello World\u0026#34; \u0026gt;\u0026gt; hello.txt « Linux-安全登录\n» shell 基础\n"},{"id":175,"href":"/linux/shell/","title":"Shell","section":"Linux","content":" 🏠 首页 / Linux / shell 基础\nshell 基础 # shell 注释 # 单行注释:\n# 注释内容 多行注释:\n:\u0026lt;\u0026lt;EOF 注释内容 注释内容 注释内容 EOF 或\n:\u0026lt;\u0026lt;! 注释内容 注释内容 注释内容 ! 或\n:\u0026lt;\u0026lt;\u0026#39; 注释内容 注释内容 注释内容 \u0026#39; shell 变量 # 定义变量:\nmy_name=\u0026#34;Ding Peng\u0026#34; 使用变量:\n$my_name ${my_name} 只读变量:\nmy_name=\u0026#34;Ding Peng\u0026#34; readonly my_name 删除变量:\nunset my_name 变量类型:\n局部变量:在脚本或命令中定义,仅在当前shell实例有效 环境变量:所有shell实例有效 shell变量:shell程序设置的特殊变量 shell 字符串 # 单引号与双引号的区别:\n单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的; 单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用; 双引号里可以有变量; 双引号里可以出现转义字符 my_name=\u0026#34;Ding Peng\u0026#34; hello_string=\u0026#34;Hllo,\\\u0026#34;$my_name\\\u0026#34;!\u0026#34; echo $hello_string 获取字符串长度:\nmy_name=\u0026#34;dp\u0026#34; echo ${#my_name} # 输出5 截取字符串:\nhello_world=\u0026#34;Hello World!\u0026#34; # 从index 1截取4个字符 echo ${hello_world:1:4} # 输出ello shell 数组 # 只有以为数组,没有多维数组\n# 定义数组,空格隔开 names=(\u0026#39;Ding Peng\u0026#39; \u0026#39;Jay Chou\u0026#39; \u0026#34;Lebron James\u0026#34;) # 获取数组元素,根据index获取 echo ${names[1]} # 获取数组长度 echo ${#names[@]} echo ${#names[*]} shell 传参 # 执行shell脚本文件时,传递参数\n假如有一个test.sh文件内容如下:\n#!/bin/bash echo \u0026#34;Shell 传递参数实例!\u0026#34;; # 使用$n接收参数 echo \u0026#34;执行的文件名:$0\u0026#34;; echo \u0026#34;第一个参数为:$1\u0026#34;; echo \u0026#34;第二个参数为:$2\u0026#34;; chmod +x test.sh ./test.sh 1 2 第一行命令给test.sh添加可执行权限\n第二行命令执行test.sh文件\n以上命令将输出:\nShell 传递参数实例! 执行的文件名:./test.sh 第一个参数为:1 第二个参数为:2 其他参数处理:\n参数 说明 $# 传递到脚本的参数个数 $* 以一个单字符串显示所有向脚本传递的参数 $$ 脚本运行的当前进程ID号 $@ 与$*相同,但是使用时加引号,并在引号中返回每个参数。 shell if 控制语句 # if 后面需要接者 then:\nif [ condition-for-test ] then command ... fi 或者,\nif [ condition-for-test ]; then command ... fi 如:\n#!/bin/bash VAR=myvar if [ $VAR = myvar ]; then echo \u0026#34;1: \\$VAR is $VAR\u0026#34; # 1: $VAR is myvar fi if [ \u0026#34;$VAR\u0026#34; = myvar ]; then echo \u0026#34;2: \\$VAR is $VAR\u0026#34; # 2: $VAR is myvar fi if [ $VAR = \u0026#34;myvar\u0026#34; ]; then echo \u0026#34;3: \\$VAR is $VAR\u0026#34; # 3: $VAR is myvar fi if [ \u0026#34;$VAR\u0026#34; = \u0026#34;myvar\u0026#34; ]; then echo \u0026#34;4: \\$VAR is $VAR\u0026#34; # 4: $VAR is myvar fi 上面,我们在比较时,可以用双引号把变量引用起来。\n但要注意单引号的使用。\n#!/bin/bash VAR=myvar if [ \u0026#39;$VAR\u0026#39; = \u0026#39;myvar\u0026#39; ]; then echo \u0026#39;5a: $VAR is $VAR\u0026#39; else echo \u0026#34;5b: Not equal.\u0026#34; fibas # Output: # 5b: Not equal. 上面这个就把 '$VAR' 当一个字符串了。\n但如果变量是多个单词,我们就必须用到双引号了,如\n#!/bin/bash # 这样写就有问题 VAR1=\u0026#34;my var\u0026#34; if [ $VAR1 = \u0026#34;my var\u0026#34; ]; then echo \u0026#34;\\$VAR1 is $VAR1\u0026#34; fi # Output # error [: too many arguments # 用双引号 if [ \u0026#34;$VAR1\u0026#34; = \u0026#34;my var\u0026#34; ]; then echo \u0026#34;\\$VAR1 is $VAR1\u0026#34; fi 总的来说,双引号可以一直加上。\n空格问题 # 比较表达式中,如果 = 前后没有空格,那么整个表法式会被认为是一个单词,其判断结果为 True.\n#!/bin/bash VAR2=2 # 由于被识别成一个单词, [] 里面为 true if [ \u0026#34;$VAR2\u0026#34;=1 ]; then echo \u0026#34;$VAR2 is 1.\u0026#34; else echo \u0026#34;$VAR2 is not 1.\u0026#34; fi # Output # 2 is 1. # 前后加上空格就好了 if [ \u0026#34;$VAR2\u0026#34; = 1 ]; then echo \u0026#34;$VAR2 is 1.\u0026#34; else echo \u0026#34;$VAR2 is not 1.\u0026#34; fi # Output # 2 is not 1. 另外需要注意的是, 在判断中,中括号 [ 和变量之间一定要有一个空格,= 或者 ==。 如果缺少了空格,你可能会到这类似这样的错误:unary operator expected’ or missing]` 。\n# 正确, 符号前后有空格 if [ $VAR2 = 1 ]; then echo \u0026#34;\\$VAR2 is 1.\u0026#34; else echo \u0026#34;It\u0026#39;s not 1.\u0026#34; fi # Output # 2 is 1. # 错误, 符号前后无空格 if [$VAR2=1]; then echo \u0026#34;$VAR2 is 1.\u0026#34; else echo \u0026#34;It\u0026#39;s not 1.\u0026#34; fi # Output # line 3: =1: command not found # line 5: [=1]: command not found # It\u0026#39;s not 1. 文件测试表达式 # 对文件进行相关测试,判断的表达式如下:\n表达式 True file1 -nt file2 file1 比 file2 新。 file1 -ot file2 file1 比 file2 老。 -d file 文件file存在,且是一个文件夹。 -e file 文件 file 存在。 -f file 文件file存在,且为普通文件。 -L file 文件file存在,且为符号连接。 -O file 文件 flle 存在, 且由有效用户 ID 拥有。 -r file 文件 flle 存在, 且是一个可读文件。 -s file 文件 flle 存在, 且文件大小大于 0。 -w file 文件 flle 可写入。 -x file 文件 flle 可写执行。 可以使用 man test 查看详细的说明。\n当表达式为 True 时,测试命令返回退出状态 0,而表达式为 False 时返回退出状态1。\n#!/bin/bash FILE=\u0026#34;/etc/resolv.conf\u0026#34; if [ -e \u0026#34;$FILE\u0026#34; ]; then if [ -f \u0026#34;$FILE\u0026#34; ]; then echo \u0026#34;$FILE is a file.\u0026#34; fi if [ -d \u0026#34;$FILE\u0026#34; ]; then echo \u0026#34;$FILE is a directory.\u0026#34; fi if [ -r \u0026#34;$FILE\u0026#34; ]; then echo \u0026#34;$FILE is readable.\u0026#34; fi fi 字符串比较表达式 # 表达式 True string1 = string2 或 string1 == string2 两字符相等 string1 != string2 两个字符串不相等 string1 \u0026gt; string2 string1 大于 string2. string1 \u0026lt; string2 string1 小于string2. -n string 字符串长度大于0 -z string 字符串长度等于0 #!/bin/bash STRING=\u0026#34;\u0026#34; if [ -z \u0026#34;$STRING\u0026#34; ]; then echo \u0026#34;There is no string.\u0026#34; \u0026gt;\u0026amp;2 exit 1 fi # Output # There is no string. 其中 \u0026gt;\u0026amp;2 将错误信息定位到标准错误输出。\n数字比较表达式 # 下面这些是用来比较数字的一些表达式。\n[…] ((…)) True [ “int1” -eq “int2” ] (( “int1” == “int2” )) 相等. [ “int1” -nq “int2” ] (( “int1” != “int2” )) 不等. [ “int1” -lt “int2” ] (( “int1” \u0026lt; “int2” )) int2 大于 int1. [ “int1” -le “int2” ] (( “int1” \u0026lt;= “int2” )) int2 大于等于 int1. [ “int1” -gt “int2” ] (( “int1 \u0026gt; “int2” )) int1 大于 int2 [ “int1” -ge “int2” ] (( “int1 \u0026gt;= “int2” )) int1 大于等于 int2 shell 运算符 # 算数运算符 # val1=`expr 2 + 2` val2=`expr 2 - 2` val3=`expr 2 \\* 2` # *前面必须加\\ val4=`expr 2 / 2` val5=`expr 2 % 2` echo \u0026#34;2 + 2: $val1\u0026#34; echo \u0026#34;2 - 2: $val2\u0026#34; echo \u0026#34;2 * 2: $val3\u0026#34; echo \u0026#34;2 / 2: $val4\u0026#34; echo \u0026#34;2 % 2: $val5\u0026#34; 注意:\n使用反引号而不是单引号; 关键字expr; 运算符如:+-*/% 字符前后都需要空格,否则被认为是字符串 其他算数运算符:\n运算符 说明 示例 = 赋值 a=$b == 判等 [ $a == $b ] 返回0 != 不等 [ $a != $b ] 返回1 关系运算符 # 运算符 说明 示例 -eq 等于 [ $a -eq $b ] -ne 不等于 -gt 大于 -lt 小于 -ge 大于等于 -le 小于等于 注意:\n关系运算符只适用于数字,不支持字符串,除非字符串的值是数字 布尔运算符 # 运算符 说明 示例 ! 非运算 [ $a != $b ] -o 或运算 [ $a -eq $b -o $c -eq $d ] -a 与运算 [ $a -eq $b -a $c -eq $d ] 逻辑运算符 # 运算符 说明 示例 \u0026amp;\u0026amp; 与运算 [[ $a -eq $b \u0026amp;\u0026amp; $c -eq $d ]] || 或运算 `[[ $a -eq $b 说明:\n需要两层[] 字符串运算符 # 运算符 说明 示例 = 判等 [ $a = $b ] != 不等 [ $a != $b ] -z 字符长度是否为0 [ -z $a ] -n 字符串长度不为0 [ -n $b ] $ 字符串不为空,empty或whitespace [ $a ] 文件测试运算符 # 运算符 说明 示例 -b file 是否为块设备文件 [ -b $filepath ] -c file 是否为字符设备文件 -d file 是否为目录 -f file 是否为普通文件(非设备文件) -g file 是否设置SGID位 -k file 是否设置了粘着位(sticky bit) -p file 是否有名管道 -u file 是否设置SUID位 -r file 是否可读 -w file 是否可写 -x file 是否可执行 -s file 是否不为空文件(文件大小为0) -e file 文件(目录)是否存在 -S file 文件是否socket -L file 文件是否存在并且是一个符号链接 shell 命令 # echo # 打印字符串:\n可以是带双引号,单引号,不带引号 可以转义 可以用参数 echo \u0026#34;Hello World\u0026#34; echo \u0026#39;Hello World\u0026#39; echo Hello World name=\u0026#34;Ding Peng\u0026#34; echo \u0026#34;Hello, $name!\u0026#34; # -e 开启转义 echo -e \u0026#34;Hi Ding,\\n\u0026#34; # 两行之间空一行 echo \u0026#34;Nice to meet you.\u0026#34; echo -e \u0026#34;Hi Jay,\\c\u0026#34; # 不会换行,都在一行内输出 echo \u0026#34;Nice to meet you.\u0026#34; echo -e \u0026#34;Hi James,\\r\u0026#34; # 会换行,但是没有\\r也会默认换行的。 echo -e \u0026#34;Nice to meet you.\u0026#34; 打印到某文件:\necho \u0026#34;Hello World\u0026#34; \u0026gt; temp.txt 变量不转义,原样输出:\necho \u0026#39;$name\u0026#39; echo \u0026#39;\\*\u0026#39; 说明:\n需要用单引号 显示命令执行结果:\necho `date` 说明:\n使用反引号,不是单引号 read # 交互,获取控制台输入并赋值给某变量\nread name echo $name printf # 输出命令,移植性优于echo。默认不换行,可以在字符串后添加\\n\nprintf \u0026#34;Hello\\n\u0026#34; 通过以下脚本学习printf命令的一些格式化功能\nprintf \u0026#34;%-10s %-8s %-4s\\n\u0026#34; 姓名 性别 体重kg printf \u0026#34;%-10s %-8s %-4.2f\\n\u0026#34; 郭靖 男 66.1234 printf \u0026#34;%-10s %-8s %-4.2f\\n\u0026#34; 杨过 男 48.6543 printf \u0026#34;%-10s %-8s %-4.2f\\n\u0026#34; 郭芙 女 47.9876 %s %c %d %f都是格式替代符 %-10s 指一个宽度为10个字符(-表示左对齐,没有则表示右对齐),任何字符都会被显示在10个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。 %-4.2f 指格式化为小数,其中.2指保留2位小数。 shell 变量引用 # 当你要使用变量的时候,用 $ 来引用, 如果后面要接一些其他字符,可以用 {} 括起来。\n#!/bin/bash WORLD=\u0026#34;world world\u0026#34; echo \u0026#34;hello $WORLD\u0026#34; # hello world world echo \u0026#34;hello ${WORLD}2\u0026#34; # hello world world2 在 Bash 中要注意 单引号 ' ,双引号 \u0026quot; ,反引号 ` 的区别。\n单引号,双引号都能用来保留引号内的为文字值,其差别在于,双引号在遇到 $(参数替换) ,反引号 `(命令替换) 的时候有例外,单引号则剥夺其中所有字符的特殊含义。\n而反引号的作用 和 $() 是差不多的。 在执行一条命令的时候,会先执行其中的命令,再把结果放到原命令中。\n#!/bin/bash var=\u0026#34;music\u0026#34; sports=\u0026#39;sports\u0026#39; echo \u0026#34;I like $var\u0026#34; # I like music echo \u0026#34;I like ${var}\u0026#34; # I like music echo I like $var # I like music echo \u0026#39;I like $var\u0026#39; # I like $var echo \u0026#34;I like \\$var\u0026#34; # I like $var echo \u0026#39;I like \\$var\u0026#39; # I like \\$var echo `bash -version` # GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu)... echo \u0026#39;bash -version\u0026#39; # bash -version shell 经典实例 # 删除确认 # #!/bin/bash delete_sure delete_sure(){ cat \u0026lt;\u0026lt; eof $(echo -e \u0026#34;\\033[1;36mNote:\\033[0m\u0026#34;) Delete the KubeSphere cluster, including the module kubesphere-system kubesphere-devops-system kubesphere-monitoring-system kubesphere-logging-system openpitrix-system. eof read -p \u0026#34;Please reconfirm that you want to delete the KubeSphere cluster. (yes/no) \u0026#34; ans while [[ \u0026#34;x\u0026#34;$ans != \u0026#34;xyes\u0026#34; \u0026amp;\u0026amp; \u0026#34;x\u0026#34;$ans != \u0026#34;xno\u0026#34; ]]; do read -p \u0026#34;Please reconfirm that you want to delete the KubeSphere cluster. (yes/no) \u0026#34; ans done if [[ \u0026#34;x\u0026#34;$ans == \u0026#34;xno\u0026#34; ]]; then exit fi } « shell 命令间隔符\n» 使用 SSH Tunnel 连接中间件\n"},{"id":176,"href":"/linux/ssh-tunnel-connect-middleware/","title":"SSH Tunnel Connect Middleware","section":"Linux","content":" 🏠 首页 / Linux / 使用 SSH Tunnel 连接中间件\n使用 SSH Tunnel 连接中间件 # 背景 # 一般线上的数据库是不允许本机直接访问的,只能通过跳板机访问。但是这么多的开发人员都要访问数据库的话,跳板机的数量就有压力了。\n本篇介绍如何使用 SSH Tunnel 的方式访问数据库,数据库不限于 Sql Server、MySql、Mongodb、Redis 等。\n前提条件 # 已经拥有数据库的登录信息,如数据库访问的 host、port、user、password; 拥有一台可以访问数据库的跳板机登录权限,如跳板机的 IP、user、password(或密钥文件); 本机安装了有 SSH Tunnel 功能的数据库的可视化工具,如 DBeaver,Navicate,Robo 3T 等。 RDB # 使用 DBeaver 或 Navicate 等工具通过 SSH Tunnel 方式访问关系型数据库,以 Sql Server 为例。\n打开DBeaver,选择 Sql Server 连接。\n在连接配置页面 Main,输入 Sql Server 连接的基本信息,这里 host 直接使用原本的数据库 host 即可。\n切至 SSH,勾选 Use SSH Tunnel,输入跳板机的连接配置即可。\n配置完成,Ok连接即可。\n使用 SSMS + SSH Tunnel 连接 Sql Server # 本机需要安装 SSMS 和 Putty 工具。\n这篇文档对我帮助很大: https://courses.cs.washington.edu/courses/cse444/11wi/resources/tunneling-instructions.html\n打开 Putty 工具,在 Session 创建跳板机的连接。\n并且在 Connection 中配置登录账号和密码(或密钥文件)。\n然后在 Connection=\u0026gt;SSH=\u0026gt;Tunnels 中添加 Sql Server 的 server 信息\n在 Source Port 中输入 \u0026lt;port\u0026gt;(Sql Server的默认端口是1433);\n在 Destination 中输入 \u0026lt;host\u0026gt;:\u0026lt;port\u0026gt;(例如:db.example.com:1433);\n输入完之后点击Add按钮。\n完成操作之后你的页面也应是下面这个样子。\n回到 Connection=\u0026gt;SSH,勾选 Don’t start as shell or command at all.\n上面的配置完成后,点击 Open,应该会跳入如下的远程界面。\n**注意:**如果本机已经安装了 Sql Server 数据库,需要现在 Service 中停掉本机的 SqlServer 服务,否则可能会造成端口冲突。\n这时,你可以使用 SSMS 连接数据库服务了。\n**注意:**SSMS 连接配置页面,Server name 必须是 127.0.0.1 而不是原本的数据库 host。\nNoSql # 打开 Robo 3T,新建连接,并在 Connection 页面配置 mongo 的连接信息。\n切至 SSH 页面,勾选 Use SSH Tunnel,输入跳板机的连接配置即可。\n配置完成,Save 后连接即可。\n其他例如Redis数据库,可以使用 Redis Desktop Manager SSH Tunnel 连接。\n« shell 基础\n» tee 保存 stderr 到文件\n"},{"id":177,"href":"/linux/tee-keep-stderr/","title":"Tee Keep Stderr","section":"Linux","content":" 🏠 首页 / Linux / tee 保存 stderr 到文件\ntee 保存 stderr 到文件 # tee命令是将 stdout 写入到某文件当中,但是如何将 stderr 也写入到文件当中?\n示例如下:\n1.sh\necho exec 1.sh start! \u0026amp;\u0026amp; \\ cat hello.txt \u0026amp;\u0026amp; \\ echo exec 1.sh end! 假如 hello.txt 文件不存在,执行 1.sh 文件中的 cat 命令将报错。如果我们想将执行 1.sh 文件的输出写入到一个 log 文件,例如:\nsh 1.sh | tee 1.log 执行以上命令,控制台的输出是:\n$ sh 1.sh | tee 1.log exec 1.sh start! cat: hello.txt: No such file or directory 但是写入到 1.log 日志文件中的内容是:\nexec 1.sh start! 可以看到,并没有将错误输出到日志文件中,如果我们想将错误一并输出到日志文件,该怎么做呢?\n使用以下办法:\nsh 1.sh 2\u0026amp;\u0026gt;1 | tee 1.log 这下我们就能同时在控制台和日志文件中看到错误输出了。\n科普:\n上述命令中使用到的 2\u0026gt;\u0026amp;1 是什么意思?\n0 stdin,1 stdout,2 stderr\n2\u0026gt;\u0026amp;1:将标准错误重定向至标准输出\n« 使用 SSH Tunnel 连接中间件\n» vim 使用\n"},{"id":178,"href":"/linux/vim-common-commands/","title":"Vim Common Commands","section":"Linux","content":" 🏠 首页 / Linux / vim 使用\nvim 使用 # 设置 # vim /etc/vim/vimrc 或者当前环境设置\n:set paste :set nopaste :set number :set nonumber :set hlsearch :set nohlsearch :set cursorline :set nocursorline vim删除所有行 # ggdG 撤销 # u ctrl+r 查找 # 大小写问题:\n默认大小写敏感。\n大小写不敏感:/hello\\c\n大小写敏感:/hello\\C\n设置大小写敏感:\necs+:set ignorecase:设置默认忽略大小写敏感\necs+:set smartcase:如果查找字符中存在大写则自动大小写敏感\n查找当前字符:\n光标移动 # h:向左 j:向下 k:向上 l:向右 替换 # :s/jay/dp/g 替换当前行中所有匹配 jay =\u0026gt; dp :1,$s/jay/dp/g 替换所有 :1,5s/jay/dp/g 替换 1 到 5 行 翻页 # ctrl+f:下一页 ctrl+d:下半页 ctrl+b:上一页 ctrl+u:上半页 行操作 # dG:删除当前行到尾行\ndd:删除当前行\nyy:复制当前行\np:粘贴行\nddp:下一行\n退出 # 如果你没有 sudo 权限打开了文件,但是你已经编写了很多内容,怎么保存推出呢:\n:w ! sudo tee % 保存到另外一个文件:\n:w otherfile # 会生成新文件,修改内容体现在新文件中,原文件不变 查找 # 在 normal 模式下按下 / 即可进入查找模式,输入要查找的字符串并按下回车。 Vim 会跳转到第一个匹配。按下 n 查找下一个,按下 N 查找上一个。\nVim 查找支持正则表达式,例如 /vim$ 匹配行尾的 \u0026quot;vim\u0026quot;。 需要查找特殊字符需要转义,例如 /vim\\$ 匹配 \u0026quot;vim$\u0026quot;。\n注意查找回车应当用 \\n,而替换为回车应当用 \\r(相当于 \u0026lt;CR\u0026gt;)。\n大小写敏感查找 # 在查找模式中加入 \\c 表示大小写不敏感查找,\\C 表示大小写敏感查找。例如:\n/foo\\c 将会查找所有的 \u0026quot;foo\u0026quot;,\u0026quot;FOO\u0026quot;,\u0026quot;Foo\u0026quot; 等字符串。\n大小写敏感配置 # Vim 默认采用大小写敏感的查找,为了方便我们常常将其配置为大小写不敏感:\n\u0026#34; 设置默认进行大小写不敏感查找 set ignorecase \u0026#34; 如果有一个大写字母,则切换到大小写敏感查找 set smartcase 将上述设置粘贴到你的 ~/.vimrc,重新打开 Vim 即可生效。\n查找当前单词 # 在 normal 模式下按下 * 即可查找光标所在单词(word), 要求每次出现的前后为空白字符或标点符号。例如当前为 foo, 可以匹配 foo bar 中的 foo,但不可匹配 foobar 中的 foo。 这在查找函数名、变量名时非常有用。\n按下 g* 即可查找光标所在单词的字符序列,每次出现前后字符无要求。 即 foo bar 和 foobar 中的 foo 均可被匹配到。\n其他设置 # :set incsearch 可以在敲键的同时搜索,按下回车把移动光标移动到匹配的词; 按下 Esc 取消搜索。\n:set wrapscan 用来设置到文件尾部后是否重新从文件头开始搜索。\n查找与替换 # :s(substitute)命令用来查找和替换字符串。语法如下:\n:{作用范围}s/{目标}/{替换}/{替换标志} 例如 :%s/foo/bar/g 会在全局范围(%)查找 foo 并替换为 bar,所有出现都会被替换(g)。\n作用范围 # 作用范围分为当前行、全文、选区等等。\n当前行:\n:s/foo/bar/g 全文:\n:%s/foo/bar/g 选区,在 Visual 模式下选择区域后输入:,Vim 即可自动补全为 :'\u0026lt;,'\u0026gt;。\n:\u0026#39;\u0026lt;,\u0026#39;\u0026gt;s/foo/bar/g 2-11行:\n:5,12s/foo/bar/g 当前行 . 与接下来两行 +2:\n:.,+2s/foo/bar/g 替换标志 # 上文中命令结尾的 g 即是替换标志之一,表示全局 global 替换(即替换目标的所有出现)。 还有很多其他有用的替换标志:\n空替换标志表示只替换从光标位置开始,目标的第一次出现:\n:%s/foo/bar i 表示大小写不敏感查找,I 表示大小写敏感:\n:%s/foo/bar/i # 等效于模式中的\\c(不敏感)或\\C(敏感) :%s/foo\\c/bar c 表示需要确认,例如全局查找 \u0026quot;foo\u0026quot; 替换为 \u0026quot;bar\u0026quot; 并且需要确认:\n:%s/foo/bar/gc 回车后Vim会将光标移动到每一次 \u0026quot;foo\u0026quot; 出现的位置,并提示\nreplace with bar (y/n/a/q/l/^E/^Y)? 按下 y 表示替换,n 表示不替换,a 表示替换所有,q 表示退出查找模式, l 表示替换当前位置并退出。^E 与 ^Y 是光标移动快捷键,参考: Vim中如何快速进行光标移动。\n高亮设置 # 高亮颜色设置 # 如果你像我一样觉得高亮的颜色不太舒服,可以在 ~/.vimrc 中进行设置:\nhighlight Search ctermbg=yellow ctermfg=black highlight IncSearch ctermbg=black ctermfg=yellow highlight MatchParen cterm=underline ctermbg=NONE ctermfg=NONE 上述配置指定 Search 结果的前景色(foreground)为黑色,背景色(background)为灰色; 渐进搜索的前景色为黑色,背景色为黄色;光标处的字符加下划线。\n更多的CTERM颜色可以查阅: http://vim.wikia.com/wiki/Xterm256_color_names_for_console_Vim\n禁用/启用高亮 # 有木有觉得每次查找替换后 Vim 仍然高亮着搜索结果? 可以手动让它停止高亮,在 normal 模式下输入:\n:nohighlight \u0026#34; 等效于 :nohl 其实上述命令禁用了所有高亮,只禁用搜索高亮的命令是 :set nohlsearch。 下次搜索时需要 :set hlsearch 再次启动搜索高亮。\n延时禁用 # 怎么能够让 Vim 查找/替换后一段时间自动取消高亮,发生查找时自动开启呢?\n\u0026#34; 当光标一段时间保持不动了,就禁用高亮 autocmd cursorhold * set nohlsearch \u0026#34; 当输入查找命令时,再启用高亮 noremap n :set hlsearch\u0026lt;cr\u0026gt;n noremap N :set hlsearch\u0026lt;cr\u0026gt;N noremap / :set hlsearch\u0026lt;cr\u0026gt;/ noremap ? :set hlsearch\u0026lt;cr\u0026gt;? noremap * *:set hlsearch\u0026lt;cr\u0026gt; 将上述配置粘贴到 ~/.vimrc,重新打开 vim 即可生效。\n一键禁用 # 如果延时禁用搜索高亮仍然不够舒服,可以设置快捷键来一键禁用/开启搜索高亮:\nnoremap n :set hlsearch\u0026lt;cr\u0026gt;n noremap N :set hlsearch\u0026lt;cr\u0026gt;N noremap / :set hlsearch\u0026lt;cr\u0026gt;/ noremap ? :set hlsearch\u0026lt;cr\u0026gt;? noremap * *:set hlsearch\u0026lt;cr\u0026gt; nnoremap \u0026lt;c-h\u0026gt; :call DisableHighlight()\u0026lt;cr\u0026gt; function! DisableHighlight() set nohlsearch endfunc 希望关闭高亮时只需要按下 Ctrl+H,当发生下次搜索时又会自动启用。\n参考阅读 # XTERM 256色: http://vim.wikia.com/wiki/Xterm256_color_names_for_console_Vim Vim Wikia - 查找与替换: http://vim.wikia.com/wiki/Search_and_replace 用 Vim 打造 IDE 环境: https://harttle.land/2015/11/04/vim-ide.html 本文采用 知识共享署名 4.0 国际许可协议(CC-BY 4.0)进行许可,转载注明来源即可: https://harttle.land/2016/08/08/vim-search-in-file.html。学识粗浅写作仓促,如有错误辛苦评论或 邮件 指出。\n写入、保存、退出 # :q[uit] \u0026#34; 退出 :q! \u0026#34; 强制退出 :w[rite] \u0026#34; 保存 :w! \u0026#34; 强制保存,能不能保存成功取决于用户对文件的权限 :w ! sudo tee % \u0026#34; 如果没有权限保存,试试这个命令 ZZ \u0026#34; 两个大写的 Z,没有修改直接退出,有修改保存后退出 :w newfilename \u0026#34; 另存为新文件 :1, 10 w newfilename \u0026#34; 将 1 到 10 行的内容另存为新文件 :1, 10 w \u0026gt;\u0026gt; filename \u0026#34; 将 1 到 10 行的内容另存为新文件 :r filename \u0026#34; 将目标文件的内容追加到当前光标下一行 :3 r filename \u0026#34; 将目标文件的内容追加到第 3 行一下 :! ls \u0026#34; 暂时离开 Vim 查看当前目录的文件,回车后返回 Vim 光标移动 # h \u0026#34; 方向键 ← j \u0026#34; 方向键 ↓ k \u0026#34; 方向键 ↑ l \u0026#34; 方向键 → 0 \u0026#34; 移动到行首 $ \u0026#34; 移动到行尾的回车符 g_ \u0026#34; 移动到行尾最后一个非空字符 gg \u0026#34; 移动到第一行 G \u0026#34; 移动到最后一行\u0026#34; w \u0026#34; 移动到下一个单词开头 e \u0026#34; 移动到单词的结尾 b \u0026#34; 移动到单词的开头 \u0026#34; 不常用 nh \u0026#34; 向左移动 n 格,n 为数字 nl \u0026#34; 向右移动 n 格 nj \u0026#34; 向下移动 n 行 nk \u0026#34; 向上移动 n 行 n\u0026lt;space\u0026gt; \u0026#34; 向右移动 n 格,同 nl H \u0026#34; 移动到当前屏幕第一行的第一个字符 M \u0026#34; 移动到当前屏幕中间行的第一个字符 L \u0026#34; 移动到当前屏幕最后一行的第一个字符 + \u0026#34; 移动到非空白字符的下一行 - \u0026#34; 移动到非空白字符的上一行 :n\u0026lt;cr\u0026gt; \u0026#34; 跳转到第 n 行h \u0026#34; 方向键 ← j \u0026#34; 方向键 ↓ k \u0026#34; 方向键 ↑ l \u0026#34; 方向键 → 0 \u0026#34; 移动到行首 $ \u0026#34; 移动到行尾的回车符 g_ \u0026#34; 移动到行尾最后一个非空字符 gg \u0026#34; 移动到第一行 G \u0026#34; 移动到最后一行\u0026#34; w \u0026#34; 移动到下一个单词开头 e \u0026#34; 移动到单词的结尾 b \u0026#34; 移动到单词的开头 \u0026#34; 不常用 nh \u0026#34; 向左移动 n 格,n 为数字 nl \u0026#34; 向右移动 n 格 nj \u0026#34; 向下移动 n 行 nk \u0026#34; 向上移动 n 行 n\u0026lt;space\u0026gt; \u0026#34; 向右移动 n 格,同 nl H \u0026#34; 移动到当前屏幕第一行的第一个字符 M \u0026#34; 移动到当前屏幕中间行的第一个字符 L \u0026#34; 移动到当前屏幕最后一行的第一个字符 + \u0026#34; 移动到非空白字符的下一行 - \u0026#34; 移动到非空白字符的上一行 :n\u0026lt;cr\u0026gt; \u0026#34; 跳转到第 n 行 翻页 # \u0026lt;c-f\u0026gt; \u0026#34; 向下移动一页 \u0026lt;c-d\u0026gt; \u0026#34; 向下移动半页 \u0026lt;c-b\u0026gt; \u0026#34; 向上移动一页 \u0026lt;c-u\u0026gt; \u0026#34; 向上移动半页 查找与替换 # /word \u0026#34; 从光标位置向下搜索 word 单词 ?word \u0026#34; 从光标位置向上搜索 word 单词 n \u0026#34; 英文字母 n,根据 / 或 ? 搜索的方向定位到下一个匹配目标 N \u0026#34; 与 n 相反,定位匹配目标 :n1,n2s/word1/word2/g \u0026#34; n1, n2 表示数字,替换 n1 行到 n2 行的单词 :1,$s/word1/word2/g \u0026#34; 全文替换,也可以写成 :%s/word1/word2/g :1,$s/word1/word2/gc \u0026#34; 全文替换,并出现确认提示 复制、粘贴、删除 # x \u0026#34; 向后删除一个字符 nx \u0026#34; 向后删除 n 个字符 X \u0026#34; 向前删除一个字符 nX \u0026#34; 向前删除 n 个字符 dd \u0026#34; 删除当前行 ndd \u0026#34; 向下删除 n 行 d1G / dgg \u0026#34; 删除第一行到当前行的数据 dG \u0026#34; 删除当前行到最后一行的数据 d$ \u0026#34; 删除当前字符到行尾 D \u0026#34; 删除当前字符到行尾 d0 \u0026#34; 从行首删除到当前字符 yy \u0026#34; 复制当前行 Y \u0026#34; 复制当前行 nyy \u0026#34; 从当前行开始复制 n 行 y1G / ygg \u0026#34; 从第一行复制到当前行 yG \u0026#34; 从当前行复制到最后一行 y0 \u0026#34; 从行首复制到当前字符 y$ \u0026#34; 从当前字符复制到行尾 p, P \u0026#34; 黏贴,p 黏贴到光标下一行,P 黏贴到光标上一行 yyp \u0026#34; 复制并粘贴 ddp \u0026#34; 删除并粘贴,相当于下移当前行 \u0026#34;+y \u0026#34; 复制本文到系统剪切板 \u0026#34;+p \u0026#34; 粘贴系统剪切板到 Vim(不会影响文本格式) 插入 # i \u0026#34; 在光标前进入 insert 模式 I \u0026#34; 在当前行左边第一个非空字符前进入 insert 模式,类似其他编辑器的 \u0026lt;c-a\u0026gt; 快捷键 a \u0026#34; 在光标后进入 insert 模式 A \u0026#34; 在当前行右边第一个非空字符前进入 insert 模式,类似其他编辑器的 \u0026lt;c-e\u0026gt; 快捷键 o \u0026#34; 在光标的下一行插入 O \u0026#34; 在光标的上一行插入 s \u0026#34; 删除当前字符,并进入 insert 模式 S \u0026#34; 删除当前行,并进入插入模式 vc \u0026#34; 删除当前字符,并进入 insert 模式 cc \u0026#34; 删除当前行,并进入插入模式 c0 \u0026#34; 删除光标位置到行首,并进入 insert 模式 cg_ \u0026#34; 删除光标位置到行尾最后一个非空字符,并进入 insert 模式 ce \u0026#34; 删除光标位置到单词末尾,并进入 insert 模式 cw \u0026#34; 删除光标位置到单词末尾,并进入 insert 模式 ciw \u0026#34; 删除当前单词,并进入 insert 模式 cip \u0026#34; 删除整个段落,并进入 insert 模式 ci( \u0026#34; 删除当前括号内的内容,并进入 insert 模式 适用于 ([{\u0026lt;\u0026#39;` 等所有成对的标签 撤销重做 # u \u0026#34; 撤销 \u0026lt;c-r\u0026gt; \u0026#34; 重做 . \u0026#34; 重复完成操作 替换 # r \u0026#34; 替换单个字符,自动返回 normal 模式 R \u0026#34; 连续替换多个字符,手动 \u0026lt;esc\u0026gt; 返回 normal 模式 大小写 # ~ \u0026#34; 当前字符大小写反转 g~~ \u0026#34; 正行字符大小写反转 vu \u0026#34; 当前字符小写 vU \u0026#34; 当前字符大写 vU \u0026#34; 当前字符大写 viwu \u0026#34; 当前字符小写 viwU \u0026#34; 当前字符大写 ggguG \u0026#34; 文本所有字符小写 gggUG \u0026#34; 文本所有字符大写 :%s/\\\u0026lt;./\\u\u0026amp;/g \u0026#34; 将所有单词首字母大写(需要使用 :nohls 去掉高亮) :%s/\\\u0026lt;./\\l\u0026amp;/g \u0026#34; 将所有单词首字母小写 :%s/.*/\\u\u0026amp; \u0026#34; 将每行第一个字母大写 :%s/.*/\\l\u0026amp; \u0026#34; 将每行第一个字母小写 多窗口操作 # :sp filename \u0026#34; 上下分割窗口 :vs[p] filename \u0026#34; 左右分割窗口 \u0026lt;c-w\u0026gt;h[j[k[l]]] \u0026#34; 根据方向键移动光标到该方向的窗口上 \u0026lt;c-w\u0026gt;[N]\u0026gt; \u0026#34; N 位数字,可选,增加当前窗口 N 列宽\u0026#34; \u0026lt;c-w\u0026gt;[N]\u0026lt; \u0026#34; N 位数字,可选,减少当前窗口 N 列宽\u0026#34; \u0026lt;c-w\u0026gt;[N]+ \u0026#34; N 位数字,可选,增加当前窗口 N 行高\u0026#34; \u0026lt;c-w\u0026gt;[N]- \u0026#34; N 位数字,可选,减少当前窗口 N 行高\u0026#34; \u0026lt;c-w\u0026gt;= \u0026#34; 将所有窗口设置等宽高 \u0026lt;c-w\u0026gt;[N]n \u0026#34; N 位数字,可选,打开一个新窗口 N 行高,默认为整个窗口的一半\u0026#34; \u0026lt;c-w\u0026gt;[N]s \u0026#34; N 位数字,可选,将当前窗口垂直分割为上下两个窗口展示\u0026#34; \u0026#34; 新窗口可以为 N 行高,默认为整个窗口的一半\u0026#34; \u0026#34; 类似于 :sp current_file\u0026#34; \u0026lt;c-w\u0026gt;[N]v \u0026#34; N 位数字,可选,将当前窗口水平分割为左右两个窗口展示\u0026#34; \u0026#34; 新窗口可以为 N 列宽,默认为整个窗口的一半\u0026#34; \u0026#34; 类似于 :vs current_file\u0026#34; \u0026lt;c-w\u0026gt;o \u0026#34; 关闭除当前窗口外的所有窗口 \u0026lt;c-w\u0026gt;r \u0026#34; 顺时针转动窗口 \u0026lt;c-w\u0026gt;R \u0026#34; 逆时针转动窗口 \u0026lt;c-w\u0026gt;x \u0026#34; 对调左右或上下两个对应的窗口 \u0026lt;c-w\u0026gt;q \u0026#34; 退出窗口 :q \u0026#34; 退出窗口 多文件编辑 # vim file1 file2 \u0026#34; 同时打开两个文件 :files \u0026#34; 查看现在编辑的文件列表,%a 代表正在操作哪个文件 1 %a \u0026#34;file1\u0026#34; line 1 2 \u0026#34;file2\u0026#34; line 0 :n \u0026#34; 跳到下一个文件,这里的 n 就是字母 :N \u0026#34; 跳到上一个文件 « tee 保存 stderr 到文件\n"},{"id":179,"href":"/middleware/","title":"Middleware","section":"","content":" 🏠 首页 / 数据中间件\n数据中间件 # Elasticsearch\nMongoDB\nMySQL\nPostgres\nRedis\n"},{"id":180,"href":"/middleware/elasticsearch/","title":"Elasticsearch","section":"Middleware","content":" 🏠 首页 / 数据中间件 / Elasticsearch\nElasticsearch # 全文搜索\nAPI # 题外话:\n幂等性:多次执行同样的请求,资源只能创建或修改一次\nPOST 请求不是幂等性的,同样的数据请求,会造成不同的影响\nPUT 是幂等性的,同样的请求造成的影响是一样的\n创建索引 # PUT /users 查询索引 # 获取单个索引\nGET /users 获取所有索引\nGET /_cat/indices?v 删除索引 # DELETE /users 创建文档 # 这个操作是在单个索引下的\nPOST /users/_doc # 一定需要body,否则报错 body: { \u0026#34;name\u0026#34;: \u0026#34;dp\u0026#34;, \u0026#34;age\u0026#34;: 18 } 上面这个文档创建时会生成随机 ID(返回结果中的 _id),不便维护,使用下面的方法自定义文档 ID,此时由于 ID 自定义了,就要求幂等,所以可以使用 PUT 方法\nPOST | PUT /users/_doc/1002 PUT /users/_create/1003 # 一定需要body,否则报错 body: { \u0026#34;name\u0026#34;: \u0026#34;dp\u0026#34;, \u0026#34;age\u0026#34;: 18 } 查询文档 # 获取单个文档\nGET /users/_doc/1001 获取所有文档\nGET /users/_search 修改文档 # PUT /users/_doc/1001 body: { \u0026#34;name\u0026#34;: \u0026#34;dp\u0026#34;, \u0026#34;age\u0026#34;: 28 } 你可能发现了,PUT 既可以创建也可以修改。\n修改特定字段(非幂等)\nPOST /users/_update/1001 body: { \u0026#34;doc\u0026#34;: { \u0026#34;age\u0026#34;: 29 } } 删除文档 # DELETE /users/_doc/1001 复杂查询 # 条件查询 # GET /users/_search?q=name:dp 或者通过请求体查询\nGET /users/_search { \u0026#34;query\u0026#34;: { // \u0026#34;match\u0026#34;: { // \u0026#34;name\u0026#34;: \u0026#34;dp\u0026#34; // } // 查询所有 \u0026#34;match_all\u0026#34;: { } } // 分页 \u0026#34;from\u0026#34;: 0, \u0026#34;size\u0026#34;: 10, // 过滤字段 \u0026#34;_source\u0026#34;: [\u0026#34;name\u0026#34;, \u0026#34;age\u0026#34;] // 排序 \u0026#34;sort\u0026#34;: { \u0026#34;age\u0026#34;: { \u0026#34;order\u0026#34;: \u0026#34;asc|desc\u0026#34; } }, \u0026#34;highlight\u0026#34;: { \u0026#34;fields\u0026#34;: { \u0026#34;name\u0026#34;: {} } } } 使用 match,如果查询条件是 \u0026ldquo;name\u0026rdquo;: \u0026ldquo;Hello World\u0026rdquo;,查询结果会是 Hello,World 分别查询的结果集合,因为 ES 会将查询关键词拆解,每个单词都单独匹配索引。\n如果香要完全匹配,使用 match_phrase。\n多条件查询 # GET /users/_search { \u0026#34;query\u0026#34;: { \u0026#34;bool\u0026#34;: { // must =\u0026gt; and; should =\u0026gt; or \u0026#34;must\u0026#34;: [ { \u0026#34;match\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;dp\u0026#34; } }, { \u0026#34;match\u0026#34;: { \u0026#34;age\u0026#34;: 28 } } ], \u0026#34;filter\u0026#34;: { \u0026#34;range\u0026#34;: { \u0026#34;age\u0026#34;: { \u0026#34;gt\u0026#34;: 18 } } } } } } 聚合查询 # { \u0026#34;aggs\u0026#34;: { // 聚合操作 \u0026#34;age_group\u0026#34;: { // 名称,随意命名 \u0026#34;terms\u0026#34;: { // 分组 \u0026#34;field\u0026#34;: \u0026#34;age\u0026#34; // 分组字段 }, \u0026#34;avg\u0026#34;: { // 平均值 \u0026#34;field\u0026#34;: \u0026#34;age\u0026#34; // 分组字段 } // 此外还有 max, min } }, \u0026#34;size\u0026#34;: 0 //不显示原始数据 } 设置 # 设置集群最大索引数 # 如果遇到错误:\nValidation Failed: 1: this action would add [2] total shards, but this cluster currently has [3000]/[3000] maximum shards open; 那么导致该问题的原因可能是由于现创建的 index 太多,已经达到了 cluster.max_shards_per_node 的限制,需要计划清除一些无用的 index 或者增加es集群节点 index 限制:\nPUT /_cluster/settings { \u0026#34;transient\u0026#34;: { \u0026#34;cluster.max_shards_per_node\u0026#34;:5000 } } 或者使用 curl 调用集群设置的 api\ncurl -XPUT $CLUSTER_URL/_cluster/settings -H \u0026#39;Content-type: application/json\u0026#39; --data-binary $\u0026#39;{\u0026#34;transient\u0026#34;:{\u0026#34;cluster.max_shards_per_node\u0026#34;:5000}}\u0026#39; 设置删除权限 # 删除权限需要\nPUT /_cluster/settings { \u0026#34;persistent\u0026#34; : { \u0026#34;action.destructive_requires_name\u0026#34; : \u0026#34;false\u0026#34; } } 或者使用 curl 调用集群设置的 api\ncurl -XPUT $CLUSTER_URL/_cluster/settings -H \u0026#39;Content-type: application/json\u0026#39; --data-binary $\u0026#39;{\u0026#34;persistent\u0026#34;:{\u0026#34;action.destructive_requires_name\u0026#34;:\u0026#34;false\u0026#34;}}\u0026#39; » MongoDB\n"},{"id":181,"href":"/middleware/mongodb/","title":"Mongodb","section":"Middleware","content":" 🏠 首页 / 数据中间件 / MongoDB\nMongoDB # 资料 # http://cw.hubwiz.com/card/c/543b2f3cf86387171814c026/1/1/1/ http://cw.hubwiz.com/card/c/5438c259032c7817c40298b5/1/1/1/ 安装 # 按照官网给出的指南在 ubuntu 系统安装 mongod,参考 https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/\n验证 mongo 是否安装成功:进入 ubuntu shell 窗口,直接输入\nmongo --version 窗口正常输出 mongo 版本就说明 mongo 安装成功\n启动 mongo 服务\nsudo systemctl statt mongod #/stop/restart 创建 dba 用户并添加权限验证\nmongodb 没有开启权限验证之前,使用 mongo 命令可以直接连接本地 mongodb;\nsudo mongo 使用 db.createUser 命令创建 dba 用户,为 dba 用户添加所有 database 的管理员权限;\n\u0026gt; db.createUser({user:\u0026#34;dba\u0026#34;,pwd:\u0026#34;[your pass]\u0026#34;,roles:[ {role:\u0026#34;readWriteAnyDatabase\u0026#34;,db:\u0026#34;admin\u0026#34;},{role:\u0026#34;dbAdminAnyDatabase\u0026#34;,db:\u0026#34;admin\u0026#34;},{role:\u0026#34;userAdminAnyDatabase\u0026#34;,db:\u0026#34;admin\u0026#34;},{role:\u0026#34;clusterAdmin\u0026#34;,db:\u0026#34;admin\u0026#34;},{role:\u0026#34;restore\u0026#34;,db:\u0026#34;admin\u0026#34;},{role:\u0026#34;backup\u0026#34;,db:\u0026#34;admin\u0026#34;} ]}) Successfully added user: { \u0026#34;user\u0026#34; : \u0026#34;dba\u0026#34;, \u0026#34;roles\u0026#34; : [ // ... ] } dba 包含的 role:\nreadWriteAnyDatabase dbAdminAnyDatabase userAdminAnyDatabase clusterAdmin restore backup 修改 mongod.conf 文件,mongodb 可以对外访问,并开启权限验证\nsudo vim /etc/mongod.conf mongod.conf 文件原始内容: mongod.conf 修改后内容:\n注意:如果仅仅是将 bindIPAll 配置为 true,即允许外部网络网络,而没有开启权限验证,那么外部对 mongodb 拥有很大的操作权限,存在很大的安全问题。\nsudo service mongod restart 修改完 mongod.conf 文件后一定要重启 mongo 服务生效。\n设置权限验证后就,如果直接通过 mongo 命令连接 mongodb,绝大多数操作都是被禁用的,需要配置权限连接\nsudo mongo [ip/domain name]:[port]/[database] -u username -p pwd # 如: sudo mongo 10.0.0.1:27017/admin -u dba -p [your-pass] 创建 database\nuse devops 使用 use [database],如果 database 不存在则会默认新建; 新创建但是不存在数据的 database,使用 show dbs 将看不到,除非插入数据 也可以使用 db.createdatabase 为专有 database 创建用户\n\u0026gt; db.createUser({user:\u0026#34;devops\u0026#34;,pwd:\u0026#34;[your pass]\u0026#34;,roles:[{role:\u0026#34;readWrite\u0026#34;,db:\u0026#34;devops\u0026#34;}]}) Successfully added user: { \u0026#34;user\u0026#34; : \u0026#34;devops\u0026#34;, \u0026#34;roles\u0026#34; : [ { \u0026#34;role\u0026#34; : \u0026#34;readWrite\u0026#34;, \u0026#34;db\u0026#34; : \u0026#34;devops\u0026#34; } ] } devops 插入数据\nsudo mongo 10.0.0.1:27017/devops -u devops -p [your-pass] \u0026gt;use devops \u0026gt;db.temp.insert({\u0026#34;name\u0026#34;:\u0026#34;devops.mongodb\u0026#34;}) 注意:\ndb.temp.insert,如果没有 temp collection,则会默认创建。 坑:连接 mongo 时,如果密码带有特殊字符,如!(其他没测)需要在密码前后使用单引号引用起来!!!\nmongodb 角色: https://docs.mongodb.com/manual/reference/built-in-roles/\n数据库用户角色:read、readWrite; 数据库管理角色:dbAdmin、dbOwner、userAdmin; 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager; 备份恢复角色:backup、restore; 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase 超级用户角色:root // 这里还有几个角色间接或直接提供了系统超级用户的访问(dbOwner 、userAdmin、userAdminAnyDatabase) 内部角色:__system 基础查询 # like 查询\nmongodb 查询\ndb.getCollection(\u0026#39;test\u0026#39;).find({\u0026#34;name\u0026#34;:{$regex:/dp/i}}) /dp/i 忽略大小写 ​ python mongo 查询\ntest._get_collection().find( {\u0026#34;name\u0026#34;: {\u0026#39;$regex\u0026#39;: \u0026#39;dp\u0026#39;, \u0026#39;$options\u0026#39;: \u0026#39;i\u0026#39;}}) not like查询\nmongodb查询\ndb.getCollection(\u0026#39;test\u0026#39;).find({\u0026#34;name\u0026#34;:{$not:/dp/i}}) ​ python mongo 查询\nimport re test._get_collection().find({\u0026#34;path\u0026#34;: {\u0026#39;$not\u0026#39;: re.compile(\u0026#39;dp\u0026#39;)}}) 创建索引 # db.getCollection(\u0026#34;ColName\u0026#34;).createIndex({\u0026#34;Name\u0026#34;, 1}) # 如果集合数据量比较大了 那么创建索引会非常耗时,建议使用后台创建 db.getCollection(\u0026#34;ColName\u0026#34;).createIndex({\u0026#34;Name\u0026#34;, 1}, {\u0026#34;background\u0026#34;: true}) 用户 # 修改用户密码:\ndb.changeUserPassword(\u0026#34;username\u0026#34;,\u0026#34;new_pwd\u0026#34;) « Elasticsearch\n» MySQL\n"},{"id":182,"href":"/middleware/mysql/","title":"Mysql","section":"Middleware","content":" 🏠 首页 / 数据中间件 / MySQL\nMySQL # 安装 # Windows 安装 MySQL # 下载 Mysql 安装包: https://dev.mysql.com/downloads/installer/\n下载完成后,双击 msi 文件安装。\nUbuntu 安装 MySQL # sudo apt update sudo apt install mysql-server -y # 只安装 mysql 客户端 sudo apt install mysql-client -y Docker 安装 MySQL # docker run -d --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7 Troubleshooting # Q1. root 用户本地登录 # 使用命令 mysql -u root -p,输入密码后登录失败,提示如下:\nAccess denied for user \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; 解决方案::\n修改 mysqld.cnf 配置文件\nsudo vim /etc/mysql/mysql.conf.d/mysqld.cnf 在 [mysqld] 块的 skip-external-locking 下添加 skip-grant-tables\n重启mysql服务\nsudo systemctl restart mysql.service root 无密码登录 mysql\nmysql -u root -p 修改 root 用户密码以及 plugin\n# 修改 root 用户密码 use mysql; update user set authentication_string=password(\u0026#34;123456\u0026#34;),plugin=\u0026#39;mysql_native_password\u0026#39; where user=\u0026#39;root\u0026#39;; flush privileges; # quit 退出 quit 注释 etc/mysql/mysql.conf.d/mysqld.cnf 刚新加的行\n$ sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf ... #skip-grant-tables ... 再次重启 mysql 服务\nsudo systemctl restart mysql.service 使用root密码登录mysql\nmysql -u root -p # Enter Password:123456 Q2. 创建用户 # create user zhangsan identified by \u0026#39;zhangsan\u0026#39;; grant all privileges on zhangsanDb.* to zhangsan@\u0026#39;%\u0026#39; identified by \u0026#39;zhangsan\u0026#39;; flush privileges; 遇到 Your password does not satisfy the current policy requirements 问题。密码验证无法通过。\n解决方案::\nSHOW VARIABLES LIKE \u0026#39;validate_password%\u0026#39;; set global validate_password_policy=LOW; MySQL配置 # 创建用户 # use mysql; create user \u0026#39;admin\u0026#39;@\u0026#39;%\u0026#39; identified by \u0026#39;Admin@123\u0026#39;; %,表示允许所有访问地址远程访问,一般 root 账号是使用的\u0026rsquo;root\u0026rsquo;@\u0026rsquo;localhost'\ngrant all privileges on *.* to \u0026#39;admin\u0026#39;@\u0026#39;%\u0026#39; identified by \u0026#39;Admin@123\u0026#39; with grant option; *.*,第一个 * 表示数据库资源,第二个表示表资源,也可以使用为 store_db.t_order\nwith grant option,表示允许级联授权\nflush privileges; # 查看用户 select user, host from user; MySQL 开启 binary logging # 修改配置文件,如果 MySQL 部署在 Ubuntu 上,修改 /etc/mysql/mysql.conf.d/mysqld.cnf 文件,取消 server-id,log_bin 的配置项的注释; sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf server-id = 1 log_bin = /var/log/mysql/mysql-bin.log 重启 MySQL。 sudo service mysql restart AWS MySQL 开启 binary logging # 可以参考官方文档: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_LogAccess.Concepts.MySQL.html#USER_LogAccess.MySQL.BinaryFormat\nMySQL 执行进程 # show processlist # 结束执行进程,只删除 command 不为 Sleep 的即可 kill \u0026lt;id\u0026gt; Mysql Join # Inner Join # select * from t_a inner join t_b on t_a.bid = t_b.id Right Join # select * from t_a right join t_b on t_a.bid = t_b.id Left Join # select * from t_a left join t_b on t_a.bid = t_b.id MySQL常用 # 字符串 # 拼接字符串:\n直接拼接字符串\nselect concat(\u0026#34;hello, \u0026#34;,\u0026#34;world!\u0026#34;); # 输出 hello, world 以某个分隔符拼接字符串\nselect concat_ws(\u0026#39;;\u0026#39;,\u0026#34;Apple\u0026#34;,\u0026#34;Orange\u0026#34;,\u0026#34;Banana\u0026#34;); # 输出 Apple;Orange;Banana 截取字符串:\n某个字符串,从第 n 个,返回长度为 l 的字符串\nselect substring(\u0026#39;hello, world!\u0026#39;,2,4); # 或者 select mid(\u0026#39;hello, world!\u0026#39;,2,4) # 输出 ello 从左/右开始截图长度为 l 的字符串\nselect left(\u0026#39;hello, world!\u0026#39;,4) # 输出 hell select right(\u0026#39;hello, world!\u0026#39;,4) # 输出 rld! 替换字符串:\n某个字符串,从第 n 个,长度为 l 的字符串 str1 替换为 str2\nselect insert(\u0026#39;hello, world!\u0026#39;,2,4,\u0026#39;xxxx\u0026#39;) # 输出 hxxx, world! 替换特定字符串\nselect replace(\u0026#39;hello, world!\u0026#39;,\u0026#39;ello\u0026#39;,\u0026#39;xxxx\u0026#39;) # 输出 hxxx, world! 查询字符串位置:\nselect locate(\u0026#39;ello\u0026#39;,\u0026#39;hello, world!\u0026#39;) # 或者 select position(\u0026#39;ello\u0026#39; IN \u0026#39;hello, world!\u0026#39;) # 或者 select instr(\u0026#39;hello, world!\u0026#39;,\u0026#39;ello\u0026#39;) # 均输出 2 时间 # 获取当前时间:\nselect now() 格式化时间:\nselect date_format(now(),\u0026#39;%y-%m-%d\u0026#39;); 格式化参数\n%S, %s 两位数字形式的秒( 00,01, \u0026hellip;, 59) %I, %i 两位数字形式的分( 00,01, \u0026hellip;, 59) %H 两位数字形式的小时,24 小时(00,01, \u0026hellip;, 23) %h 两位数字形式的小时,12 小时(01,02, \u0026hellip;, 12) %k 数字形式的小时,24 小时(0,1, \u0026hellip;, 23) %l 数字形式的小时,12 小时(1, 2, \u0026hellip;, 12) %T 24 小时的时间形式(hh:mm:ss) %r 12 小时的时间形式(hh:mm:ss AM 或hh:mm:ss PM) %p AM或PM %W 一周中每一天的名称(Sunday, Monday, \u0026hellip;, Saturday) %a 一周中每一天名称的缩写(Sun, Mon, \u0026hellip;, Sat) %d 两位数字表示月中的天数(00, 01,\u0026hellip;, 31) %e 数字形式表示月中的天数(1, 2, \u0026hellip;, 31) %D 英文后缀表示月中的天数(1st, 2nd, 3rd,\u0026hellip;) %w 以数字形式表示周中的天数( 0 = Sunday, 1=Monday, \u0026hellip;, 6=Saturday) %j 以三位数字表示年中的天数( 001, 002, \u0026hellip;, 366) %U 周(0, 1, 52),其中Sunday 为周中的第一天 %u 周(0, 1, 52),其中Monday 为周中的第一天 %M 月名(January, February, \u0026hellip;, December) %b 缩写的月名( January, February,\u0026hellip;., December) %m 两位数字表示的月份(01, 02, \u0026hellip;, 12) %c 数字表示的月份(1, 2, \u0026hellip;., 12) %Y 四位数字表示的年份 %y 两位数字表示的年份 %% 直接值“%”\n数据导入导出 # A 表数据写入 B 表:\n如果 a 表字段和 b 表字段一致,则直接使用以下语句\ninsert into t_b select * from t_a 如果只希望 A 表部分字段写入 B 表,则使用以下语句\ninsert into t_b (t_b.f1,t_b.f2,...) select t_a.f1, t_a.f2, ... from t_a 可以使用 as 使字段映射,可以使用 where 过滤数据\n« MongoDB\n» Postgres\n"},{"id":183,"href":"/middleware/postgres/","title":"Postgres","section":"Middleware","content":" 🏠 首页 / 数据中间件 / Postgres\nPostgres # 自增 Id 数据插入权限\nGRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public TO \u0026lt;user_name\u0026gt;; 修改用户密码\nalter user postgres with password \u0026#39;admin123\u0026#39;; « MySQL\n» Redis\n"},{"id":184,"href":"/middleware/redis/","title":"Redis","section":"Middleware","content":" 🏠 首页 / 数据中间件 / Redis\nRedis # 安装 # Docker 安装 redis # sudo docker run --name redis-01 \\ -p 2501:6379 \\ -v /home/dp/apps/redis/redis-01/conf/redis.conf:/etc/redis/redis.conf \\ -v /home/dp/apps/redis/redis-01/data:/data \\ -d \\ redis:6.0 redis-server /etc/redis/redis.conf --appendonly yes redis.conf 是配置文件 redis-server /etc/redis/redis.conf,启用配置,如果没有 redis-server 则 redis 默认是无配置启动 \u0026ndash;appendonly yes 启用数据持久化 redis.conf 参照:\nbind 127.0.0.1 # 注释掉这部分,使 redis 可以外部访问 daemonize no # 用守护线程的方式启动 requirepass your_pwd # 给 redis 设置密码 appendonly yes # redis 持久化 默认是 no tcp-keepalive 300 # 防止出现远程主机强迫关闭了一个现有的连接的错误 默认是 300 « Postgres\n"},{"id":185,"href":"/os/","title":"Os","section":"","content":" 🏠 首页 / 操作系统\n操作系统 # MacOS\nohmyzsh\nopenssl\nUbuntu\nWindows 使用姿势\n"},{"id":186,"href":"/os/macos/","title":"Macos","section":"Os","content":" 🏠 首页 / 操作系统 / MacOS\nMacOS # Git 忽略 .DS_Store 文件 # # 配置全局忽略文件 git config --global core.excludesfile ~/.gitignore_global # 添加 .DS_Store 文件到全局忽略文件 echo .DS_Store \u0026gt;\u0026gt; ~/.gitignore_global echo ._.DS_Store \u0026gt;\u0026gt; ~/.gitignore_global echo **/.DS_Store \u0026gt;\u0026gt; ~/.gitignore_global echo **/._.DS_Store \u0026gt;\u0026gt; ~/.gitignore_global 配置 PATH # 在终端使用 export 命令设置 PATH 并不能全局生效,如果你想设置全局 PATH ,可以使用以下这个方法:\nsudo mkdir /etc/paths.d/mypath vim /etc/paths.d/mypath /your/path 查看端口占用并退出程序 # 有时候使用 VSCode 调试或运行程序后,无法成功推出程序,端口一直占用。\n查看端口占用:\n# [port] 替换成你想查看的端口号,例如:sudo lsof -i tcp:8080 sudo lsof -i tcp:[port] 上述命令可以得到程序的进程 PID,退出进程:\n# [PID] 替换成程序的进程 PID sudo kill -9 [PID] 重置 Downie 试用 # rm -rfv ~/Library/Containers/com.charliemonroe.Downie-4/Data/Library/Application\\ Support/Downie\\ 4 配置快捷命令:\nvim ~/.zsh alias reset-downie-trial=\u0026#39;rm -rfv ~/Library/Containers/com.charliemonroe.Downie-4/Data/Library/Application\\ Support/Downie\\ 4\u0026#39; 自定义 PATH # mkdir -p ~/.dev/bin echo \u0026#34;export PATH=$PATH:~/.dev/bin\u0026#34; 更新 Python 的证书包 # /Applications/Python\\ 3.6/Install\\ Certificates.command Mac 缓存清理 # Go 缓存清理 # go clean -cache npm 缓存清理 # npm cache clean --force sudo npm cache clean --force yarn 缓存清理 # yarn cache clean rust 缓存清理 # # install cargo-cache cargo install cargo-cache # clean cache cargo cache --autoclean iTerm2 # 解决 iTerm2 下使用 Solarized Dark 主题时,zsh-autosuggestions 显示问题:\nvim .oh-my-zsh/custom/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh 找到并配置:ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=10'\nexec zsh Go 调试问题 # 如果遇到 Failed to launch: could not launch process: can not run under Rosetta, check that the installed build of Go is right for your CPU architecture 问题,尝试以下解决方案:\n安装最新版本的 delve:\ngo install github.com/go-delve/delve/cmd/dlv@latest » ohmyzsh\n"},{"id":187,"href":"/os/ohmyzsh/","title":"Ohmyzsh","section":"Os","content":" 🏠 首页 / 操作系统 / ohmyzsh\nohmyzsh # macos # echo $SHELL /bin/zsh 安装 ohmyzsh sh -c \u0026#34;$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\u0026#34; 安装 zsh-autosuggestions git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions 配置 .zshrc 文件添加 ohmyzsh 插件: plugins=(git docker docker-compose kubectl autojump zsh-autosuggestions) linux # 安装 zsh: sudo apt update sudo apt install zsh -y 修改 shell: chsh -s /usr/bin/zsh 打开新的终端,将使用 zsh\necho $SHELL /usr/bin/zsh 安装 ohmyzsh: sh -c \u0026#34;$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\u0026#34; 安装 zsh-autosuggestions: git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions 配置 .zshrc 文件添加 ohmyzsh 插件: plugins=(git docker docker-compose kubectl autojump zsh-autosuggestions globalias) GLOBALIAS_FILTER_VALUES=(ls grep) « MacOS\n» openssl\n"},{"id":188,"href":"/os/openssl/","title":"Openssl","section":"Os","content":" 🏠 首页 / 操作系统 / openssl\nopenssl # openssl 常用于生成证书、签名、加密等操作。它是一个开源的工具,可以在 Linux、Windows、MacOS 等操作系统上运行。\n包含三个组件:\nopenssl:命令行工具 libcrypto:加密算法库 libssl:加密模块应用库,实现了 SSL 和 TLS 协议 对称加密 # echo test \u0026gt; test.txt # 加密 openssl enc -e -des3 -a -salt -in test.txt -out test.txt.enc # 解密 openssl enc -d -des3 -a -salt -in test.txt.enc -out test.txt.dec -salt:加盐,增加破解难度,使用 openssl 默认盐值 -S [salt]:指定盐值\n非对称加密 # 生成密钥对\nopenssl genrsa -out rsa_private_key.pem 2048 openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem 加密(公钥加密,私钥解密)\necho test \u0026gt; test.txt # 加密 openssl rsautl -encrypt -inkey rsa_public_key.pem -pubin -in test.txt -out test.txt.enc # 解密 openssl rsautl -decrypt -inkey rsa_private_key.pem -in test.txt.enc -out test.txt.dec 数字签名(私钥签名,公钥验证)\necho test \u0026gt; test.txt # 签名 openssl dgst -sha256 -sign rsa_private_key.pem -out test.txt.sign test.txt # 验证 openssl dgst -sha256 -verify rsa_public_key.pem -signature test.txt.sign test.txt CA 证书 \u0026amp; 颁发证书 # 生成 CA 私钥和证书\n# 至少需要输入 4 位密码口令 openssl req -new -x509 -days 3650 -keyout ca.key -out ca.crt 生成服务端私钥和证书\n# 服务端私钥 openssl genrsa -out server.key 2048 # 服务端证书签署请求(csr) openssl req -new -key server.key -out server.csr # 服务端证书 openssl x509 -req -days 3650 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt 查看证书信息\n# 查看 CA 证书 openssl x509 -in ca.crt -noout -text # 查看服务端证书 openssl x509 -in server.crt -noout -text « ohmyzsh\n» Ubuntu\n"},{"id":189,"href":"/os/ubuntu/","title":"Ubuntu","section":"Os","content":" 🏠 首页 / 操作系统 / Ubuntu\nUbuntu # 终端默认使用英文 # LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=en_US.UTF-8 下载 arm64 桌面镜像 # https://cdimage.ubuntu.com/jammy/daily-live/current/jammy-desktop-arm64.iso 关闭防火墙 # sudo ufw disable sudo ufw status 修改时区 # sudo cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime sudo timedatectl set-timezone Asia/Shanghai date # 同步时间 sudo apt install ntpdate -y sudo ntpdate cn.pool.ntp.org 解决英文系统下中文显示问题 # 修改字体优先级\nsudo vim /etc/fonts/conf.avail/64-language-selector-prefer.conf 注意:在 Ubuntu 23.10 或更新版本的系统上修改文件:\nsudo vim /etc/fonts/conf.d/64-language-selector-cjk-prefer.conf 将 `JP` 和 `KR` 所在行往下调整即可,调整成如下所示: ```xml \u0026lt;?xml version=\u0026#34;1.0\u0026#34;?\u0026gt; \u0026lt;!DOCTYPE fontconfig SYSTEM \u0026#34;fonts.dtd\u0026#34;\u0026gt; \u0026lt;fontconfig\u0026gt; \u0026lt;alias\u0026gt; \u0026lt;family\u0026gt;sans-serif\u0026lt;/family\u0026gt; \u0026lt;prefer\u0026gt; \u0026lt;family\u0026gt;Noto Sans CJK SC\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Noto Sans CJK TC\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Noto Sans CJK HK\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Noto Sans CJK JP\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Noto Sans CJK KR\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Lohit Devanagari\u0026lt;/family\u0026gt; \u0026lt;/prefer\u0026gt; \u0026lt;/alias\u0026gt; \u0026lt;alias\u0026gt; \u0026lt;family\u0026gt;serif\u0026lt;/family\u0026gt; \u0026lt;prefer\u0026gt; \u0026lt;family\u0026gt;Noto Serif CJK SC\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Noto Serif CJK TC\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Noto Serif CJK JP\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Noto Serif CJK KR\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Lohit Devanagari\u0026lt;/family\u0026gt; \u0026lt;/prefer\u0026gt; \u0026lt;/alias\u0026gt; \u0026lt;alias\u0026gt; \u0026lt;family\u0026gt;monospace\u0026lt;/family\u0026gt; \u0026lt;prefer\u0026gt; \u0026lt;family\u0026gt;Noto Sans Mono CJK SC\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Noto Sans Mono CJK TC\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Noto Sans Mono CJK HK\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Noto Sans Mono CJK JP\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Noto Sans Mono CJK KR\u0026lt;/family\u0026gt; \u0026lt;/prefer\u0026gt; \u0026lt;/alias\u0026gt; \u0026lt;/fontconfig\u0026gt; 安装搜狗输入法 # 参照官方文档: https://pinyin.sogou.com/linux/help.php\n# 禁用ctrl+shift+f vim ~/.config/sogoupinyin/conf/env.ini # 找到这行ShortCutFanJian=1,改为ShortCutFanJian=0 vim ~/.config/fcitx/conf/fcitx-chttrans.config # 找到 #Hotkey=CTRL_SHIFT_F,取消注释,并改为 #Hotkey=CTRL_SHIFT_] # 保存之后,重新登录即可 修复 No Wi-Fi Adapter Found 问题 # rfkill unblock all sudo /etc/init.d/networking restart sudo rm /etc/udev/rules.d/70-persistent-net.rules sudo reboot M720 鼠标优联连接 # 下载 Solaar\nsudo apt install solaar 下载完成之后,插入 USB 接收器,M720 切换至未被占用的 Channel,并重新启动(Off =\u0026gt; On)。\n打开 Solaar 应用\nsolaar 如果没有发现 M720 鼠标,则重启 Ubuntu,重启之后再次打开 Solaar 之后,可以看到列表中出现 Solaar,点击 \u0026ldquo;Pair the device\u0026rdquo; 即可。\nUbuntu 启用中文输入法 # 好像是 Ubuntu 23.10 后支持。\n首先需要安装中文语言; 选择输入法,Chinese,双击打开; 输入法设置,取消英文模式。 虚拟机 # VMWare Fusion 开启宿主机复制粘贴功能,桌面版 Ubuntu 系统安装插件:\nsudo apt install open-vm-tools open-vm-tools-desktop -y 安装完之后,重启虚拟机即可。\n升级 # 22.04 -\u0026gt; 23.04 # sudo apt update sudo apt upgrade -y sudo apt install update-manager-core -y sudo vim /etc/update-manager/release-upgrades # modify Prompt=lts to Prompt=normal sudo sed -i \u0026#39;s/jammy/lunar/g\u0026#39; /etc/apt/sources.list sudo apt update sudo apt upgrade -y sudo apt dist-upgrade -y 23.04 -\u0026gt; 23.10 # sudo apt update sudo apt upgrade -y sudo do-release-upgrade 输入法 # RIME 输入法:\nsudo apt install ibus-rime 安装完成后,需要注销当前用户重新登录或者直接重启生效。\n配置:\n输入时按 F4,可以直接在输入界面配置,例如配置默认使用简体,默认使用英文等。\ngnome-tweak 工具 # 下载:\nsudo apt install gnome-tweak -y 应用界面找到 Tweak 应用运行,或者直接终端输入 gnome-tweak 运行。\n配置:\n在 2K 分辨率屏幕下可以调整配置:字体 -\u0026gt; Scaling Factor -\u0026gt; 调整为 1.2; « openssl\n» Windows 使用姿势\n"},{"id":190,"href":"/os/windows/","title":"Windows","section":"Os","content":" 🏠 首页 / 操作系统 / Windows 使用姿势\nWindows 使用姿势 # 0. 激活 windows # 以管理员身份运行命令行,输入以下三行命令:\nslmgr /ipk W269N-WFGWX-YVC9B-4J6C9-T83GX # 等待弹窗出现,点击确定之后,再继续执行下一行命令 slmgr /skms kms.loli.best # slmgr /skms kms.03k.org # 同样需要等待弹窗出现,点击确定之后,再继续执行 slmgr /ato # 正常情况下应该会出现激活成功的弹窗 1. Windows Terminal SSH 连接超时自动断开 # 使用 Windows Terminal SSH 连接 linux 服务器,每过一段时间后,就会自动断开。\n解决方案:\n打开配置文件 %USERPROFILE%/.ssh/config,在该配置文件中添加配置行:\nServerAliveInterval 60 2. VSCode 搭配 Remote-SSH # 配置远程访问文件 %USERPROFILE%/.ssh/config:\n密钥文件进行SSH连接 # Host aliyun HostName 11.11.11.11 User root IdentityFile ~/.ssh/aliyun_key 用户密码进行SSH连接 # Host ubuntu HostName 192.168.11.11 User dp 但是如果你不是使用密钥形式配置的话,每次连接时都需要输入密码。\n解决方案:\ncd ~/.ssh ssh-keygen -t rsa -C \u0026#34;remote\u0026#34; -f ubuntu 将生成 ubuntu 和 ubuntu.pub 文件,将 ubuntu.pub 文件内容追加拷贝至远程服务器 ~/.ssh/authorized_keys 文件即可,如果文件不存在,则创建。\n3. Powershell 命令 # 清除历史命令\nRemove-Item (Get-PSReadlineOption).HistorySavePath 4. 安装 windows11 虚拟机 # 安装 windwos 虚拟机跳过网络的方法,按下 Shift+F10 或者是 Fn+Shift+F10 快捷键调出命令提示符窗口,执行命令:\noobe\\BypassNRO « Ubuntu\n"},{"id":191,"href":"/reading/","title":"Reading","section":"","content":" 🏠 首页 / 阅读\n阅读 # 云原生应用开发:Operator原理与实践\n我的第一本算法书\n深入理解计算机网络.md\n"},{"id":192,"href":"/reading/%E4%BA%91%E5%8E%9F%E7%94%9F%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91Operator%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E8%B7%B5/","title":"云原生应用开发: Operator原理与实践","section":"Reading","content":" 🏠 首页 / 阅读 / 云原生应用开发:Operator原理与实践\n云原生应用开发:Operator原理与实践 # 2. Operator原理 # 2.2 client-go原理 # client-go主要用在Kubernetes的Controller中,包括内置Controller(如kube-controller-manager)和CRD控制器;\n实现对各类K8s API资源的增删改查操作;\n包含Reflector,Informer,Indexr等组件。\n2.2.1 client-go介绍 # 是操作k8s集群资源的编程式交互客户端,利用对kube-apiserver的交互访问,实现对各类K8s API资源的增删改查操作;\nclient-go不仅被k8s项目本身使用(如kubectl),还在基于k8s的二次开发中被外部用户广泛使用:自定义控制器,Operator等。\n使用:\nclient-go库抽象封装了与k8s reset api的交互,便于开发者基于k8s做二次开发。利用client-go操作k8s资源的流程基本如下:\n通过kubeconfig信息构造Config实例,该实例记录了集群证书,k8s apiserver地址等信息; 根据Config实例携带的信息构建特定的客户端(clientset,dynamicset等); 利用客户端向k8s apiserver发起请求,操作k8s资源。 以下是使用 client-go 获取 pod 的代码清单:\nfunc main() { var kubernetes *string if home := homeDir(); home != \u0026#34;\u0026#34; { kubeconfig := flag.String( \u0026#34;kubeconfig\u0026#34;, filePath.Join(home, \u0026#34;.kube\u0026#34;, \u0026#34;config\u0026#34;), \u0026#34;(optional) absolute path to the kubeconfig file\u0026#34;, ) } else { kubeconfig := flag.String(\u0026#34;kubeconfig\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;absolute path to the kubeconfig file\u0026#34;) } flag.Parse() // use the current context in kubeconfig config, err := clientcmd.BuildConfigFromFlags(\u0026#34;\u0026#34;, *kubeconfig) if err != nil { panic(err.Error()) } // create the clientset clientset, err := kubernetes.NewForConfig(config) if err != nil { panic(err.Error()) } for { pods, err := clientset.CoreV1().Pods(\u0026#34;\u0026#34;).List(context.TODO(), metav1.ListOptions{}) if err != nil { panic(err.Error) } // ... } } func homeDir() string { if h := os.Getenv(\u0026#34;HOME\u0026#34;); h != \u0026#34;\u0026#34; { return h } return os.Getenv(\u0026#34;USERPROFILE\u0026#34;) // windows } 2.2.2 client-go 主体结构 # client-go 支持 4 种与 k8s apiserver 交互的客户端:\nRESTClient # 最基础的客户端,主要对 HTTP 请求进行封装,支持 JSON 和 Protobuf 格式数据;\npackage main import ( \u0026#34;context\u0026#34; \u0026#34;fmt\u0026#34; corev1 \u0026#34;k8s.io/api/core/v1\u0026#34; metav1 \u0026#34;k8s.io/apimachinery/pkg/apis/meta/v1\u0026#34; \u0026#34;k8s.io/client-go/kubernetes/scheme\u0026#34; \u0026#34;k8s.io/client-go/reset\u0026#34; \u0026#34;k8s.io/client-go/tools/clientcmd\u0026#34; ) func main() { // 加载配置文件,生成config对象 config, err := clientcmd.BuildConfigFromFlags(\u0026#34;\u0026#34;, \u0026#34;/root/.kube/config\u0026#34;) if err != nil { panic(err.Error()) } // 配置API路径和请求的GV config.APIPath = \u0026#34;api\u0026#34; config.GroupVersion = \u0026amp;corev1.SchemeGroupVersion // 配置数据的编解码器 config.NegotiatedSerializer = scheme.Codecs // 实例化RESTClient对象 restclient, err := rest.RESTClientFor(config) if err != nil { panic(err.Error()) } result := \u0026amp;corev1.PodList{} err = restclient.Get(). Namespace(\u0026#34;default\u0026#34;). Resource(\u0026#34;pods\u0026#34;). VersionedParams(\u0026amp;metav1.ListOptions{Limit: 100}, scheme.ParameterCodec). Do(context.TODO()).Into(result) if err != nil { panic(err.Error) } for _, d := range result.Items { fmt.Printf( \u0026#34;NAMESPACE: %v \\t NAME: %v \\t STATUS: %v\\n\u0026#34;, d.Namespace, d.Name, d.Status.Phase, ) } } Clientset # k8s 自身内置资源的客户端集合,仅能操作已知类型的内置资源,如 Pod,Service 等;\npackage main DynamicClient # 动态客户端,可以对任意k8s资源执行通用操作,包括CRD。\n通过动态指定GVR操作任意k8s资源,以 map[string]interface{} 结构存储 k8s apiserver 的返回数据,再利用反射机制在运行时进行数据绑定。这种松耦合的方式意味着更高的灵活性,但与此同时,也无法获取强数据类型检查和验证的好处。\n理解DynamicClient,你可能需要对这 Object.runtime 接口和 Unstructured 结构体有所了解:\nObject.runtime:k8s中所有资源都实现了该接口,包括 DeepCopyObject 和 GetObjectKind 方法: Unstructured:包含 DiscoveryClient # 发现客户端,发现apiserver支持的资源组,资源版本和资源信息(GVR),使用kubectl api-versions可以获取支持的资源;\n» 我的第一本算法书\n"},{"id":193,"href":"/reading/%E6%88%91%E7%9A%84%E7%AC%AC%E4%B8%80%E6%9C%AC%E7%AE%97%E6%B3%95%E4%B9%A6/","title":"我的第一本算法书","section":"Reading","content":" 🏠 首页 / 阅读 / 我的第一本算法书\n我的第一本算法书 # 作者:石田保辉,宫崎修一\n1. 算法的基础知识 # 1.1 什么是算法 # 算法是计算机计算和解决问题的步骤。\n选择排序:\nfunc selectionSort(nums []int) []int { for i := 0; i \u0026lt; len(nums); i++ { min := i for j := i + 1; j \u0026lt; len(nums); j++ { if nums[j] \u0026lt; nums[min] { min = j } } nums[i], nums[min] = nums[min], nums[i] } return nums } « 云原生应用开发:Operator原理与实践\n» 深入理解计算机网络.md\n"},{"id":194,"href":"/reading/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/","title":"深入理解计算机网络","section":"Reading","content":" 🏠 首页 / 阅读 / 深入理解计算机网络.md\n二进制数的四种表示方法 # 原码 # 二进制数第一位用来表示正负符号,0 表示 +,1 表示 -。 原码就是带正负符号的二进制数,例如,+3 原码为 00000011,-3 原码为 10000011。 原码表示方法中,0 有 +0 和 -0 表现形式。\n反码 # 补码 # 原码在加减法运算中的不便,\n移码 # « 我的第一本算法书\n"},{"id":195,"href":"/rust/","title":"Rust","section":"","content":" 🏠 首页 / Rust 编程\nRust 编程 # Rust cargo 管理工具\nRust 开发环境配置\nRust 入门\n查看根目录\nRust VSCode 调试\nRust WASM 编程\n"},{"id":196,"href":"/rust/cargo/","title":"Cargo","section":"Rust","content":" 🏠 首页 / Rust 编程 / Rust cargo 管理工具\nRust cargo 管理工具 # cargo 是 Rust 的构建系统和包管理器。\n创建项目 # cargo new hello-world cd hello-world 可以使用 cargo new --vcs git hello-world 创建项目并初始化 git 仓库,它将自动创建一个 .gitignore 文件。\n编译项目 # cargo build # 编译之后将在 target/debug 目录下生成可执行文件 # 可以通过以下命令运行 ./target/debug/hello-world 默认构建模式是 debug,里面包含了大量的符号和调试信息,优化级别不高。建议使用 relase 模式构建发布到生产环境。\nrelease 模式构建花费的时间较长,但是构建出来的二进制文件则要精简很多。\ncargo build --release 运行项目 # cargo run 追踪 panic 位置运行:\nRUST_BACKTRACE=1 cargo run 创建类包 # cargo new --lib mylib 检测项目是否可以编译 # cargo check 安装可执行文件(更新) # cargo install --path . 卸载可执行文件 # cargo uninstall hello-world 发布项目 # 发布到 crates.io,需要注册账号。\n并且,需要在 Cargo.toml 中添加部分内容,例如作者、描述、许可证必要信息以及其他信息:\n[package] name = \u0026#34;hello-world\u0026#34; version = \u0026#34;0.1.0\u0026#34; edition = \u0026#34;2021\u0026#34; authors = [\u0026#34;Pone Ding \u0026lt;poneding@gmail.com\u0026gt;\u0026#34;] description = \u0026#34;A hello world program in Rust\u0026#34; license = \u0026#34;MIT OR Apache-2.0\u0026#34; readme = \u0026#34;README.md\u0026#34; keywords = [\u0026#34;hello\u0026#34;, \u0026#34;world\u0026#34;] categories = [\u0026#34;hello-world\u0026#34;] cargo publish # 忽略未提交的更改 cargo publish --allow-dirty 添加依赖 # cargo add rand cargo add hello-world # 添加本地依赖 cargo add hello-world --path ../hello-world 依赖外部类库以及引用:\nCargo.toml:\n... [dependencies] rand = \u0026#34;0.8.5\u0026#34; main.rs:\nextern crate rand; use rand::Rng; 依赖内部类库以及引用:\nCargo.toml:\n... [dependencies] hello_lib = { path = \u0026#34;../hello_lib\u0026#34; } main.rs:\nextern crate hello_lib; 更新依赖 # # 更新所有依赖 cargo update # 更新指定依赖 cargo update rand 移除依赖 # cargo rm rand 清除编译 # cargo clean 生成文档 # cargo doc 测试 # cargo test workspace # 目前需要手动在 workspace 目录下创建 Cargo.toml 文件。\nvim Cargo.toml [workspace] resolver = \u0026#34;2\u0026#34; members = [ \u0026#34;hello_world\u0026#34;, \u0026#34;hello_lib\u0026#34;, ] 检查 workspace 下编译:\ncargo check --workspace 构建\n# 构建所有工作区成员 cargo build # 构建单个工作区成员 cargo build -p hello_world » Rust 开发环境配置\n"},{"id":197,"href":"/rust/dev-env-config/","title":"Dev Env Config","section":"Rust","content":" 🏠 首页 / Rust 编程 / Rust 开发环境配置\nRust 开发环境配置 # 安装 # Linux \u0026amp; Mac:\ncurl --proto \u0026#39;=https\u0026#39; --tlsv1.2 https://sh.rustup.rs -sSf | sh 一些常用的 Rust 包依赖于 C 代码,因此可能需要额外安装 C 编译器,在 Mac 上通过运行以下命令可以获得 C 编译器:\nxcode-select --install Ubuntu 上通过运行以下命令可以获得 C 编译器:\nsudo apt install build-essential 更新 # rustup update 卸载 # rustup self uninstall 配置命令补全 # 第一种方式,zsh 添加 rust 插件:\nvim ~/.zshrc 找到 plugins 配置位置,追加 rust:\nplugins=(... rust) 第二种方式:\n查看帮助:\nrustup completions --help 以 Ubuntu 为例,创建目录:\nmkdir ~/.zfunc 在 .zshrc 文件中添加内容:\nfpath+=~/.zfunc autoload -Uz compinit \u0026amp;\u0026amp; compinit 生成补全脚本:\nrustup completions zsh \u0026gt; ~/.zfunc/_rustup rustup completions zsh cargo \u0026gt; ~/.zfunc/_cargo 注销重新登录以生效,或者直接运行以下命令:\nexec zsh « Rust cargo 管理工具\n» Rust 入门\n"},{"id":198,"href":"/rust/getting-started/","title":"Getting Started","section":"Rust","content":" 🏠 首页 / Rust 编程 / Rust 入门\nRust 入门 # Rust 是一种系统编程语言,类似于 C 和 C++。它的设计目标是提供安全性和并发性,同时保持高性能。Rust 通过所有权系统来实现这些目标。\n安装 Rust # MacOS,linux 或其他类 Unix 系统用户可以直接在终端中运行以下命令安装 Rust:\ncurl --proto \u0026#39;=https\u0026#39; --tlsv1.2 -sSf https://sh.rustup.rs | sh Windows 用户可以在 Rust 官网 下载安装程序。\nHello, World # 让我们从一个简单的 \u0026ldquo;Hello, World!\u0026rdquo; 程序开始。创建一个新文件 main.rs 并输入以下内容:\nfn main() { println!(\u0026#34;Hello, World!\u0026#34;); } 要运行这个程序,使用 rustc 编译器:\nrustc main.rs \u0026amp;\u0026amp; ./main 执行后,你应该看到输出 Hello, World!。\n« Rust 开发环境配置\n» 查看根目录\n"},{"id":199,"href":"/rust/rust-programming/","title":"Rust Programming","section":"Rust","content":" 🏠 首页 / Rust 编程 / 查看根目录\nRust 编程\n信息 # # 查看根目录 rustc --print sysroot # 二进制程序位置 $(rustc --print sysroot)/bin # 源码位置 $(rustc --print sysroot)/lib/rustlib/src/ String 与 \u0026amp;str # String:字符串\n\u0026amp;str:字符串切片\nlet s: \u0026amp;str = \u0026#34;Hello World!\u0026#34;; let s1 = s.to_string(); let s1 = String::from(s); let s2 = \u0026amp;s1[..]; let s2 = s1.as_ref(); Panic # 设置 RUST_BACKTRACE=1 环境变量值,可以追踪到 panic 位置,例如:\n« Rust 入门\n» Rust VSCode 调试\n"},{"id":200,"href":"/rust/vscode-debugging/","title":"Vscode Debugging","section":"Rust","content":" 🏠 首页 / Rust 编程 / Rust VSCode 调试\nRust VSCode 调试 # 1. 安装插件 # CodeLLDB rust-analyzer 2. 配置 # 项目根目录配置 .vscode/launch.json,调试运行时打开 main.rs 文件。\n{ \u0026#34;version\u0026#34;: \u0026#34;0.2.0\u0026#34;, \u0026#34;configurations\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;lldb\u0026#34;, \u0026#34;request\u0026#34;: \u0026#34;launch\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Debug Rust Project\u0026#34;, \u0026#34;cargo\u0026#34;: { \u0026#34;args\u0026#34;: [ \u0026#34;build\u0026#34;, \u0026#34;--target-dir=${fileDirname}/../target\u0026#34;, \u0026#34;--manifest-path=${fileDirname}/../Cargo.toml\u0026#34; ] }, \u0026#34;args\u0026#34;: [], \u0026#34;cwd\u0026#34;: \u0026#34;${workspaceFolder}\u0026#34; }, { \u0026#34;type\u0026#34;: \u0026#34;lldb\u0026#34;, \u0026#34;request\u0026#34;: \u0026#34;launch\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Debug Rust Unit Tests\u0026#34;, \u0026#34;cargo\u0026#34;: { \u0026#34;args\u0026#34;: [ \u0026#34;test\u0026#34;, \u0026#34;--no-run\u0026#34;, \u0026#34;--target-dir=${fileDirname}/../target\u0026#34;, \u0026#34;--manifest-path=${fileDirname}/../Cargo.toml\u0026#34; ] }, \u0026#34;args\u0026#34;: [], \u0026#34;cwd\u0026#34;: \u0026#34;${workspaceFolder}\u0026#34; } ] } 支持 Wrokspace 下多 Rust 项目调试。\n« 查看根目录\n» Rust WASM 编程\n"},{"id":201,"href":"/rust/wasm-programming/","title":"Wasm Programming","section":"Rust","content":" 🏠 首页 / Rust 编程 / Rust WASM 编程\nRust WASM 编程 # 1. 初始化项目 # cargo new hello-wasm cd hello-wasm 2. 安装 wasm-pack # cargo install wasm-pack 3. 编写代码 # 编辑 src/main.rs 文件:\n// 使用 wasm-bindgen 在 Rust 与 JavaScript 之间通信 extern crate wasm_bindgen; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern { pub fn alert(s: \u0026amp;str); } #[wasm_bindgen] pub fn greet(name: \u0026amp;str){ alert(\u0026amp;format!(\u0026#34;Hello, {}!\u0026#34;,name)); } 编辑 Cargo.toml 文件:\n[package] name = \u0026#34;hello-wasm\u0026#34; version = \u0026#34;0.1.0\u0026#34; edition = \u0026#34;2021\u0026#34; authors = [\u0026#34;Pone Ding \u0026lt;poneding@gmail.com\u0026gt;\u0026#34;] description = \u0026#34;A sample project with wasm-pack\u0026#34; license = \u0026#34;MIT/Apache-2.0\u0026#34; repository = \u0026#34;https://github.com/poneding/hello-wasm\u0026#34; [lib] crate-type = [\u0026#34;cdylib\u0026#34;] [dependencies] wasm-bindgen = \u0026#34;0.2\u0026#34; 4. 构建项目 # wasm-pack build --scope [npm-username] « Rust VSCode 调试\n"}] \ No newline at end of file +[{"id":0,"href":"/algo/","title":"Algo","section":"","content":" 🏠 首页 / 数据结构与算法\n数据结构与算法 # 堆排序\n快速排序\n"},{"id":1,"href":"/algo/%E5%A0%86%E6%8E%92%E5%BA%8F/","title":"堆排序","section":"Algo","content":" 🏠 首页 / 数据结构与算法 / 堆排序\n堆排序 # 堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种**选择排序,**它的最坏,最好,平均时间复杂度均为 O(nlogn),它也是不稳定排序。首先简单了解下堆结构。\n堆 # 堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。:\n算法实现(golang) # package main import \u0026#34;fmt\u0026#34; type BinaryTreeNode struct { Value int Left, Right *BinaryTreeNode } func main() { tree := \u0026amp;BinaryTreeNode{ Left: \u0026amp;BinaryTreeNode{ Left: \u0026amp;BinaryTreeNode{ Value: 1, }, Right: \u0026amp;BinaryTreeNode{ Value: 2, }, Value: 3, }, Right: \u0026amp;BinaryTreeNode{ Value: 4, }, Value: 5, } res := HeapSort(tree) fmt.Println(res) } func HeapSort(tree *BinaryTreeNode) []int { var res []int if tree == nil { return []int{} } res = heapSortHelper(tree, res) return res } func heapSortHelper(tree *BinaryTreeNode, res []int) []int { if tree.Left == nil { res = append(res, tree.Value) } else { res = heapSortHelper(tree.Left, res) res = heapSortHelper(tree.Right, res) res = append(res, tree.Value) } return res } » 快速排序\n"},{"id":2,"href":"/algo/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F/","title":"快速排序","section":"Algo","content":" 🏠 首页 / 数据结构与算法 / 快速排序\n快速排序 # 步骤如下:\n先从数列中取出一个数作为基准数。一般取第一个数。 分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。 再对左右区间重复第二步,直到各区间只有一个数。 举一个例子:5 9 1 6 8 14 6 49 25 4 6 3。\n一般取第一个数 5 作为基准,从它左边和最后一个数使用[]进行标志, 如果左边的数比基准数大,那么该数要往右边扔,也就是两个[]数交换,这样大于它的数就在右边了,然后右边[]数左移,否则左边[]数右移。 5 [9] 1 6 8 14 6 49 25 4 6 [3] 因为 9 \u0026gt; 5,两个[]交换位置后,右边[]左移 5 [3] 1 6 8 14 6 49 25 4 [6] 9 因为 3 !\u0026gt; 5,两个[]不需要交换,左边[]右移 5 3 [1] 6 8 14 6 49 25 4 [6] 9 因为 1 !\u0026gt; 5,两个[]不需要交换,左边[]右移 5 3 1 [6] 8 14 6 49 25 4 [6] 9 因为 6 \u0026gt; 5,两个[]交换位置后,右边[]左移 5 3 1 [6] 8 14 6 49 25 [4] 6 9 因为 6 \u0026gt; 5,两个[]交换位置后,右边[]左移 5 3 1 [4] 8 14 6 49 [25] 6 6 9 因为 4 !\u0026gt; 5,两个[]不需要交换,左边[]右移 5 3 1 4 [8] 14 6 49 [25] 6 6 9 因为 8 \u0026gt; 5,两个[]交换位置后,右边[]左移 5 3 1 4 [25] 14 6 [49] 8 6 6 9 因为 25 \u0026gt; 5,两个[]交换位置后,右边[]左移 5 3 1 4 [49] 14 [6] 25 8 6 6 9 因为 49 \u0026gt; 5,两个[]交换位置后,右边[]左移 5 3 1 4 [6] [14] 49 25 8 6 6 9 因为 6 \u0026gt; 5,两个[]交换位置后,右边[]左移 5 3 1 4 [14] 6 49 25 8 6 6 9 两个[]已经汇总,因为 14 \u0026gt; 5,所以 5 和[]之前的数 4 交换位置 第一轮切分结果:4 3 1 5 14 6 49 25 8 6 6 9 现在第一轮快速排序已经将数列分成两个部分: 4 3 1 和 14 6 49 25 8 6 6 9 左边的数列都小于 5,右边的数列都大于 5。 使用递归分别对两个数列进行快速排序。 package main import \u0026#34;fmt\u0026#34; type BinaryTreeNode struct { Value int Left, Right *BinaryTreeNode } func main() { nums := []int{4, 7, 2, 9, 3, 1} quickSort(nums, 0, len(nums)-1) fmt.Println(nums) } func quickSort(nums []int, r, l int) { if r \u0026lt; l { loc := quickSortHelper(nums, r, l) quickSort(nums, r, loc-1) quickSort(nums, loc+1, l) } } func quickSortHelper(nums []int, r, l int) int { i := r + 1 j := l for i \u0026lt; j { if nums[i] \u0026gt; nums[r] { nums[i], nums[j] = nums[j], nums[i] j-- } else { i++ } } if nums[r] \u0026lt;= nums[i] { i-- } nums[r], nums[i] = nums[i], nums[r] return i } « 堆排序\n"},{"id":3,"href":"/aws/","title":"Aws","section":"","content":" 🏠 首页 / AWS\nAWS # 搭建EKS集群\nCluster AutoScaler\n创建 EKS 集群\nEKS配置 ALB Ingress\nEKS小细节汇总\nEKS实践 集成Gitlab自动发布(一)\nEKS实践 集成Gitlab自动发布(二)\nEKS-使用EFS\nGitlab \u0026amp; EKS\nK8s 部署 Kong 服务\nK8s 部署 konga\nK8s 部署 Postgres\nTerraform 重新管理资源\n"},{"id":4,"href":"/aws/build-eks-cluster/","title":"Build Eks Cluster","section":"Aws","content":" 🏠 首页 / AWS / 搭建EKS集群\n搭建EKS集群 # 安装aws,kubectl和eksctl命令行工具 # 引言 # 安装aws cli 安装kubectl cli 安装eksctl cli 安装aws cli # 参考 # https://docs.aws.amazon.com/zh_cn/cli/latest/userguide/cli-chap-install.html\n安装示例(windows) # 下载安装包: https://awscli.amazonaws.com/AWSCLIV2.msi 运行下载的安装包 确实安装是否成功 aws --version 使用Security Credentials配置aws cli # 访问aws控制台:Service =\u0026gt; IAM 选择IAM User,使用子用户,强烈不建议使用root用户 进入用户详情页面,Security Credentials页 创建Access Key 拷贝Access Key ID和Secret Access Key 使用aws命令配置 $ aws configure AWS Access Key ID [None]: ABCDEFGHIAZBERTUCNGG (替换Access Key ID) AWS Secret Access Key [None]: uMe7fumK1IdDB094q2sGFhM5Bqt3HQRw3IHZzBDTm (替换Secret Access Key) Default region name [None]: us-east-1 Default output format [None]: json 测试配置是否生效 aws ec2 describe-vpcs 卸载(windows) # 控制面板 =\u0026gt; 程序和功能,找到aws cli,卸载即可。 安装kubectl cli # 如果你确实是使用EKS的Kubernetes,建议使用aws提供的kubectl命令工具。\n参考 # AWS\nhttps://docs.aws.amazon.com/zh_cn/eks/latest/userguide/install-kubectl.html\n官方\nhttps://kubernetes.io/docs/tasks/tools/\n安装示例(windows) # 下载安装包,示例中下载路径:C:\\apps\\kubectl curl -o kubectl.exe https://amazon-eks.s3.us-west-2.amazonaws.com/1.16.8/2020-04-16/bin/windows/amd64/kubectl.exe 更新系统PATH环境变量,添加C:\\apps\\kubectl\n验证kubectl版本\nkubectl version --client 卸载(windows) # 直接删除程序文件以及PATH环境变量即可。\n安装eksctl cli # 参考 # https://docs.aws.amazon.com/zh_cn/eks/latest/userguide/eksctl.html\n安装示例(windows) # 卸载(windows) # EKS最佳实践:\nhttps://aws.github.io/aws-eks-best-practices/security/docs/iam/\n» Cluster AutoScaler\n"},{"id":5,"href":"/aws/cluster-autoscaler/","title":"Cluster Autoscaler","section":"Aws","content":" 🏠 首页 / AWS / Cluster AutoScaler\nCluster AutoScaler # 我当前已经有了一个EKS服务搭建起来的K8s集群,我现在希望我的集群拥有自动伸缩(体现在节点的扩缩)的能力。\n当我的集群资源充足,而我部署在集群中的应用只使用到了很少量的资源,我希望集群回收资源以节省费用;当我的应用服务越来越多,当前集群资源不足时,我希望集群能增加节点,以满足应用的部署条件。\n那么本篇就是介绍如何通过使用Kubernetes Cluster Autoscaler让你的集群拥有自动伸缩的能力,而不用你时刻关注集群的资源是否过于宽松或紧张。\nNodeGroup添加Tag # 我们创建EKS时,需要定义NodeGroup,一般在这个NodeGroup中定义:\nasg_desired_capacity:期望创建的Node数量;\nasg_max_size:最小Node数量,默认为1;\nasg_min_size:最大Node数量。\n定义的NodeGroup会生成Auto Scaling Group资源,并且由Auto Scaling Group来管理Node的创建。\n现在需要做的就是为你的Auto Scaling Group添加Tag。\ntag键值:\nTag Key Tag Value k8s.io/cluster-autoscaler/\u0026lt;your_cluster_name\u0026gt; owned k8s.io/cluster-autoscaler/enabled true 第一个Tag Key中的集群名\u0026lt;your_cluster_name\u0026gt;需要替换;\nTag Value的值是什么不重要,主要是需要这两个Tag Key来识别是否对这个集群开启Auto Scaling的能力。\n添加Policy # 创建IAM策略,将新创建的策略Attach到集群节点绑定的IAM role上,让你的集群节点拥有自动伸缩的能力。\nIAM策略Json内容:\n{ \u0026#34;Version\u0026#34;: \u0026#34;2012-10-17\u0026#34;, \u0026#34;Statement\u0026#34;: [ { \u0026#34;Action\u0026#34;: [ \u0026#34;autoscaling:DescribeAutoScalingGroups\u0026#34;, \u0026#34;autoscaling:DescribeAutoScalingInstances\u0026#34;, \u0026#34;autoscaling:DescribeLaunchConfigurations\u0026#34;, \u0026#34;autoscaling:DescribeTags\u0026#34;, \u0026#34;autoscaling:SetDesiredCapacity\u0026#34;, \u0026#34;autoscaling:TerminateInstanceInAutoScalingGroup\u0026#34;, \u0026#34;ec2:DescribeLaunchTemplateVersions\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34;, \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34; } ] } 部署Cluster AutoScaler # 在集群中部署Cluster AutoScaler,准备资源清单文件cluster-autoscaler.yaml:\n--- apiVersion: v1 kind: ServiceAccount metadata: labels: k8s-addon: cluster-autoscaler.addons.k8s.io k8s-app: cluster-autoscaler name: cluster-autoscaler namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: cluster-autoscaler labels: k8s-addon: cluster-autoscaler.addons.k8s.io k8s-app: cluster-autoscaler rules: - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;events\u0026#34;, \u0026#34;endpoints\u0026#34;] verbs: [\u0026#34;create\u0026#34;, \u0026#34;patch\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;pods/eviction\u0026#34;] verbs: [\u0026#34;create\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;pods/status\u0026#34;] verbs: [\u0026#34;update\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;endpoints\u0026#34;] resourceNames: [\u0026#34;cluster-autoscaler\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;update\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;nodes\u0026#34;] verbs: [\u0026#34;watch\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;get\u0026#34;, \u0026#34;update\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: - \u0026#34;pods\u0026#34; - \u0026#34;services\u0026#34; - \u0026#34;replicationcontrollers\u0026#34; - \u0026#34;persistentvolumeclaims\u0026#34; - \u0026#34;persistentvolumes\u0026#34; verbs: [\u0026#34;watch\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;get\u0026#34;] - apiGroups: [\u0026#34;extensions\u0026#34;] resources: [\u0026#34;replicasets\u0026#34;, \u0026#34;daemonsets\u0026#34;] verbs: [\u0026#34;watch\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;get\u0026#34;] - apiGroups: [\u0026#34;policy\u0026#34;] resources: [\u0026#34;poddisruptionbudgets\u0026#34;] verbs: [\u0026#34;watch\u0026#34;, \u0026#34;list\u0026#34;] - apiGroups: [\u0026#34;apps\u0026#34;] resources: [\u0026#34;statefulsets\u0026#34;, \u0026#34;replicasets\u0026#34;, \u0026#34;daemonsets\u0026#34;] verbs: [\u0026#34;watch\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;get\u0026#34;] - apiGroups: [\u0026#34;storage.k8s.io\u0026#34;] resources: [\u0026#34;storageclasses\u0026#34;, \u0026#34;csinodes\u0026#34;] verbs: [\u0026#34;watch\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;get\u0026#34;] - apiGroups: [\u0026#34;batch\u0026#34;, \u0026#34;extensions\u0026#34;] resources: [\u0026#34;jobs\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;watch\u0026#34;, \u0026#34;patch\u0026#34;] - apiGroups: [\u0026#34;coordination.k8s.io\u0026#34;] resources: [\u0026#34;leases\u0026#34;] verbs: [\u0026#34;create\u0026#34;] - apiGroups: [\u0026#34;coordination.k8s.io\u0026#34;] resourceNames: [\u0026#34;cluster-autoscaler\u0026#34;] resources: [\u0026#34;leases\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;update\u0026#34;] --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: cluster-autoscaler namespace: kube-system labels: k8s-addon: cluster-autoscaler.addons.k8s.io k8s-app: cluster-autoscaler rules: - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;configmaps\u0026#34;] verbs: [\u0026#34;create\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;watch\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;configmaps\u0026#34;] resourceNames: [\u0026#34;cluster-autoscaler-status\u0026#34;, \u0026#34;cluster-autoscaler-priority-expander\u0026#34;] verbs: [\u0026#34;delete\u0026#34;, \u0026#34;get\u0026#34;, \u0026#34;update\u0026#34;, \u0026#34;watch\u0026#34;] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: cluster-autoscaler labels: k8s-addon: cluster-autoscaler.addons.k8s.io k8s-app: cluster-autoscaler roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-autoscaler subjects: - kind: ServiceAccount name: cluster-autoscaler namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: cluster-autoscaler namespace: kube-system labels: k8s-addon: cluster-autoscaler.addons.k8s.io k8s-app: cluster-autoscaler roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: cluster-autoscaler subjects: - kind: ServiceAccount name: cluster-autoscaler namespace: kube-system --- apiVersion: apps/v1 kind: Deployment metadata: name: cluster-autoscaler namespace: kube-system labels: app: cluster-autoscaler spec: replicas: 1 selector: matchLabels: app: cluster-autoscaler template: metadata: labels: app: cluster-autoscaler annotations: prometheus.io/scrape: \u0026#34;true\u0026#34; prometheus.io/port: \u0026#34;8085\u0026#34; cluster-autoscaler.kubernetes.io/safe-to-evict: \u0026#34;false\u0026#34; spec: serviceAccountName: cluster-autoscaler containers: - image: us.gcr.io/k8s-artifacts-prod/autoscaling/cluster-autoscaler:v1.16.5 name: cluster-autoscaler resources: limits: cpu: 100m memory: 300Mi requests: cpu: 100m memory: 300Mi command: - ./cluster-autoscaler - --v=4 - --stderrthreshold=info - --cloud-provider=aws - --skip-nodes-with-local-storage=false - --expander=least-waste - --node-group-auto-discovery=asg:tag=k8s.io/cluster-autoscaler/enabled,k8s.io/cluster-autoscaler/\u0026lt;your_cluster_name\u0026gt; - --balance-similar-node-groups - --skip-nodes-with-system-pods=false volumeMounts: - name: ssl-certs mountPath: /etc/ssl/certs/ca-certificates.crt readOnly: true imagePullPolicy: \u0026#34;Always\u0026#34; volumes: - name: ssl-certs hostPath: path: \u0026#34;/etc/ssl/certs/ca-bundle.crt\u0026#34; 资源清单内容比较长,但是需要注意的是 cluster-autoscaler 的 Deployment 资源中定义的 command 中,有一行你会觉得眼熟,没错就是我们上面为 Auto Scale Group 添加的 Tag,这里你也需要替换你的集群名 \u0026lt;your_cluster_name\u0026gt;,此外你可能需要替换使用到的镜像,你可以在 Cluster AutoScaler 发版页面去找对应的镜像版本。例如我的K8s集群是1.16,那么我需要找一个1.16.x版本的镜像。上面资源清单里就是使用了 us.gcr.io/k8s-artifacts-prod/autoscaling/cluster-autoscaler:v1.16.5 的镜像。\n使用下面的命令创建 Cluster AutoScaler 资源:\nkubectl apply -f cluster-autoscaler.yaml 查看Cluster AutoScaler # 我们部署完成之后,Cluster 默认每 10s 检查一次集群的资源情况,并根据资源情况判断是否需要扩缩容。\n使用命令查看Cluster AutoScaler的检查情况:\nkubectl logs -f deployment.apps/cluster-autoscaler -n kube-system 分析原理 # 你可以通过突然增加Pod示例数量,并且增加 Pod 的 resource.request.cpu/memory 设置为较高的值,这样来模拟集群资源紧张的场景,观察集群节点是否会自动扩容,之后将增加的 Pod 资源删除,再观察集群节点是否自动回收。这里我已经实践过了,由于演示起来比较复杂,可能会耗费很长的篇幅,所以这里就省去了,你可以亲自动手实践尝试。\n相信有一个疑问一直环绕着,Cluster AutoScaler是怎么做到自动伸缩的呢,我们也没有给出类似于CPU超过75%就扩容的伸缩条件。我没有深入去研究,但是我自己根据Cluster AutoScaler的日志输出理解了一下。\nPod的申请的资源(resources.requests)与现有节点资源作比较,如果发现集群资源超出申请的资源能够使得我们移除一个节点的话,那么Cluster AutoScaler便会修改Auto Scaling Group的asg_desired_capacity值,例如-1,但是不会小于asg_max_size。当有新Pod部署,发现集群资源达不到新Pod申请的资源时,那么Cluster AutoScaler便会修改Auto Scaling Group的asg_desired_capacity值,例如+1,但是不会大于asg_max_size。只要修改了Auto Scaling Group的asg_desired_capacity值,那么集群节点便会自动伸缩。\n« 搭建EKS集群\n» 创建 EKS 集群\n"},{"id":6,"href":"/aws/create-eks-cluster/","title":"Create Eks Cluster","section":"Aws","content":" 🏠 首页 / AWS / 创建 EKS 集群\n创建 EKS 集群 # 1. EKS简介 # Amazon Elastic Kubernetes Service (Amazon EKS) 是一项托管服务,可让您在 AWS 上轻松运行 Kubernetes,而无需支持或维护您自己的 Kubernetes 控制层面。Kubernetes 是一个用于实现容器化应用程序的部署、扩展和管理的自动化的开源系统。(该段介绍来自Amazon EKS文档,更多了解 https://docs.aws.amazon.com/zh_cn/eks/latest/userguide/what-is-eks.html)\n2. eksctl创建eks集群 # 2.1 什么是eksctl # eksctl是一种用于在 Amazon EKS 上创建和管理 Kubernetes 集群的简单命令行实用程序。eksctl 命令行实用程序提供了使用工作线程节点为 Amazon EKS 创建新集群的最快、最简单的方式。\neksctl更多了解 https://eksctl.io\n2.2 为什么用eksctl # 创建EKS集群可以在AWS的控制台创建,也可以使用AWS开发的eksctl工具创建,为什么选择使用eksctl创建eks集群呢,有以下几点原因:\n直接在AWS的控制台创建集群,需要手动创建各种Role,以及选择合适的Subnet,Security Group等繁杂操作,你需要在浏览器中打开多个页面,操作过程可能也要时不时参阅文档; eksctl创建EKS集群只需要一行eksctl create cluster \u0026lt;参数\u0026gt;命令即可,会自动的给你创建Role等资源; eksctl的命令可以记录到脚本,便于复用。 2.3 安装eksctl(基于ubuntu) # 使用以下命令下载并提取最新版本的 eksctl。 curl --silent --location \u0026#34;https://github.com/weaveworks/eksctl/releases/download/latest_release/eksctl_$(uname -s)_amd64.tar.gz\u0026#34; | tar xz -C /tmp 将提取的二进制文件移至 /usr/local/bin。 sudo mv /tmp/eksctl /usr/local/bin 使用以下命令测试您的安装是否成功。 eksctl version 注意:\nGitTag 版本应至少为 0.11.1。否则,请检查您的终端输出是否有任何安装或升级错误。\n2.4 使用eksctl创建集群 # 准备工作:\n一台拥有创建eks权限的机器 已经安装好eksctl eksctl create cluster \\ --name prod-eks \\ --version 1.14 \\ --region us-east-1 \\ --nodegroup-name prod-eks-workers \\ --node-type t3.medium \\ --nodes 2 \\ --nodes-min 2 \\ --nodes-max 4 \\ --ssh-access \\ --ssh-public-key -eks-public-key \\ --managed --ssh-public-key 可选的,但建议您在创建包含节点组的集群时指定该选项。通过此选项,可以对托管节点组中的节点进行 SSH 访问。启用 SSH 访问后,如果出现问题,您可以连接到实例并收集诊断信息。\n注意:在us-east-1区中,创建eks集群时您可能会遇到UnsupportedAvailabilityZoneException类型的异常。如果遇到这种情况,可以传递zones参数,例如,\neksctl create cluster \\ --name prod-eks \\ --version 1.14 \\ --region us-east-1 \\ --zones us-east-1a,us-east-1b \\ --nodegroup-name prod-eks-workers \\ --node-type t3.medium \\ --nodes 2 \\ --nodes-min 2 \\ --nodes-max 4 \\ --ssh-access \\ --ssh-public-key prod-eks-public-key \\ --managed 以上创建eks集群的命令会大致输出以下内容,集群的整个创建过程可能会花费10到15分钟:\n[ℹ] CloudWatch logging will not be enabled for cluster \u0026#34;prod-eks\u0026#34; in \u0026#34;us-east-1\u0026#34; [ℹ] you can enable it with \u0026#39;eksctl utils update-cluster-logging --region=us-east-1 --cluster=prod-eks\u0026#39; [ℹ] Kubernetes API endpoint access will use default of {publicAccess=true, privateAccess=false} for cluster \u0026#34;prod-eks\u0026#34; in \u0026#34;us-east-1\u0026#34; [ℹ] 2 sequential tasks: { create cluster control plane \u0026#34;prod-eks\u0026#34;, create managed nodegroup \u0026#34;prod-eks-workers\u0026#34; } [ℹ] building cluster stack \u0026#34;eksctl-prod-eks-cluster\u0026#34; [ℹ] deploying stack \u0026#34;eksctl-prod-eks-cluster\u0026#34; [ℹ] building managed nodegroup stack \u0026#34;eksctl-prod-eks-nodegroup-prod-eks-workers\u0026#34; [ℹ] deploying stack \u0026#34;eksctl-prod-eks-nodegroup-prod-eks-workers\u0026#34; [✔] all EKS cluster resources for \u0026#34;prod-eks\u0026#34; have been created ... 如果输出中存在 could not find any of the authenticator commands: aws-iam-authenticator, heptio-authenticator-aws,则需要安装aws-iam-authenticator,使用以下命令:\ncurl -o aws-iam-authenticator https://amazon-eks.s3-us-west-2.amazonaws.com/1.14.6/2019-08-22/bin/linux/amd64/aws-iam-authenticator chmod +x ./aws-iam-authenticator mkdir -p $HOME/bin \u0026amp;\u0026amp; cp ./aws-iam-authenticator $HOME/bin/aws-iam-authenticator \u0026amp;\u0026amp; export PATH=$PATH:$HOME/bin echo \u0026#39;export PATH=$PATH:$HOME/bin\u0026#39; \u0026gt;\u0026gt; ~/.bashrc aws-iam-authenticator help 如果本次操作机器没有安装kubectl,可以使用以下命令安装,如果已经安装则跳过:\nsudo apt-get update \u0026amp;\u0026amp; sudo apt-get install -y apt-transport-https curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - echo \u0026#34;deb https://apt.kubernetes.io/ kubernetes-xenial main\u0026#34; | sudo tee -a /etc/apt/sources.list.d/kubernetes.list sudo apt-get update sudo apt-get install -y kubectl 可以通过kubectl命令操作本次创建的集群:\nkubectl get svc 输出如下,可以说明eks集群已经成功创建了。\nNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.100.0.1 \u0026lt;none\u0026gt; 443/TCP 31m 3. 部署 Kubernetes Metrics Server # 打开终端窗口,导航到您要下载最新 metrics-server 版本的目录。\n将以下命令复制并粘贴到您的终端窗口,然后键入 Enter 执行这些命令。这些命令将下载最新版本,提取它,然后在集群中应用版本 1.8+ 清单。\nDOWNLOAD_URL=$(curl -Ls \u0026#34;https://api.github.com/repos/kubernetes-sigs/metrics-server/releases/latest\u0026#34; | jq -r .tarball_url) DOWNLOAD_VERSION=$(grep -o \u0026#39;[^/v]*$\u0026#39; \u0026lt;\u0026lt;\u0026lt; $DOWNLOAD_URL) curl -Ls $DOWNLOAD_URL -o metrics-server-$DOWNLOAD_VERSION.tar.gz mkdir metrics-server-$DOWNLOAD_VERSION tar -xzf metrics-server-$DOWNLOAD_VERSION.tar.gz --directory metrics-server-$DOWNLOAD_VERSION --strip-components 1 kubectl apply -f metrics-server-$DOWNLOAD_VERSION/deploy/1.8+/ 使用以下命令验证 metrics-server 部署是否运行所需数量的 Pod: kubectl get deployment metrics-server -n kube-system 输出:\nNAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE metrics-server 1 1 1 1 56m eksctl create cluster --name dev-devops-eks2 --version 1.14 --region ap-southeast-1 --nodegroup-name dev-devops-eks2-workers --node-type t3.medium --nodes 3 --nodes-min 1 --nodes-max 4 --ssh-access --ssh-public-key dev-devops-eks2-public-key --managed EKS Node 默认给的Role权限需要attch s3、secrets manager的权限。\n« Cluster AutoScaler\n» EKS配置 ALB Ingress\n"},{"id":7,"href":"/aws/eks-config-alb-ingress/","title":"Eks Config Alb Ingress","section":"Aws","content":" 🏠 首页 / AWS / EKS配置 ALB Ingress\nEKS配置 ALB Ingress # 官方文档: https://kubernetes-sigs.github.io/aws-load-balancer-controller/latest/guide/controller/installation/\n部署Alb Ingress Controller # IAM中创建Policy,给集群的Node节点的Role添加该Policy。\nPolicy的JSON配置如下:\n{ \u0026#34;Version\u0026#34;: \u0026#34;2012-10-17\u0026#34;, \u0026#34;Statement\u0026#34;: [ { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;acm:DescribeCertificate\u0026#34;, \u0026#34;acm:ListCertificates\u0026#34;, \u0026#34;acm:GetCertificate\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;ec2:AuthorizeSecurityGroupIngress\u0026#34;, \u0026#34;ec2:CreateSecurityGroup\u0026#34;, \u0026#34;ec2:CreateTags\u0026#34;, \u0026#34;ec2:DeleteTags\u0026#34;, \u0026#34;ec2:DeleteSecurityGroup\u0026#34;, \u0026#34;ec2:DescribeAccountAttributes\u0026#34;, \u0026#34;ec2:DescribeAddresses\u0026#34;, \u0026#34;ec2:DescribeInstances\u0026#34;, \u0026#34;ec2:DescribeInstanceStatus\u0026#34;, \u0026#34;ec2:DescribeInternetGateways\u0026#34;, \u0026#34;ec2:DescribeNetworkInterfaces\u0026#34;, \u0026#34;ec2:DescribeSecurityGroups\u0026#34;, \u0026#34;ec2:DescribeSubnets\u0026#34;, \u0026#34;ec2:DescribeTags\u0026#34;, \u0026#34;ec2:DescribeVpcs\u0026#34;, \u0026#34;ec2:ModifyInstanceAttribute\u0026#34;, \u0026#34;ec2:ModifyNetworkInterfaceAttribute\u0026#34;, \u0026#34;ec2:RevokeSecurityGroupIngress\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;elasticloadbalancing:AddListenerCertificates\u0026#34;, \u0026#34;elasticloadbalancing:AddTags\u0026#34;, \u0026#34;elasticloadbalancing:CreateListener\u0026#34;, \u0026#34;elasticloadbalancing:CreateLoadBalancer\u0026#34;, \u0026#34;elasticloadbalancing:CreateRule\u0026#34;, \u0026#34;elasticloadbalancing:CreateTargetGroup\u0026#34;, \u0026#34;elasticloadbalancing:DeleteListener\u0026#34;, \u0026#34;elasticloadbalancing:DeleteLoadBalancer\u0026#34;, \u0026#34;elasticloadbalancing:DeleteRule\u0026#34;, \u0026#34;elasticloadbalancing:DeleteTargetGroup\u0026#34;, \u0026#34;elasticloadbalancing:DeregisterTargets\u0026#34;, \u0026#34;elasticloadbalancing:DescribeListenerCertificates\u0026#34;, \u0026#34;elasticloadbalancing:DescribeListeners\u0026#34;, \u0026#34;elasticloadbalancing:DescribeLoadBalancers\u0026#34;, \u0026#34;elasticloadbalancing:DescribeLoadBalancerAttributes\u0026#34;, \u0026#34;elasticloadbalancing:DescribeRules\u0026#34;, \u0026#34;elasticloadbalancing:DescribeSSLPolicies\u0026#34;, \u0026#34;elasticloadbalancing:DescribeTags\u0026#34;, \u0026#34;elasticloadbalancing:DescribeTargetGroups\u0026#34;, \u0026#34;elasticloadbalancing:DescribeTargetGroupAttributes\u0026#34;, \u0026#34;elasticloadbalancing:DescribeTargetHealth\u0026#34;, \u0026#34;elasticloadbalancing:ModifyListener\u0026#34;, \u0026#34;elasticloadbalancing:ModifyLoadBalancerAttributes\u0026#34;, \u0026#34;elasticloadbalancing:ModifyRule\u0026#34;, \u0026#34;elasticloadbalancing:ModifyTargetGroup\u0026#34;, \u0026#34;elasticloadbalancing:ModifyTargetGroupAttributes\u0026#34;, \u0026#34;elasticloadbalancing:RegisterTargets\u0026#34;, \u0026#34;elasticloadbalancing:RemoveListenerCertificates\u0026#34;, \u0026#34;elasticloadbalancing:RemoveTags\u0026#34;, \u0026#34;elasticloadbalancing:SetIpAddressType\u0026#34;, \u0026#34;elasticloadbalancing:SetSecurityGroups\u0026#34;, \u0026#34;elasticloadbalancing:SetSubnets\u0026#34;, \u0026#34;elasticloadbalancing:SetWebACL\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;iam:CreateServiceLinkedRole\u0026#34;, \u0026#34;iam:GetServerCertificate\u0026#34;, \u0026#34;iam:ListServerCertificates\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;cognito-idp:DescribeUserPoolClient\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;waf-regional:GetWebACLForResource\u0026#34;, \u0026#34;waf-regional:GetWebACL\u0026#34;, \u0026#34;waf-regional:AssociateWebACL\u0026#34;, \u0026#34;waf-regional:DisassociateWebACL\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;tag:GetResources\u0026#34;, \u0026#34;tag:TagResources\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;waf:GetWebACL\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;shield:DescribeProtection\u0026#34;, \u0026#34;shield:GetSubscriptionState\u0026#34;, \u0026#34;shield:DeleteProtection\u0026#34;, \u0026#34;shield:CreateProtection\u0026#34;, \u0026#34;shield:DescribeSubscription\u0026#34;, \u0026#34;shield:ListProtections\u0026#34; ], \u0026#34;Resource\u0026#34;: \u0026#34;*\u0026#34; } ] } 下载alb-ingress-controller.yaml\nwget https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.6/docs/examples/alb-ingress-controller.yaml 修改alb-ingress-controller.yaml,修改以下三个配置项\n--cluster-name=dev-eks\n--aws-vpc-id=vpc-xxxxxx\n--aws-region=us-west-1\n部署:\nkubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.6/docs/examples/rbac-role.yaml kubectl apply -f alb-ingress-controller.yaml 验证部署情况:\nkubectl logs -n kube-system $(kubectl get po -n kube-system | egrep -o \u0026#34;alb-ingress[a-zA-Z0-9-]+\u0026#34;) 配置Alb Ingress Controller # 设置External DNS # 本篇内容来源: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v1.1/guide/external-dns/setup/\n背景 # 在AWS中,如果我们希望以某个域名访问K8s集群的某个服务,我们可以通过维护Route 53中Record Set和LoadBalancer映射来实现,这无疑会增加一些手动操作的成本,本篇即介绍使用external-dns来自动维护它们之间的关系。\nexternal-dns项目地址: https://github.com/kubernetes-incubator/external-dns\nexternal-dns根据host的信息提供DNS记录,它将建立和管理Route 53中Record Set与LoadBalancer的映射关系。\n前提条件 # 角色权限 # 必须给K8s集群运行external-dns的Node配置角色和策略。\nIAM中创建Policy,给集群的Node节点的Role添加该Policy。\nPolicy的JSON配置如下:\n{ \u0026#34;Version\u0026#34;: \u0026#34;2012-10-17\u0026#34;, \u0026#34;Statement\u0026#34;: [ { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;route53:ChangeResourceRecordSets\u0026#34; ], \u0026#34;Resource\u0026#34;: [ \u0026#34;arn:aws:route53:::hostedzone/*\u0026#34; ] }, { \u0026#34;Effect\u0026#34;: \u0026#34;Allow\u0026#34;, \u0026#34;Action\u0026#34;: [ \u0026#34;route53:ListHostedZones\u0026#34;, \u0026#34;route53:ListResourceRecordSets\u0026#34; ], \u0026#34;Resource\u0026#34;: [ \u0026#34;*\u0026#34; ] } ] } 安装 # 下载external-dns.yaml示例文件 wget https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/v1.1.6/docs/examples/external-dns.yaml 修改external-dns.yaml文件,主要修改``\u0026ndash;domain-filter`这个配置内容 --domain-filter=example.com 部署external-dns kubectl apply -f external-dns.yaml 验证部署是否成功和实时记录 kubectl logs -f $(kubectl get po | egrep -o \u0026#39;external-dns[A-Za-z0-9-]+\u0026#39;) 使用方式 # 为了在子域名中建立Record Set,需要在Ingress或者Service资源中添加如下注释: annotations: # for creating record-set external-dns.alpha.kubernetes.io/hostname: nginx.example.com # give your domain name here 如果是service,需要将type设置成LoadBalancer\n查看实时日志,2分钟后在Route 53中查看记录是否生成或更新。 实践记录 # Pod内容器使用Secrets Manager,需要给Node的Role添加相应的权限; « 创建 EKS 集群\n» EKS小细节汇总\n"},{"id":8,"href":"/aws/eks-details/","title":"Eks Details","section":"Aws","content":" 🏠 首页 / AWS / EKS小细节汇总\nEKS小细节汇总 # 如果alb的ingress使用了自定义的security group,那么需要将该安全组加入到worker\n« EKS配置 ALB Ingress\n» EKS实践 集成Gitlab自动发布(一)\n"},{"id":9,"href":"/aws/eks-intergrate-gitlab-auto-release-01/","title":"Eks Intergrate Gitlab Auto Release 01","section":"Aws","content":" 🏠 首页 / AWS / EKS实践 集成Gitlab自动发布(一)\nEKS实践 集成Gitlab自动发布(一) # 系列介绍如何使用Gitlab CI/CD自动部署应用到EKS(K8s)集群中。本篇介绍如何在EKS(K8s)集群中为Gitlab的CI/CD创建Gitlab Runner。\nGitlab添加K8s集群 # 添加方式 # 第一种方式,基于单个仓库添加K8s集群:\n进入Gitlab仓库,依次从左边菜单栏Operations =\u0026gt; Kubernetes进入添加页面,点击Add Kubernetes cluster按钮。这种方式添加的K8s集群只对该项目仓库有效。\n第二种方式,基于Group添加K8s集群:\n进入Gitlab主页,依次从上边菜单栏Groups =\u0026gt; Your groups,选择Group进入页面,然后依次从左边菜单栏Kuberentes进入添加页面,点击Add Kubernetes cluster。这种方式添加的K8s集群对该Group下的项目仓库有效。\n第三种方式,基于全局添加K8s集群:\n这种方式需要用到gitlab的root权限。进入Gitlab主页,从上边菜单栏Admin Area(扳手图标) 进入页面,然后依次从左边菜单栏Kuberentes进入添加页面,点击Add Kubernetes cluster。这种方式添加的K8s集群对所有项目仓库有效。\n添加步骤 # 添加已有的K8s集群,按照如下步骤获取到对应的值填入表单即可。\nCluster Name: 这个可以自定义,能自行区分就行。\nAPI URL: 运行以下命令得到输出值:\nkubectl cluster-info | grep \u0026#39;Kubernetes master\u0026#39; | awk \u0026#39;/http/ {print $NF}\u0026#39; CA Certificate: 运行以下命令得到输出值:\nkubectl get secret $(kubectl get secret | grep default-token | awk \u0026#39;{print $1}\u0026#39;) -o jsonpath=\u0026#34;{[\u0026#39;data\u0026#39;][\u0026#39;ca\\.crt\u0026#39;]}\u0026#34; | base64 --decode Service Token: 创建文件gitlab-admin-service-account.yaml:\nvim gitlab-admin-service-account.yaml 文件写入如下内容:\napiVersion: v1 kind: ServiceAccount metadata: name: gitlab-admin namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: gitlab-admin roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: gitlab-admin namespace: kube-system 运行以下命令得到输出值:\nkubectl apply -f gitlab-admin-service-account.yaml kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep gitlab-admin | awk \u0026#39;{print $1}\u0026#39;) 安装Helm以及Runner # 添加完K8s集群之后,在集群中安装Helm Tiller。\n安装完Helm Tiller之后,再安装Helm TIller下面的Runner。\n之后,你可以发现K8s资源中多了一个gitlab-managed-apps命令空间,并且在该命名空间下,包含了tiller和runner的一些资源。\n安装Gitlab Runner # 上面介绍了Gitlab集成EKS,并安装Gitlab Runner的操作过程,其实也可以完全不用这种方法。下面介绍另一种安装Gitlab Runner的方法。\n在Group或项目仓库下都可以,Settings =\u0026gt; CI / CD =\u0026gt; Runners 获取到Url和Token。\n从这里下载values.yaml文件: https://gitlab.com/gitlab-org/charts/gitlab-runner/blob/master/values.yaml\n下载完之后,将上面获取到的Url和Token替换文件中gitlabUrl和runnerRegistrationToken值。\n然后使用helm命令安装Gitlab Runner:\nkubectl create -n gitlab-managed-apps # 手动创建命名空间 helm install -n gitlab-managed-apps \u0026lt;gitlab-runner-name\u0026gt; -f values.yaml gitlab/gitlab-runner # 删除gitlab runner # helm uninstall -n gitlab-managed-apps \u0026lt;gitlab-runner-name\u0026gt; 在安装了之后,查看安装的Gitlab Runner:\n此时,进入Gitlab Group页面 Setting =\u0026gt; CI / CD =\u0026gt; Runners下可以看到有一个可用的Group Runner。\n一些经验 # 要求Gitlab的服务和K8s可以互相访问,也就是网络是连通的;\n重复添加集群安装Helm Tiller\n同一个K8s集群可以被同一个Gitlab服务多次添加,但是Helm Tiller和Runner是不能多次安装的,这是因为安装Helm Tiller和Gitlab Runner创建K8s资源(gitlab-managed-apps命名空间下的Deployment)会冲突。\nGitlab Runner的作用域\n一个项目可以选择使用多个Gitlab Runner:\nSpecific Runners:基于当前项目添加的K8s集群中安装的Gitlab Runner\nShared Runners:Root权限基于全局添加的K8s集群中安装的Gitlab Runner\nGroup Runners:当前项目所属Group添加的K8s集群中安装的Gitlab Runner\n如果使用Root权限基于全局添加了一个K8s集群,并且安装了Gitlab Runner之后,随便进入一个项目仓库 Settings =\u0026gt; CI / CD =\u0026gt; Runners下面可以看到Shared Runners下面会有一个可用的Runner,如下图所示。你可能注意到了这个Runner默认有两个Tag:cluster,kubernetes,这两个Tag的作用下次将会使用到。\n结束语 # 本篇介绍将EKS(K8s)集群添加到项目仓库中,并且安装所需的Helm Tiller和GItlab Runner。\nHelm是K8s的类似包管理工具,Helm的各类Charts可以帮助我们快速的定义,安装和升级K8s应。Helm对K8s来说是一个客户端,它的服务端则是安装在K8s中的Tiller。\n可以使用Helm在K8s集群中安装Gitlab Runner,使用Gitlab Runner实现Gitlab 自动CI/CD,前面我们的步骤就是使用Helm安装GItlab Runner。\n« EKS小细节汇总\n» EKS实践 集成Gitlab自动发布(二)\n"},{"id":10,"href":"/aws/eks-intergrate-gitlab-auto-release-02/","title":"Eks Intergrate Gitlab Auto Release 02","section":"Aws","content":" 🏠 首页 / AWS / EKS实践 集成Gitlab自动发布(二)\nEKS实践 集成Gitlab自动发布(二) # 系列介绍如何使用Gitlab CI/CD自动部署应用到EKS(K8s)集群中。本篇介绍如何为Runnr镜像的制作。\n上文中创建的Gitlab Runner会持续存活在EKS集群中,但是它不做具体的Pipeline任务,当有Pipeline任务来临时,由它来创建临时的Runner来执行。而临时拆功创建的Runner使用什么容器环境以及具体执行什么任务是由仓库目录下.gitlab-ci.yml文件定义的。\n制作Temp Runner镜像 # 我们制作这个镜像的目的是为了能让镜像运行起来后,可以完成我们的自动发布任务。比如在容器temp-runner容器需要将我们的代码build成应用镜像,然后将应用镜像发布到EKS集群,可以看到,在容器中我们就必须可以使用docker build功能以及kubectl apply功能。\n按照以上的需要,我们制作的镜像至少需要安装好docker 以及kubectl。\nDockerfile如下:\nFROM docker:18 RUN apk add --no-cache curl jq python3 git tar tree \u0026amp;\u0026amp; \\ curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl \u0026amp;\u0026amp; chmod +x ./kubectl \u0026amp;\u0026amp; mv ./kubectl /usr/local/bin/kubectl 直接使用docker镜像作为基础镜像,然后安装kubectl\n使用命令制作并推送镜像到dockerhub(实际我的镜像推动到了ECR)\ndocker build . -t gitlab-runner-base:latest --rm --no-cache docker push gitlab-runner-base:latest 仓库配置 # 项目仓库根目录下新增.gitlab-ci.yml文件,然后文件内容\n« EKS实践 集成Gitlab自动发布(一)\n» EKS-使用EFS\n"},{"id":11,"href":"/aws/eks-use-efs/","title":"Eks Use Efs","section":"Aws","content":" 🏠 首页 / AWS / EKS-使用EFS\nEKS-使用EFS # 创建EFS\naws efs create-file-system \\ --performance-mode generalPurpose \\ --throughput-mode bursting \\ --encrypted \\ --tags Key=Name,Value=\u0026lt;fs-name\u0026gt; Key=creator,Value=dp Key=env:dev,Value=1 # 上面的命令会得到fs-id aws efs create-mount-target \\ --file-system-id \u0026lt;fs-id\u0026gt; \\ --subnet-id subnet-08d7609e614373fb8 \\ --security-groups sg-0af0f0e8705380529 aws efs create-mount-target \\ --file-system-id \u0026lt;fs-id\u0026gt; \\ --subnet-id subnet-09c0707ea8ad281bb \\ --security-groups sg-0af0f0e8705380529 aws efs create-mount-target \\ --file-system-id \u0026lt;fs-id\u0026gt; \\ --subnet-id subnet-063a8f10feb97868d \\ --security-groups sg-0af0f0e8705380529 « EKS实践 集成Gitlab自动发布(二)\n» Gitlab \u0026amp; EKS\n"},{"id":12,"href":"/aws/gitlab-eks/","title":"Gitlab Eks","section":"Aws","content":" 🏠 首页 / AWS / Gitlab \u0026amp; EKS\nGitlab \u0026amp; EKS # 创建IAM User\u0026amp;Group # User:gitlab-ci,保存生成的Access key ID和Secret Access Key,后面会用到\nGroup:Gitlab.CI,添加Policy如下:\nPolicy Name AmazonEKSWorkerNodePolicy AmazonEC2ContainerRegistryFullAccess AmazonEC2ContainerRegistryReadOnly AmazonEC2ContainerServiceFullAccess AmazonEKS_CNI_Policy 将user gitlab-ci添加到Group Gitlab.CI\n将IAM User添加到ConfigMap # kubectl edit cm aws-auth -n kube-system 在mapUsers键追加:\n- \u0026#34;groups\u0026#34;: - \u0026#34;system:masters\u0026#34; \u0026#34;userarn\u0026#34;: \u0026#34;arn:aws:iam::xxxxxxx:user/gitlab-ci\u0026#34; \u0026#34;username\u0026#34;: \u0026#34;gitlab-ci\u0026#34; Gitlab仓库设置 # Setting =\u0026gt; CI/CD =\u0026gt; Variables,添加变量:\nAWS_ACCESS_KEY_ID:\u0026lt;gitlab-ci用户的Access key ID\u0026gt; AWS_SECRET_ACCESS_KEY:\u0026lt;gitlab-ci用户的Secret Access Key\u0026gt; Gitlab仓库.gitlab-ci.yml # « EKS-使用EFS\n» K8s 部署 Kong 服务\n"},{"id":13,"href":"/aws/k8s-deploy-kong/","title":"K8s Deploy Kong","section":"Aws","content":" 🏠 首页 / AWS / K8s 部署 Kong 服务\nK8s 部署 Kong 服务 # 本篇只涉及 Kong 服务在 K8s 集群的部署操作,不涉及概念知识。\n提前准备 # K8s 集群,本文使用的是 AWS EKS 集群服务 一台可以连接 K8s 集群的服务器,已经安装 kubectl 和 docker 等基础应用,之后称之为操作机器 Postgres 数据库,作为 Kong 服务的后端数据库 初始化数据库 # 使用 Postgres 作为 Kong 服务的后端数据库,我们需要提前做数据库的初始化,准备 Kong 服务需要的数据表等。这里使用 K8s-Job 来实现数据库的初始化工作。\nkong-migrations-job.yaml:\napiVersion: batch/v1 kind: Job metadata: name: kong-migrations namespace: kong spec: template: metadata: name: kong-migrations spec: containers: - command: - /bin/sh - -c - kong migrations bootstrap env: - name: KONG_PG_PASSWORD value: \u0026#34;kong\u0026#34; - name: KONG_PG_HOST value: \u0026#34;postgres/postgres\u0026#34; - name: KONG_PG_PORT value: \u0026#34;5432\u0026#34; image: kong:1.4 name: kong-migrations initContainers: - command: - /bin/sh - -c - until nc -zv $KONG_PG_HOST $KONG_PG_PORT -w1; do echo \u0026#39;waiting for db\u0026#39;; sleep 1; done env: - name: KONG_PG_HOST value: \u0026#34;localhost:5432\u0026#34; - name: KONG_PG_PORT value: \u0026#34;5432\u0026#34; image: busybox name: wait-for-postgres restartPolicy: OnFailure 使用的是 kong 的镜像,这个镜像运行起来的容器里执行 kong migrations bootstrap 命令来完成 kong 数据库的初始化操作。\ninitContainers 容器的作用是保证 ping postgres 的网络可以连通,如果无法连通,不会执行初始化。\n在操作机器上使用以下命令开始 kong 数据库的初始化:\nkubectl apply -f kong-migrations-job.yaml 如果 kong 数据库初始化成功,你可以在数据库中看到新出现的表\nK8s资源文件 # kong-configmap.yaml:\napiVersion: v1 data: servers.conf: | # Prometheus metrics server server { server_name kong_prometheus_exporter; listen 0.0.0.0:9542; # can be any other port as well access_log off; location /metrics { default_type text/plain; content_by_lua_block { local prometheus = require \u0026#34;kong.plugins.prometheus.exporter\u0026#34; prometheus:collect() } } location /nginx_status { internal; stub_status; } } # Health check server server { server_name kong_health_check; listen 0.0.0.0:9001; # can be any other port as well access_log off; location /health { return 200; } } kind: ConfigMap metadata: name: kong-server-blocks namespace: kong 使用 nginx 对外暴露 kong 指标数据的接口,通过这个接口,prometheus 可以抓取到 kong 服务指标数据。\n« Gitlab \u0026amp; EKS\n» K8s 部署 konga\n"},{"id":14,"href":"/aws/k8s-deploy-konga/","title":"K8s Deploy Konga","section":"Aws","content":" 🏠 首页 / AWS / K8s 部署 konga\nK8s 部署 konga # 本篇只涉及 konga 的部署操作,不涉及概念知识。\n提前准备 # K8s 集群,本文使用的是 AWS EKS 集群服务 一台可以连接 K8s 集群的服务器,已经安装 kubectl 和 docker 等基础应用,之后称之为操作机器 Postgres数据库 !注意:该数据库使用 9.5 版本,其他最新版本的数据库在初始化 konga 数据库时会报如下错:\nerror: Failed to prepare database: error: column r.consrc does not exist 这是部署 konga 踩过的坑之一。\n确保 K8s 集群中已经创建了 nginx-ingress,nginx-ingress 用于根据定制的 Rule(如后文 kong-ingress 的配置)将流量转发至 K8s 集群的 Service 中去。 创建 nginx-ingress 指令(可参照 https://kubernetes.github.io/ingress-nginx/deploy/)步骤如下:\nStep 1. 执行以下强制命令\nkubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/mandatory.yaml Step 2. 针对我们使用的 AWS 服务,并且根据你想配置的 ELB 类型选择层(L4或L7),执行以下命令\n​ # Layer 4: use TCP as the listener protocol for ports 80 and 443.\n​ # Layer 7: use HTTP as the listener protocol for port 80 and terminate TLS in the ELB\n# FOR Layer 4 kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/aws/service-l4.yaml kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/aws/patch-configmap-l4.yaml # FOR Layer 7 kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/aws/service-l7.yaml kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/nginx-0.30.0/deploy/static/provider/aws/patch-configmap-l7.yaml 初始化数据库 # 在操作机器上执行如下指令\ndocker run --rm pantsel/konga:latest -c prepare -a {{adapter}} -u {{connection-uri}} adapter:数据库类型,可选,\u0026lsquo;mongo\u0026rsquo;,\u0026lsquo;postgres\u0026rsquo;,\u0026lsquo;sqlserver\u0026rsquo; or \u0026lsquo;mysql\u0026rsquo;\nconnection-uri:数据库的连接字符串\n使用 postgres 数据库作为 konga 的存储,指令大概长这个样子:\ndocker run --rm pantsel/konga:latest -c prepare -a postgres -u postgresql://\u0026lt;user\u0026gt;:\u0026lt;password\u0026gt;@\u0026lt;host\u0026gt;:\u0026lt;port\u0026gt;/\u0026lt;database\u0026gt; 运行指令,输出如下,可以说明数据库初始化成功。\ndebug: Preparing database... Using postgres DB Adapter. Database exists. Continue... debug: Hook:api_health_checks:process() called debug: Hook:health_checks:process() called debug: Hook:start-scheduled-snapshots:process() called debug: Hook:upstream_health_checks:process() called debug: Hook:user_events_hook:process() called debug: Seeding User... debug: User seed planted debug: Seeding Kongnode... debug: Kongnode seed planted debug: Seeding Emailtransport... debug: Emailtransport seed planted debug: Database migrations completed! 可以连接postgres发现你的数据库中多出了下面这些表\nK8s 资源文件 # kanga-namespace.yaml:\napiVersion: v1 kind: Namespace metadata: name: konga konga-deployment.yaml:\napiVersion: extensions/v1beta1 kind: Deployment metadata: name: konga namespace: konga spec: replicas: 1 template: metadata: labels: name: konga app: konga spec: containers: - env: - name: \u0026#34;DB_ADAPTER\u0026#34; value: \u0026#34;postgres\u0026#34; - name: \u0026#34;DB_HOST\u0026#34; value: \u0026#34;postgres/postgres\u0026#34; - name: \u0026#34;DB_PORT\u0026#34; value: \u0026#34;5432\u0026#34; - name: \u0026#34;DB_USER\u0026#34; value: konga - name: \u0026#34;DB_PASSWORD\u0026#34; value: konga - name: \u0026#34;DB_DATABASE\u0026#34; value: konga - name: \u0026#34;DB_PG_SCHEMA\u0026#34; value: \u0026#34;public\u0026#34; # - name: BASE_URL # value: /konga/ name: konga image: pantsel/konga ports: - containerPort: 1337 该文件需要设置 postgres 的数据库配置,需要自行替换;\n可以注意到 yaml 文件中的 BASE_URL 环境变量被我注释掉了,这个参数的作用本来是设置成我们可以通过 http://www.example.com/konga/来访问konga程序的,但是镜像的作者目前还没有处理好相关的路由问题。所以目前还只能通过http://konga.example.com使用。\nkonga-service.yaml:\napiVersion: v1 kind: Service metadata: name: konga namespace: konga spec: ports: - name: tcp port: 1337 targetPort: 1337 protocol: TCP selector: app: konga kong-ingress.yaml:\napiVersion: extensions/v1beta1 kind: Ingress metadata: name: konga-ingress namespace: konga annotations: nginx.ingress.kubernetes.io/ssl-redirect: \u0026#34;false\u0026#34; nginx.ingress.kubernetes.io/force-ssl-redirect: \u0026#34;false\u0026#34; nginx.ingress.kubernetes.io/rewrite-target: / spec: tls: - hosts: - konga.example.com secretName: tls-secret rules: - host: konga.example.com http: paths: - path: / backend: serviceName: konga servicePort: 1337 这里host(s)需要替换成真正的域名。\n创建Konga # 先创建konga命令空间\nkubectl apply -f konga-namespace.yaml 再创建其他资源即可\nkubectl apply -f konga-deployment.yaml kubectl apply -f konga-service.yaml kubectl apply -f konga-ingress.yaml 验证konga # 在以上创建命令执行后大概1-2分钟,浏览器访问 http://konga.example.com,出现konga注册页面说明已经部署成功。\n« K8s 部署 Kong 服务\n» K8s 部署 Postgres\n"},{"id":15,"href":"/aws/k8s-deploy-postgres/","title":"K8s Deploy Postgres","section":"Aws","content":" 🏠 首页 / AWS / K8s 部署 Postgres\nK8s 部署 Postgres # 本篇只涉及 Postgres 的部署操作,不涉及概念知识。\n特别说明:Postgres 相对于普通的程序应用而言,属于有状态的服务,因为它存储的数据是需要持久保存的,这点决定了我们选择 K8s-StatefulSet 的部署而非 K8s-Deployment。\n提前准备 # K8s 集群,本文使用的是AWS EKS集群服务 一台可以连接 K8s 集群的服务器,已经安装 kubectl 和 docker 等基础应用 K8s资源文件 # postgres-namespace.yaml:\napiVersion: v1 kind: Namespace metadata: name: postgres postgres-config.yaml:\napiVersion: v1 kind: ConfigMap metadata: name: postgres-config namespace: postgres labels: app: postgres data: POSTGRES_DB: master POSTGRES_USER: dba POSTGRES_PASSWORD: pg_pass 这里的数据库密码涉及到信息敏感,更建议使用 Secret 资源而非 ConfigMap,这里就偷懒了。\npostgres-statefulset.yaml:\napiVersion: apps/v1 kind: StatefulSet metadata: name: postgres namespace: postgres spec: serviceName: \u0026#34;postgres\u0026#34; replicas: 1 selector: matchLabels: app: postgres template: metadata: labels: app: postgres spec: containers: - name: postgres image: postgres:9.5 envFrom: - configMapRef: name: postgres-config ports: - containerPort: 5432 name: postgredb volumeMounts: - name: postgres-data mountPath: /var/lib/postgresql/data subPath: postgres volumeClaimTemplates: - metadata: name: postgres-data spec: accessModes: [\u0026#34;ReadWriteOnce\u0026#34;] storageClassName: gp2 resources: requests: storage: 4Gi 在这个 yaml 中,可以看到我们使用了之前创建的 configmap 来设置环境变量;\n在文件的底部,我们使用了 gp2 的 storageClass 帮助我们自动创建 PV、PVC, 以及关联,你可能需要替换。\npostgres-service.yaml:\napiVersion: v1 kind: Service metadata: name: postgres namespace: postgres labels: app: postgres spec: ports: - port: 5432 name: postgres type: LoadBalancer selector: app: postgres 创建Postgres # 先创建 postgres 命令空间\nkubectl apply -f postgres-namespace.yaml 再创建其他资源即可\nkubectl apply -f postgres-config.yaml kubectl apply -f postgres-statefulset.yaml kubectl apply -f postgres-service.yaml 因为我们是在 AWS 的 EKS 集群中创建资源,所以在以上命令执行完成后,我们可以在 AWS 的 Volume 中找到我们创建的存储卷。\n查看该卷信息,发现它其实已经挂载在集群的一个节点上去了。\n验证Postgres # 我们使用以下命令查看我们创建的 postgres-service\nkubectl get pod -n postgres kubectl get svc -n postgres 输出如下,说明我们的 postgres 已经部署成功\n推荐使用 DBeaver 数据库可视化工具测试连接 postgres,使用的 host 正是输出的 EXTERNAL-IP 列的信息,而其他连接信息在我们的 ConfigMap 中。\n« K8s 部署 konga\n» Terraform 重新管理资源\n"},{"id":16,"href":"/aws/terraform-remanage-resource/","title":"Terraform Remanage Resource","section":"Aws","content":" 🏠 首页 / AWS / Terraform 重新管理资源\nTerraform 重新管理资源 # 看到这个标题你可能会有点懵,我先来解释下。\n在使用Terraform管理AWS的VPC-Subnet资源时(下面是定义资源的代码清单),我遇到了一个问题:当我修改aws_subnet.eks-private-subnet-1资源的cidr_block时,假设我修改成了172.28.2.0/24,这时候旧的\nresource \u0026#34;aws_subnet\u0026#34; \u0026#34;eks-private-subnet-1\u0026#34; { vpc_id = \u0026#34;${var.vpc_id}\u0026#34; cidr_block = \u0026#34;172.28.1.0/24\u0026#34; map_public_ip_on_launch = \u0026#34;false\u0026#34; availability_zone = \u0026#34;${var.region}a\u0026#34; tags = merge( {Name = \u0026#34;${var.cluster_name}-private-subnet-1a\u0026#34;}, \u0026#34;${local.cluster_private_subnet_tags}\u0026#34;) } « K8s 部署 Postgres\n"},{"id":17,"href":"/cka/001/","title":"1st","section":"Cka","content":" 🏠 首页 / CKA / 001\n001 # 01 Task - 英文 # Create a new ClusterRole named deployment-clusterrole that only allows the creation of the following resource types:\nDeployment StatefulSet DaemonSet Create a new ServiceAccount named cicd-token in the existing namespace app-team1. Limited to namespace app-team1, bind the new ClusterRole deployment-clusterrole to the new ServiceAccount cicd-token. kubectl create ns app-team1 kubectl create serviceaccount cicd-token -n app-team1 kubectl create clusterrole deployment-clusterrole --verb=create --resource=deployment,statefulset,daemonset #limted to the namespace app-team1。需要限制的是namespace级别,clusterrolebinding为设置全局,rolebinding正确 kubectl create rolebinding cicd-clusterrole -n app-team1 --clusterrole=deployment-clusterrole --serviceaccount=app-team1:cicd-token 02 Task - 英文 # Set the node named ek8s-node-1 as unavaliable and reschedule all the pods running on it.\nkubectl cordon ek8s-node-1 kubectl drain ek8s-node-1 --delete-local-data --ignore-daemonsets --force //删除所有pod(包括daemonset管理的pod),则需要--ignore-daemonsets或--ignore-daemonsets=true 03 Task - 英文 # Given an existing Kubernetes cluster running version 1.18.8,upgrade all of Kubernetes control plane and node components on the master node only to version 1.19.0。\nYou are also expected to upgrade kubelet and kubectl on the master node。\nBe sure to drain the master node before upgrading it and uncordon it after the upgrade. Do not upgrade the worker nodes,etcd,the container manager,the CNI plugin,the DNS service or any other addons.\napt update apt-cache policy kubeadm apt-get update \u0026amp;\u0026amp; apt-get install -y --allow-change-held-packages kubeadm=1.19.0 kubeadm version #检查kubeadm版本 kubectl drain master --ignore-daemonsets --delete-local-data --force #腾空控制平面节点 sudo kubeadm upgrade plan # 命令查看可升级的版本信息 sudo kubeadm upgrade apply v1.19.0 --etcd-upgrade=false #查看版本信息时,排除etcd从3.4.3-0升到3.4.7-0 kubectl uncordon master sudo kubeadm upgrade node #升级其他控制面节点 apt-get update \u0026amp;\u0026amp; apt-get install -y --allow-change-held-packages kubelet=1.19.0 kubectl=1.19.0 #升级其他控制面节点 sudo systemctl daemon-reload sudo systemctl restart kubelet 04 Task - 中文 # 首先,为运行在 https://127.0.0.1:2379 上的现有etcd 实例创建快照并将快照保存到/data/backup/etcd-snapshot.db。\n为给定实例创建快照预计能在几秒钟内完成。如果该操作似乎挂起,则命令可能有问题。用ctrl+c 来取消操作,然后重试。\n然后还原位于/var/data/etcd-snapshot-previous.db的现有先前快照。\n提供了以下TLS证书和密钥,以通过etcdctl连接到服务器。\nca证书:/opt/KUIN00601/ca.crt 客户端证书:/opt/KUIN00601/etcd-client.crt 客户端密钥:/opt/KUIN00601/etcd-client.key 一定要把这参数用熟练,如果考试时有问题,不要急,多试试!!!\n一旦正确配置了 etcd,只有具有有效证书的客户端才能访问它。要让 Kubernetes API 服务器访问,可以使用参数 \u0026ndash;etcd-certfile=k8sclient.cert,–etcd-keyfile=k8sclient.key 和 \u0026ndash;etcd-cafile=ca.cert 配置它。\n我记得我考试进用的是:–certfile=/opt/KUIN00601/etcd-client.crt \u0026ndash;keyfile=/opt/KUIN00601/etcd-client.key \u0026ndash;cafile=/opt/KUIN00601/ca.crt\nETCDCTL_API=3 etcdctl --endpoint=https://127.0.0.1:2379 --certfile=/opt/KUIN00601/etcd-client.crt --keyfile=/opt/KUIN00601/etcd-client.key --cafile=/opt/KUIN00601/ca.crt snapshot save /data/backup/etcd-snapshot.db ETCDCTL_API=3 etcdctl --endpoint=https://127.0.0.1:2379 --cert-file=/opt/KUIN00601/etcd-client.crt --key-file=/opt/KUIN00601/etcd-client.key --ca-file=/opt/KUIN00601/ca.crt snapshot restore /var/data/etcd-snapshot-previous.db 05 Task - 英文 # Create a new NetworkPolicy named allow-port-from-namespace to allow Pods in the existing namespace internal to connect to port 8080 of other Pods in the same namespace. Ensure that the new NetworkPolicy:\ndoes not allow access to Pods not listening on port 8080. does not allow access from Pods not in namespace internal. #network.yaml apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-port-from-namespace namespace: internal spec: podSelector: matchLabels: {} policyTypes: - Ingress ingress: - from: - podSelector: {} ports: - protocol: TCP port: 8080 #spec.podSelector限定了这个namespace里的pod可以访问 kubectl create -f network.yaml 06 Task - 英文 # Reconfigure the existing deployment front-end and add a port specifiction named http exposing port 80/tcp of the existing container nginx.\nCreate a new service named front-end-svc exposing the container prot http.\nConfigure the new service to also expose the individual Pods via a NodePort on the nodes on which they are scheduled.\nkubectl get deploy front-end kubectl edit deploy front-end -o yaml #port specification named http #service.yaml apiVersion: v1 kind: Service metadata: name: front-end-svc labels: app: nginx spec: ports: - port: 80 protocol: tcp name: http selector: app: nginx type: NodePort # kubectl create -f service.yaml # kubectl get svc #或者一条命令搞定,注意会遗漏port specification named http kubectl expose deployment front-end --name=front-end-svc --port=80 --tarport=80 --type=NodePort 07 Task - 英文 # Create a new nginx Ingress resource as follows:\nName: ping: Namespace: ing-internal: Exposing service hi on path /hi using service port 5678: The avaliability of service hi can be checked using the following command,which should return hi: curl -kL /hi\nvi ingress.yaml # apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ping namespace: ing-internal spec: rules: - http: paths: - path: /hi pathType: Prefix backend: service: name: hi port: number: 5678 # kubectl create -f ingress.yaml 08 Task - 英文 # Scale the deployment presentation to 3 pods.\nkubectl get deployment kubectl scale deployment.apps/presentation --replicas=3 09 Task - 英文 # Task\nSchedule a pod as follows:\nname: nginx-kusc00401: Image: nginx: Node selector: disk-spinning: #yaml apiVersion: v1 kind: Pod metadata: name: nginx-kusc00401 spec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent nodeSelector: disk: spinning # kubectl create -f node-select.yaml 10 Task - 英文 # Task Check to see how many nodes are ready (not including nodes tainted NoSchedule)and write the number to /opt/KUSC00402/kusc00402.txt.:\nkubectl describe nodes | grep ready|wc -l kubectl describe nodes | grep -i taint | grep -i noschedule |wc -l echo 3 \u0026gt; /opt/KUSC00402/kusc00402.txt # 查询集群Ready节点数量 kubectl get node | grep -i ready |wc -l # 找出节点taints、noSchedule kubectl describe nodes | grep -i taints | grep -i noschedule |wc -l #将得到的减数,写入到文件 echo 2 \u0026gt; /opt/KUSC00402/kusc00402.txt 11 Task - 英文 # Create a pod named kucc8 with a single app container for each of the following images running inside (there may be between 1 and 4 images specified): nginx + redis + memcached + consul .:\nkubectl run kucc8 --image=nginx --dry-run -o yaml \u0026gt; kucc8.yaml # vi kucc8.yaml apiVersion: v1 kind: Pod metadata: creationTimestamp: null name: kucc8 spec: containers: - image: nginx name: nginx - image: redis name: redis - image: memcached name: memcached - image: consul name: consul # kubectl create -f kucc8.yaml #12.07 12 Task - 英文 # Task Create a persistent volume whit name app-config, of capacity 1Gi and access mode ReadOnlyMany . the type of volume is hostPath and its location is /srv/app-config .:\n#vi pv.yaml apiVersion: v1 kind: PersistentVolume metadata: name: app-config spec: capacity: storage: 1Gi accessModes: - ReadOnlyMany hostPath: path: /srv/app-config # kubectl create -f pv.yaml 13 Task - 英文 # Task Create a new PersistentVolumeClaim:\nName: pv-volume: Class: csi-hostpath-sc: Capacity: 10Mi: Create a new Pod which mounts the PersistentVolumeClaim as a volume:\nName: web-server: Image: nginx: Mount path: /usr/share/nginx/html: Configure the new Pod to have ReadWriteOnce access on the volume.\nFinally,using kubectl edit or Kubectl patch expand the PersistentVolumeClaim to a capacity of 70Mi and record that change.\nvi pvc.yaml #使用指定storageclass创建一个pvc apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pv-volume spec: accessModes: - ReadWriteOnce volumeMode: Filesystem resources: requests: storage: 10Mi storageClassName: csi-hostpath-sc # vi pod-pvc.yaml apiVersion: v1 kind: Pod metadata: name: web-server spec: containers: - name: web-server image: nginx volumeMounts: - mountPath: \u0026#34;/usr/share/nginx/html\u0026#34; name: my-volume volumes: - name: my-volume persistentVolumeClaim: claimName: pv-volume # craete kubectl create -f pod-pvc.yaml #edit 修改容量 kubectl edit pvc pv-volume --record 14 Task - 英文 # Task Monitor the logs of pod bar and:\nExtract log lines corresponding to error unable-to-access-website: Write them to /opt/KUTR00101/bar: kubectl logs bar | grep \u0026#39;unable-to-access-website\u0026#39; \u0026gt; /opt/KUTR00101/bar cat /opt/KUTR00101/bar 15 Task - 英文 # Context Without changing its existing containers,an existing Pod needs to be integrated into Kubernetes’s build-in logging architecture (e.g. kubectl logs). Adding a streaming sidecar container is a good and common way to accomplish this requirement.\nTask Add a busybox sidecar container to the existing Pod big-corp-app. The new sidecar container has to run the following command:\n/bin/sh -c tail -n+1 -f /var/log/big-corp-app.log Use a volume mount named logs to make the file /var/log/big-corp-app.log available to the sidecar container.\nDon’t modify the existing container. Don’t modify the path of the log file,both containers must access it at /var/log/big-corp-app.log.\n# kubectl get pod big-corp-app -o yaml # apiVersion: v1 kind: Pod metadata: name: big-corp-app spec: containers: - name: big-corp-app image: busybox args: - /bin/sh - -c - \u0026gt; i=0; while true; do echo \u0026#34;$(date) INFO $i\u0026#34; \u0026gt;\u0026gt; /var/log/big-corp-app.log; i=$((i+1)); sleep 1; done volumeMounts: - name: logs mountPath: /var/log - name: count-log-1 image: busybox args: [/bin/sh, -c, \u0026#39;tail -n+1 -f /var/log/big-corp-app.log\u0026#39;] volumeMounts: - name: logs mountPath: /var/log volumes: - name: logs emptyDir: {} #验证: kubectl logs big-corp-app -c count-log-1 16 Task - 英文 # Form the pod label name-cpu-loader,find pods running high CPU workloads and write the name of the pod consuming most CPU to the file /opt/KUTR00401/KURT00401.txt(which alredy exists).\n查看Pod标签为name=cpu-user-loader 的CPU使用率并且把cpu使用率最高的pod名称写入/opt/KUTR00401/KUTR00401.txt文件里\nkubectl top pods -l name=name-cpu-loader --sort-by=cpu echo \u0026#39;排名第一的pod名称\u0026#39; \u0026gt;\u0026gt;/opt/KUTR00401/KUTR00401.txt 17 Task - 英文 # Task A Kubernetes worker node,named wk8s-node-0 is in state NotReady . Investigate why this is the case,and perform any appropriate steps to bring the node to a Ready state,ensuring that any changes are made permanent.\nYon can ssh to teh failed node using:\nssh wk8s-node-o You can assume elevated privileges on the node with the following command:\nsudo -i #名为wk8s-node-1 的节点处于NotReady状态,将其恢复成Ready状态,并且设置为开机自启 # 连接到NotReady节点 ssh wk8s-node-0 #获取权限 sudo -i # 查看服务是否运行正常 systemctl status kubelet #如果服务非正常运行进行恢复 systemctl start kubelet #设置开机自启 systemctl enable kubelet 19 Task - 英文 # Set configuration context $ kubectl config use-context wk8s\nconfigure the kubelet systemed managed service, on the node labelled with name=wk8s-node-1,to launch a pod containing a single container of image nginx named myservice automatically.\nAny spec file requried should be placed in the /etc/kuberneteds/mainfests directory on the node\nHints:\nYou can ssh to the failed node using $ ssh wk8s-node-0\nYou can assume elevated privileges on the node with the following command $ sudo -i\n静态Pod创建方法与注意点 # Set configuration context $ kubectl config use-context wk8s\nconfigure the kubelet systemed managed service, on the node labelled with name=wk8s-node-1,to launch a pod containing a single container of image nginx named myservice automatically.\nAny spec file requried should be placed in the /etc/kuberneteds/mainfests directory on the node\nHints:\nYou can ssh to the failed node using $ ssh wk8s-node-0\nYou can assume elevated privileges on the node with the following command $ sudo -i\nkubectl config use-context wk8s kubectl get node -l=name=wk8s-node-0 -o wide # or kubectl get node -l name=wk8s-node-0 -o wide sudo wk8s-node-0 sudo -i systemctl status kubelet -l |grep config #找到--config配置的文件路径 cat /var/lib/kubelet/config.yaml |grep staticPodPath # 得到/etc/kubernetes/manifests cd /etc/kubernetes/manifests kubectl run myservice --image=nginx --dry-run=client -o yaml \u0026gt; myservice.yaml kubectl get pod -A|grep myservice #可以得到静态Pod » 准备CKA\n"},{"id":18,"href":"/cka/","title":"Cka","section":"","content":" 🏠 首页 / CKA\nCKA # 001\n准备CKA\n考题\n"},{"id":19,"href":"/cka/prepare-cka/","title":"Prepare Cka","section":"Cka","content":" 🏠 首页 / CKA / 准备CKA\n准备CKA # 办理护照 # 优惠券:\nAffkub95-268483\nFrequently Asked Questions: CKA and CKAD \u0026amp; CKS - T\u0026amp;C DOC (linuxfoundation.org)\n« 001\n» 考题\n"},{"id":20,"href":"/cka/tasks/","title":"Tasks","section":"Cka","content":" 🏠 首页 / CKA / 考题\n考题 # kubectl scale deployment nginx --replicas=3 创建busybox:\nkubectl run busybox --image=busybox --generator=run-pod/v1 --command=true -- sleep 7d # nslookup kubectl run nginx-dns --image=nginx kubectl run busybox --image=busybox --generator=run-pod/v1 --command=true -- sleep 7d kubectl exec -it busybox -- nslookup nginx-dns kubectl exec -it busybox -- nslookup \u0026lt;pod-ip\u0026gt; etcd备份和还原:\nETCDCTL_API=3 etcdctl --endpoints=https://[127.0.0.1]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt \\ --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key \\ snapshot save ETCDCTL_API=3 etcdctl --endpoints=https://[127.0.0.1]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt \\ --name=master \\ --cert=/etc/kubernetes/pki/etcd/server.crt --key=/etc/kubernetes/pki/etcd/server.key \\ --data-dir /var/lib/etcd-from-backup \\ --initial-cluster=master=https://127.0.0.1:2380 \\ --initial-cluster-token etcd-cluster-1 \\ --initial-advertise-peer-urls=https://127.0.0.1:2380 \\ snapshot restore ... « 准备CKA\n"},{"id":21,"href":"/cs/","title":"Cs","section":"","content":" 🏠 首页 / 计算机科学\n计算机科学 # 互联网如何运作?\n网络通信\n虚拟内存\n"},{"id":22,"href":"/cs/internet/","title":"Internet","section":"Cs","content":" 🏠 首页 / 计算机科学 / 互联网如何运作?\n互联网如何运作? # 本篇文章翻译自: 原文地址\n作为开发人员,深入了解互联网是什么及其工作原理非常重要。它是构建大多数现代软件应用程序的基础。为了构建有效、安全且可扩展的应用程序和服务,您需要深入了解互联网的工作原理以及如何利用其功能和连接性。\n在本文中,我们将介绍互联网的基础知识,包括它的工作原理、一些基本概念、术语和一些用于在互联网上构建应用程序和服务的通用协议。\n我们有很多内容要介绍,所以让我们开始吧!\n互联网简介 # 在了解什么是互联网之前,我们需要先了解什么是网络。网络是一组相互连接的计算机或其他设备。例如,您家里可能有一个由计算机和设备组成的网络。您住在隔壁的朋友可能有类似的设备网络。他们的邻居可能有类似的设备网络。所有这些网络连接在一起就形成了互联网。\n互联网是一个网络的网络。\n互联网是美国国防部于 20 世纪 60 年代末开发的,作为创建能够抵御核攻击的去中心化通信网络的一种手段。多年来,它已发展成为一个遍布全球的复杂而精密的网络。\n如今,互联网已成为现代生活的重要组成部分,世界各地数十亿人使用互联网来获取信息、与朋友和家人交流、开展业务等等。作为开发人员,必须充分了解互联网的工作原理以及支撑互联网的各种技术和协议。\n互联网如何运作:概述 # 在较高层面上,互联网的工作原理是使用一组标准化协议将设备和计算机系统连接在一起。这些协议定义了设备之间如何交换信息,并确保数据可靠、安全地传输。\n互联网的核心是互连路由器的全球网络,负责引导不同设备和系统之间的流量。当您通过互联网发送数据时,数据会被分解为小数据包,然后从您的设备发送到路由器。路由器检查数据包并将其转发到通往目的地的路径中的下一个路由器。这个过程一直持续到数据包到达最终目的地。\n为了确保数据包的正确发送和接收,互联网使用了多种协议,包括互联网协议(IP)和传输控制协议(TCP)。 IP 负责将数据包路由到正确的目的地,而 TCP 确保数据包以正确的顺序可靠地传输。\n除了这些核心协议之外,还有许多其他技术和协议用于通过互联网实现通信和数据交换,包括域名系统 (DNS)、超文本传输​​协议 (HTTP) 和安全协议套接字层/传输层安全 (SSL/TLS) 协议。作为开发人员,深入了解这些不同的技术和协议如何协同工作以实现互联网上的通信和数据交换非常重要。\n基本概念和术语 # 要了解互联网,熟悉一些基本概念和术语很重要。以下是一些需要注意的关键术语和概念:\n数据包:通过互联网传输的小数据单位。 路由器:在不同网络之间引导数据包的设备。 IP 地址:分配给网络上每个设备的唯一标识符,用于将数据路由到正确的目的地。 域名:用于识别网站的人类可读名称,例如 google.com。 DNS:域名系统负责将域名转换为IP地址。 HTTP:超文本传输​​协议用于在客户端(例如网络浏览器)和服务器(例如网站)之间传输数据。 HTTPS:HTTP 的加密版本,用于在客户端和服务器之间提供安全通信。 SSL/TLS:安全套接字层和传输层安全协议用于通过互联网提供安全通信。 了解这些基本概念和术语对于使用互联网和开发基于互联网的应用程序和服务至关重要。\n协议在互联网中的作用 # 协议在通过互联网进行通信和数据交换方面发挥着至关重要的作用。协议是一组规则和标准,定义设备和系统之间如何交换信息。\n互联网通信中使用许多不同的协议,包括互联网协议 (IP)、传输控制协议 (TCP)、用户数据报协议 (UDP)、域名系统 (DNS) 等。\nIP 负责将数据包路由到正确的目的地,而 TCP 和 UDP 则确保数据包可靠且高效地传输。 DNS 用于将域名转换为 IP 地址,HTTP 用于在客户端和服务器之间传输数据。\n使用标准化协议的主要好处之一是它们允许来自不同制造商和供应商的设备和系统彼此无缝通信。例如,一家公司开发的 Web 浏览器可以与另一家公司开发的 Web 服务器通信,只要它们都遵守 HTTP 协议。\n作为开发人员,了解互联网通信中使用的各种协议以及它们如何协同工作以实现通过互联网传输数据和信息非常重要。\n了解 IP 地址和域名 # IP 地址和域名都是使用互联网时需要理解的重要概念。\nIP 地址是分配给网络上每个设备的唯一标识符。它用于将数据路由到正确的目的地,确保信息发送到预期的收件人。 IP 地址通常表示为由句点分隔的一系列四个数字,例如“192.168.1.1”。\n另一方面,域名是人类可读的名称,用于识别网站和其他互联网资源。它们通常由两个或多个部分组成,并用句点分隔。例如,“google.com”是一个域名。使用域名系统 (DNS) 将域名转换为 IP 地址。\nDNS 是互联网基础设施的重要组成部分,负责将域名转换为 IP 地址。当您在 Web 浏览器中输入域名时,您的计算机会向 DNS 服务器发送 DNS 查询,该服务器会返回相应的 IP 地址。然后,您的计算机使用该 IP 地址连接到您请求的网站或其他资源。\nHTTP 和 HTTPS 简介 # HTTP(超文本传输​​协议)和 HTTPS(HTTP 安全)是基于互联网的应用程序和服务中最常用的两种协议。\nHTTP 是用于在客户端(例如网络浏览器)和服务器(例如网站)之间传输数据的协议。当您访问网站时,您的网络浏览器会向服务器发送 HTTP 请求,询问您请求的网页或其他资源。然后,服务器将包含请求数据的 HTTP 响应发送回客户端。\nHTTPS 是 HTTP 的更安全版本,它使用 SSL/TLS(安全套接字层/传输层安全性)加密技术对客户端和服务器之间传输的数据进行加密。这提供了额外的安全层,有助于保护敏感信息,例如登录凭据、支付信息和其他个人数据。\n当您访问使用 HTTPS 的网站时,您的网络浏览器将在地址栏中显示一个挂锁图标,表明连接是安全的。您还可能会在网站地址开头看到字母“https”,而不是“http”。\n使用 TCP/IP 构建应用程序 # TCP/IP(传输控制协议/互联网协议)是大多数基于互联网的应用程序和服务使用的底层通信协议。它在不同设备上运行的应用程序之间提供可靠、有序且经过错误检查的数据传输。\n使用 TCP/IP 构建应用程序时,需要理解几个关键概念:\n端口:端口用于识别设备上运行的应用程序或服务。每个应用程序或服务都分配有一个唯一的端口号,允许将数据发送到正确的目的地。 套接字:套接字是 IP 地址和端口号的组合,代表通信的特定端点。套接字用于在设备之间建立连接并在应用程序之间传输数据。 连接:当两个设备想要相互通信时,在两个套接字之间建立连接。在连接建立过程中,设备会协商各种参数,例如最大段大小和窗口大小,这些参数决定如何通过连接传输数据。 数据传输:一旦建立连接,就可以在每个设备上运行的应用程序之间传输数据。数据通常分段传输,每个段包含序列号和其他元数据以确保可靠传输。 使用 TCP/IP 构建应用程序时,您需要确保应用程序设计为可使用适当的端口、套接字和连接。您还需要熟悉 TCP/IP 常用的各种协议和标准,例如 HTTP、FTP(文件传输协议)和 SMTP(简单邮件传输协议)。了解这些概念和协议对于构建有效、可扩展且安全的基于互联网的应用程序和服务至关重要。\n使用 SSL/TLS 保护互联网通信 # 正如我们之前讨论的,SSL/TLS 是一种用于加密通过互联网传输的数据的协议。它通常用于为 Web 浏览器、电子邮件客户端和文件传输程序等应用程序提供安全连接。\n使用 SSL/TLS 保护互联网通信时,需要了解一些关键概念:\n证书:SSL/TLS 证书用于在客户端和服务器之间建立信任。它们包含有关服务器身份的信息,并由受信任的第三方(证书颁发机构)签名以验证其真实性。\n握手:在 SSL/TLS 握手过程中,客户端和服务器交换信息以协商安全连接的加密算法和其他参数。\n加密:建立安全连接后,数据将使用约定的算法进行加密,并可以在客户端和服务器之间安全传输。\n在构建基于 Internet 的应用程序和服务时,了解 SSL/TLS 的工作原理并确保您的应用程序设计为在传输登录凭据、支付信息和其他个人数据等敏感数据时使用 SSL/TLS 非常重要。您还需要确保为您的服务器获取并维护有效的 SSL/TLS 证书,并遵循配置和保护 SSL/TLS 连接的最佳实践。通过这样做,您可以帮助保护用户数据并确保应用程序通过互联网进行通信的完整性和机密性。\n未来:新兴趋势和技术 # 互联网在不断发展,新技术和趋势不断涌现。作为开发人员,为了构建创新且有效的应用程序和服务,了解最新的发展非常重要。\n以下是塑造互联网未来的一些新兴趋势和技术:\n5G:5G是最新一代移动网络技术,与前几代相比,速度更快、延迟更低、容量更大。预计它将实现新的用例和应用,例如自动驾驶车辆和远程手术。\n物联网(IoT):物联网是指连接到互联网并可以交换数据的物理设备、车辆、家用电器和其他物体的网络。随着物联网的不断发展,预计它将彻底改变医疗保健、运输和制造等行业。\n人工智能 (AI):机器学习和自然语言处理等人工智能技术已被用于支持从语音助手到欺诈检测等广泛的应用程序和服务。随着人工智能的不断发展,预计它将催生新的用例并改变医疗保健、金融和教育等行业。\n区块链:区块链是一种分布式账本技术,可实现安全、去中心化的交易。它被用来为从加密货币到供应链管理的广泛应用提供支持。\n边缘计算:边缘计算是指在网络边缘而不是在集中式数据中心处理和存储数据。预计它将支持新的用例和应用程序,例如实时分析和低延迟应用程序。\n通过及时了解这些以及其他新兴趋势和技术,您可以确保您的应用程序和服务能够利用最新功能并为用户提供最佳体验。\n结论 # 这就是本文的结尾。我们已经涵盖了很多内容,所以让我们花点时间回顾一下我们所学到的内容:\n互联网是一个由互连计算机组成的全球网络,使用一组标准的通信协议来交换数据。 互联网的工作原理是使用标准化协议(例如 IP 和 TCP)将设备和计算机系统连接在一起。 互联网的核心是一个由互连路由器组成的全球网络,用于引导不同设备和系统之间的流量。 您需要熟悉的基本概念和术语包括数据包、路由器、IP 地址、域名、DNS、HTTP、HTTPS 和 SSL/TLS。 协议在通过互联网进行通信和数据交换方面发挥着至关重要的作用,允许来自不同制造商和供应商的设备和系统无缝通信。 我希望您觉得这篇文章有用。如果您有任何问题或意见,请随时在下面留言。谢谢阅读!\n» 网络通信\n"},{"id":23,"href":"/cs/networking/","title":"Networking","section":"Cs","content":" 🏠 首页 / 计算机科学 / 网络通信\n网络通信 # I/O 模型 # 《UNIX 网络编程》中总结了 5 种 I/O 模型,包括同步和异步 I/O:\n阻塞 I/O (Blocking I/O) 非阻塞 I/O (Nonblocking I/O) I/O 多路复用 (I/O multiplexing) 信号驱动 I/O (Signal driven I/O) 异步 I/O (Asynchronous I/O) 操作系统上的 I/O 是用户空间和内核空间的数据交互。\n« 互联网如何运作?\n» 虚拟内存\n"},{"id":24,"href":"/cs/virtual-memory/","title":"Virtual Memory","section":"Cs","content":" 🏠 首页 / 计算机科学 / 虚拟内存\n虚拟内存 # 为了更加有效的管理内存并且降低内存出错的概率。\n计算机存储器 # 速度快 容量大 价格便宜 类型,自上向下分别是寄存器,高速缓存,主存(RAM),磁盘。成本与访问速度负相关。\n寄存器的容量:32 位:32x32 bit吗,64 位:64x64 bit\n1 个字节(Bytes)等于 8 bit,因此 1kb 是 8x1024 bit\n主存(RAM)与 CPU 直接交换数据的内部存储器。\n物理内存 虚拟内存 虚拟内存核心原理 # 为每个程序设置一段\u0026quot;连续\u0026quot;的虚拟地址空间,把这个地址空间分割成多个具有连续地址范围的页 (Page),并把这些页和物理内存做映射,在程序运行期间动态映射到物理内存。当程序引用到一段在物理内存的地址空间时,由硬件立刻执行必要的映射;而当程序引用到一段不在物理内存中的地址空间时,由操作系统负责将缺失的部分装入物理内存并重新执行失败的指令。\n内存交换(swap) # 在进程运行期间只分配映射当前使用到的内存,暂时不使用的数据则写回磁盘作为副本保存,需要用的时候再读入内存,动态地在磁盘和内存之间交换数据。\n参考 # 虚拟内存精粹 « 网络通信\n"},{"id":25,"href":"/dapr/","title":"Dapr","section":"","content":" 🏠 首页 / Dapr\nDapr # Dapr 0-1\n"},{"id":26,"href":"/dapr/dapr/","title":"Dapr","section":"Dapr","content":" 🏠 首页 / Dapr / Dapr 0-1\nDapr 0-1 # 介绍 # Dapr(Distributed Application Runtime),提供分布式应用运行所需要的环境。\nSidecar架构。\n目的:\n快速落地微服务,将业务和基础设施分离,专注于业务开发,降低微服务的复杂性。\n运行环境:\n服务发现 负载均衡 故障转移 熔断限流 缓存 异步通信 日志组件 链路监控 \u0026hellip; 核心功能:\nService Invocation(服务调用) State Management(状态管理) Publish and Subscribe(消息发布订阅) Resource bingdings and triggers(资源绑定,事件触发) Actors(单线程模型)分布式锁 Observability(遥测)ELK,链路监控,告警 Secrets(安全)IdentityServer4 安装 # 依赖 # Docker: https://docs.docker.com/install/ 注意:windows平台,Docker必须运行Linux Containers模式\n安装cli # https://github.com/dapr/cli\n以在linux中安装dapr为例:\nwget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O-|/bin/bash dapr init --runtime-version Service Invocation # 解决微服务之间通信的问题。\n"},{"id":27,"href":"/design-pattern/","title":"Design Pattern","section":"","content":" 🏠 首页 / 设计模式\n设计模式 # CI/CD\n"},{"id":28,"href":"/design-pattern/cicd/","title":"Cicd","section":"Design Pattern","content":" 🏠 首页 / 设计模式 / CI/CD\nCI/CD # Concepts # Pipeline # PipelineStage # Fields:\nName Type Desc ID string PipelineID string PrevPipelineStageID string Status uint8 Pending, Runing, Success, Failed, Abort Name string PipelineStageTask # Fields:\nName Type Desc ID string PipelineStageID string PrevPipelineStageTaskID string Image string Container image task running on. DindRequired boolean Docker in docker? DindImage string dependency DindRequired default: docker:18-dind. Scripts string CacheDirs string EnvironmentVariables string Status string EnvironmentVariables string EnvironmentVariables string "},{"id":29,"href":"/devops/","title":"Devops","section":"","content":" 🏠 首页 / DevOps\nDevOps # Agile\nAnsible\n蓝绿部署、滚动部署和灰度部署\n混沌工程原则 (PRINCIPLES OF CHAOS ENGINEERING)\n商业画布\n使用grafana监控5xx服务\n使用Grafana监控service\nGrafana\nJaeger\nnginx\n"},{"id":30,"href":"/devops/agile/","title":"Agile","section":"Devops","content":" 🏠 首页 / DevOps / Agile\nAgile # 敏捷 交付产品可以看作饭店上菜 顾客点了十个菜 后厨把十个菜做完,最后十个菜一起上桌 ——不敏捷 后厨做完一个菜就上一个菜 ——敏捷\n敏捷的优点:\n做一盘上一盘,顾客早早就能吃上了,优先横扫饥饿;尽早给用户体验上产品; 做一盘上一盘,每个菜都是新出锅,顾客能吃上一口热的;对比十盘菜一起上,可能先炒的菜已经凉了,凉的菜换做成产品的话,可能就是已经过时的功能了,不符合需求了 做一盘上一盘, 如果前面的菜咸了,可以反馈给饭店,后面的菜做淡点;对比十盘菜一起上,顾客就无法从中间反馈意见了,做一个用户插不上意见的产品,严重的后果可能是用户已经不感兴趣了。 » Ansible\n"},{"id":31,"href":"/devops/ansible/","title":"Ansible","section":"Devops","content":" 🏠 首页 / DevOps / Ansible\nAnsible # 介绍 # 一款轻量级的自动化运维工具,只需要一台主机安装Ansible,便可以管理其他可连通的Linux服务器。\n开源\npython\n特点 # 自动化引擎,实现管理配置,应用部署,服务编排及其他各种服务器管理需求; 使用简单,具有客户端的特点; 基于ssh实现配置管理; 依赖python; 功能强大,支持云服务操作。 安装 # ​ 由于Ansible使用Python开发,所以可以直接使用pip安装\npip install ansible ​ 也可以使用yum或apt-get安装\nyum install ansible -y apt-get install ansible -y ansible --version ​ 只需要在Control Node上安装即可。\n主要概念 # Control node:\n安装了Ansible的主机都可以称之为Control node,反过来说,Ansible安装在Contriol Node上;\n一般为linux机器(注:目前也已经对windows做了支持)。\nManaged nodes:\n待管理的网络设备或者服务器;\nlinux机器(安装了python)。\nInventory:\nModules:\nTasks:\nAnsible配置 # ​ 配置文件位置:/etc/ansible/ansible.cfg,为ini格式文件。\n配置示例 # ​ inventory:主机清单配置文件位置,在使用Ansible命令时,也开始-i \u0026lt;path\u0026gt;指定;\n​ host_key_checking:当know_hosts中不存在的主机(即尚未访问过的主机,是否需要输入密钥);\n​ become_user:sudo用户;\n主机清单(Inventory) # ​ 在一个ini或yaml文件中管理Managed nodes清单,默认安装ansible后,在/etc/ansible/hosts文件中管理(后文统一称之为主机清单文件),一般是将maneged nodes的ip address或host name等信息存储到主机清单文件中。\nFormats # ​ 主机清单文件格式兼容两种:\nini,因为配置方便简单,一般常选。\nmail.example.com [webservers] foo.example.com bar.example.com yaml\nall: hosts: mail.example.com: children: webservers: hosts: foo.example.com: bar.example.com: Hosts # ​ 配置需要管理的主机host name或ip address。\nGroup # ​ 如果想管理某一批主机,可以将这多个host配置到统一个group下。方便对这组主机的批量管理。\n[webserver] webserver01.com webserver02.com [dbserver] dbserver01.com dbserver02.com Group变量 # 作用在group上的变量。\n[webserver] webserver01.com [webserver:vars] ansible_ssh_user=ubuntu Nested group # ​ 一个组包含其他组。\n[allserver:children] webserver dbserver Inventory内置参数 # 参数名 参数说明 ansible_ssh_host 定义主机的ssh地址 ansible_ssh_port 定义主机的ssh端口 ansible_ssh_user 定义主机的ssh认证用户 ansible_ssh_pass 定义主机的ssh认证密码 ansible_sudo 定义主机的sudo用户 ansible_sudo_pass 定义主机的sudo密码 ansible_sudo_exe 定义主机的sudo路径 ansible_connection 定义主机连接方式;与主机的连接类型.比如:local,ssh或者paramiko;Ansible 1.2以前默认使用paramiko。1.2以后的版本默认使用‘smart’,‘smart’方式会根据是否支持ControlPersist,来判断ssh方式是否可行 ansible_ssh_private_key_file 定义主机私钥文件 ansible_shell_type 定义主机shell类型 ansible_python_interpreter 定义主机python解释器路径 [webserver] webserver01.com webserver02.com [webserver:vars] ansible_ssh_private_key_file=~/singapore.pem 模块(Modules) # ​ Ansible功能的单元,一个Ansible功能对应有一个Module。\n常用模块 # file # 远程服务器上的文件进行操作。创建删除文件,修改文件权限等。\npath:执行文件、目录的路径 recurse:递归设置文件属性,只对目录有效 group:定义文件、目录的属组 mode:定义文件、目录的权限 owner:定义文件、目录的所有者 src:要被链接的源文件路径,只应用与state为link的情况 dest:被链接到的路径,只应用于state为link的情况 force:在两种情况下会强制创建软链接,一种是源文件不存在但之后会建立的情况;一种是目标软链接已经存在,需要先取消之前的软链接,然后创建新的软链接,默认值 no。 ansible servers -m file -a \u0026#39;path=~/temp state=directory\u0026#39; # 创建目录 ansible servers -m file -a \u0026#39;path=~/temp/test1.txt state=touch\u0026#39; # 创建文件 copy # 将Control Node上的目录或文件拷贝至Managed Nodes。\nsrc:指定要复制到远程主机的文件或目录。如果路径以 / 结尾,则只复制目录里的内容,如果没有以 / 结尾,则复制包含目录在内的整个内容 dest:文件复制的目的地,必须是一个绝对路径,如果源文件是一个目录,那么dest指向的也必须是一个目录 force:默认取值为yes,表示目标主机包含此文件,但内容不同时,覆盖。 backup:默认取值为no,如果为yes,在覆盖前将原文件进行备份 directory_mode:递归设定目录权限,默认为系统默认权限 others:所有file模块里的选项都可以在这里使用 touch test2.txt ansible servers -m copy -a \u0026#39;src=test2.txt dest=~/temp/test2.txt\u0026#39; # 拷贝文件到服务器 command # ​ 直接要求主机清单执行命令。\nansible servers -m command -a \u0026#34;touch ~/temp/test3.txt\u0026#34; ansible servers -m command -a \u0026#34;sudo apt update\u0026#34; ansible servers -m command -a \u0026#34;sudo apt install nginx -y\u0026#34; ansible servers -m command -a \u0026#34;sudo apt autoremove nginx -y\u0026#34; 不支持shell变量,如$NAME, \u0026lt;,\u0026gt;,\u0026amp;,| 等\nshell # ​ 可以先编辑shell脚本,然后要求主机清单执行shell脚本里的命令。\n- name: Execute the command in remote shell; stdout goes to the specified file on the remote. shell: somescript.sh \u0026gt;\u0026gt; somelog.txt - name: Change the working directory to somedir/ before executing the command. shell: somescript.sh \u0026gt;\u0026gt; somelog.txt args: chdir: somedir/ 注意:shell脚本必须有可执行的权限\n- name: \u0026#39;chmod shell\u0026#39; command: chmod 777 somedir/somescript.sh apt # ​ ubuntu/debian 包、应用管理。\nupdate_cache : yes/no,默认no,操作之前是否 apt-get update\nstate :\nabsent : 移除 latest :最新 present: 目前稳定的,默认 autoremove :yes/no,默认no,移除依赖\nautoclean :yes/no,默认no,移除缓存\nansible servers -m apt -a \u0026#34;name=nginx update_cache=yes\u0026#34; -b # append to playbook - name: Install nginx apt: name: nginx state: latest update_cache: yes ansible servers -m apt -a \u0026#34;name=nginx state=absent autoremove=yes autoclean=yes\u0026#34; -b yum # ​ CentOs管理程序包。\nservice # ​ 管理服务的模块,用来启动、停止、重启服务。\nenabled :yes/no,默认no.是否开机启动。\nname :服务名\nstate :\nreloaded restarted started stopped sleep :如果是restarted,需要等待多长秒后再重启 如:10,等待10秒后重启\nansible servers -m service -a \u0026#34;name=nginx state=started\u0026#34; # playbook - name: Start service nginx, if not started service: name: nginx state: started ansible servers -m service -a \u0026#34;name=nginx state=stoped\u0026#34; unarchive # ​ 解压缩。\ngit # Managed需要已经安装git。\n执行git相关操作。\n任务(Tasks) # ​ Ansible执行的单元,一般放在Playbook中,多个Task一起执行,使用方便,可复用。\n​ 定义方式:\n-name: names module: action --- - name: ansible-playbook demo hosts: servers tasks: - name: create temp dir file: path: ~/temp state: directory - name: create test1.txt file: path: ~/temp/test1.txt state: touch - name: copy test2.txt copy: src: test2.txt dest: ~/temp/test2.txt - name: copy test.sh copy: src: test.sh dest: ~/temp/test.sh - name: \u0026#39;chmod shell\u0026#39; command: chmod 777 ~/temp/test.sh - name: exec shell shell: ~/temp/test.sh args: executable: /bin/bash Ansible用法 # ansible命令 # ansible \u0026lt;hosts\u0026gt; -m \u0026lt;module_name\u0026gt; -a \u0026lt;arguments\u0026gt; -i \u0026lt;hosts_path\u0026gt; hosts:\nall 或 *,所有hosts文件中配置的主机 ip address host name group name 多个host或group可用:分隔 支持正则匹配:如配置了一个webserver的主机,可用ansible ~web* -m ping匹配到 module_name:例如ping,command,apt的任务模块\narguments: 执行module的参数,例如 ansible -m command -a \u0026quot;touch hello.txt\u0026quot;\nhosts_path:主机清单文件位置,默认/etc/ansible/etc,也可以自己指定\nAnsible命令参数 # [-h] # 帮助 [--version] # Ansible版本 [-v] # verbost [-b] # sudo运行,可以以sudo权限运行 [--become-method BECOME_METHOD] [--become-user BECOME_USER] [-K] # 提示输入sudo密码,当不是NOPASSWD模式时使用 [-i INVENTORY] # 指定hosts文件路径,默认default=/etc/ansible/hosts [--list-hosts] # 打印有主机列表 [-o] # 压缩输出,摘要输出 [-t TREE] [-k] # 提示输入ssh登录密码。当使用密码验证的时候用 [--private-key PRIVATE_KEY_FILE] # ssh连接的私钥文件位置 [-u REMOTE_USER] # ssh连接的用户名,默认用root,ansible.cfg中可以配置 [-c CONNECTION] # 连接类型(default=smart),例如还有local [-T TIMEOUT] # 超时限制 [--ask-vault-pass | --vault-password-file VAULT_PASSWORD_FILES] [-f FORKS] # fork多少个进程并发处理,默认为5个 [-M MODULE_PATH] # 模块的执行文件位置,一般用于扩展模块 [--playbook-dir BASEDIR] [-a MODULE_ARGS] # 模块的参数 [-m MODULE_NAME] # 要执行的模块,默认为command Anisble playbook # ​ 预先定义好所有需要执行的操作,形成一个脚本文件,执行该脚本文件即可,方便管理和复用。\nansible-playbook -i \u0026lt;hosts_path\u0026gt; \u0026lt;playbook_path\u0026gt; hosts_path:主机清单文件位置\nplaybook_path: playbook yaml文件位置\n-C:加在ansible-playbook命令后,用于验证palybook文件是否有误\n--- - name: Install prometheus on ubuntu hosts: prometheus become: yes tasks: - name: \u0026#34;Step 1: Import prometheus public GPG Key\u0026#34; apt_key: url: https://s3-eu-west-1.amazonaws.com/deb.robustperception.io/41EFC99D.gpg state: present - name: \u0026#34;Step 2: Reload local package database\u0026#34; apt: update_cache: yes - name: \u0026#34;Step 3: Install prometheus\u0026#34; apt: name: prometheus update_cache: yes - name: \u0026#34;Step 4: Install prometheus-node-exporter\u0026#34; apt: name: prometheus-node-exporter update_cache: yes - name: \u0026#34;Step 5: Install prometheus-pushgateway\u0026#34; apt: name: prometheus-pushgateway update_cache: yes - name: \u0026#34;Step 6: Install prometheus-alertmanager\u0026#34; apt: name: prometheus-alertmanager update_cache: yes - name: \u0026#34;Step 7: Start prometheus service\u0026#34; service: name: prometheus enabled: yes state: started playbook 参数 # name :标识plaoybook文件执行内容\nhosts: inventory\nbecome: yes/no,默认no,是否使用sudo执行\ntasks : 预先定义好的ansible命令,managed node将从上到下执行\nhandles: 使用notify触发\n# 修改nginx配置后,设置重启 - name: write the nginx config file template: src=/somepath/nginx.j2 dest=/data/nginx/conf/nginx.conf notify: - restart nginx handlers: - name: enable nginx service: name=nginx state=restarted when: 当满足条件时才执行,多条件可以使用and、or\ntasks: - name: \u0026#34;shut down Debian flavored systems\u0026#34; command: /sbin/shutdown -t now when: ansible_os_family == \u0026#34;Debian\u0026#34; 常用用法**:\n# --list-hosts(简写为--list) ansible webserver --list-hosts Ansible原理 # ansible命令\nansible命令所使用的模块,参数\n演示 # ansible.cfg host_key_checking = False remote_tmp = /tmp/.ansible-${USER}/tmp hosts主机清单 [ubuntu:vars] ansible_ssh_private_key_file=~/singapore.pem [ubuntu] 172.30.2.252 ansible_ssh_user=ubuntu [mysql:vars] ansible_private_key_file=~/dev-mysql.pem [mysql] 172.31.22.159 ansible_ssh_user=ubuntu [servers:children] ubuntu mysql ansible操作 文件操作\n# 创建目录 ansible servers -m file -a \u0026#39;path=~/temp state=directory mode=0755\u0026#39; # 创建文件 ansible servers -m file -a \u0026#39;path=~/temp/test1.txt state=touch\u0026#39; # Control Node分发文件至Managed Nodes ansible servers -m copy -a \u0026#39;src=index.html dest=~/temp/test2.txt\u0026#39; 踩坑记录 # 问题1\n连接aws的linux实例时,需要在/etc/ansible/hosts定义的参数\n[ubuntu:vars] ansible_private_key_file=/home/ubuntu/singapore.pem [ubuntu] 192.168.0.1 ansible_ssh_user=ubuntu 问题2\n使用docker创建可以ssh连接的ubuntu容器,无法使用root连接,但是使用其他user时,在ansible local -m ping时也会出现以下问题。\nAuthentication or permission failure. In some cases, you may have been able to authenticate and did not have permissions on the remote directory. Consider changing the remote temp path in ansible.cfg to a path rooted in \u0026#34;/tmp\u0026#34;.... 其实上面的信息已经有了一些提示,修改/etc/ansible/ansible.cfg的remote_tmp配置\nremote_tmp = /tmp/.ansible-${USER}/tmp 问题3\n如果在使用pem文件连接服务器时遇到Permissions 0664 for '/home/ubuntu/dev-mysql.pem' are too open.\\r\\nIt is required that your private key files are NOT accessible by others.问题。\n修改文件权限即可\nsudo chmod 400 /path/to/file.pem 问题4\nFailed to lock apt for exclusive operation\n添加\nbecome: yes 其他 # 预编译 # python的版本兼容问题 # ansible v2.7及更高版本默认优先支持python3,如果对特定的host支持单独的python版本,可以通过设置ansible_python_interpreter inventory参数设置。\n[webservers] webserver1.com ansible_python_interpreter=/usr/bin/python2.4 工具对比 # 参考 # Ansible入门\nAnsible Docs\n« Agile\n» 蓝绿部署、滚动部署和灰度部署\n"},{"id":32,"href":"/devops/bule-green-rollback-gray/","title":"Bule Green Rollback Gray","section":"Devops","content":" 🏠 首页 / DevOps / 蓝绿部署、滚动部署和灰度部署\n蓝绿部署、滚动部署和灰度部署 # 直接举例说明:\n现环境中运行着3个V1版本的实例,计划更新到V2版本。\n蓝绿部署 # 直接使用新的服务资源部署3个V2版本实例(仍然保留3个V1版本),然后将请求流量全部转到V2版本。\n优点:\n无需中断服务;\n有回旋的余地,如果V2版本有bug的话,可以很快速的重新回到V1版本。\n缺点:\n资源占用较大,在发布过程中需要用到6个实例的服务资源。\n滚动部署 # 现停掉一个V1版本的实例,待其停止后,部署一个V2版本的实例,V2实例部署成功之后,再停掉一个V1实例,往复,直至全部替换为V2版本实例。\n优点:\n无需中断服务;\n部署新版本时无需增加服务资源,节省成本。\n缺点:\nV2版本有bug的话,不能及时回滚。\n灰度部署 # 也叫金丝雀部署,停掉一个V1版本的实例,部署一个V2版本的实例,将部分用户请求流量的转到V2版本,如果没有问题,再逐步替换V1版本。A/B测试就是一种灰度发布。\n优点:\n无需中断服务;\n同样无需增加服务器,能较为平稳的过渡到新版本,并且当有bug时也能做到快速回滚。\n« Ansible\n» 混沌工程原则 (PRINCIPLES OF CHAOS ENGINEERING)\n"},{"id":33,"href":"/devops/chaos-engineering/","title":"Chaos Engineering","section":"Devops","content":" 🏠 首页 / DevOps / 混沌工程原则 (PRINCIPLES OF CHAOS ENGINEERING)\n混沌工程原则 (PRINCIPLES OF CHAOS ENGINEERING) # http://principlesofchaos.org/ 简体中文版\n混沌工程是在分布式系统上进行实验的学科, 目的是建立对系统抵御生产环境中失控条件的能力以及信心。:\n大规模分布式软件系统的发展正在改变软件工程。作为一个行业,我们很快采用了提高开发灵活性和部署速度的实践。紧随着这些优点的一个迫切问题是:我们对投入生产的复杂系统有多少信心?\n即使分布式系统中的所有单个服务都正常运行, 这些服务之间的交互也会导致不可预知的结果。 这些不可预知的结果, 由影响生产环境的罕见且破坏性的事件复合而成,令这些分布式系统存在内在的混沌。\n我们需要在异常行为出现之前,在整个系统内找出这些弱点。这些弱点包括以下形式:\n当服务不可用时的不正确回滚设置; 不当的超时设置导致的重试风暴; 由于下游依赖的流量过载导致的服务中断; 单点故障时的级联失败等。 我们必须主动的发现这些重要的弱点,在这些弱点通过生产环境暴露给我们的用户之前。我们需要一种方法来管理这些系统固有的混沌, 通过增加的灵活性和速率以提升我们对生产环境部署的信心, 尽管系统的复杂性是由这些部署所导致的。\n我们采用基于经验和系统的方法解决了分布式系统在规模增长时引发的问题, 并以此建立对系统抵御这些事件的能力和信心。通过在受控实验中观察分布式系统的行为来了解它的特性,我们称之为混沌工程。\n混沌工程实践 # 为了具体地解决分布式系统在规模上的不确定性,可以把混沌工程看作是为了揭示系统弱点而进行的实验。这些实验遵循四个步骤:\n首先,用系统在正常行为下的一些可测量的输出来定义“稳定状态”。 其次,假设这个在控制组和实验组都会继续保持稳定状态。 然后,在实验组中引入反映真实世界事件的变量,如服务器崩溃、硬盘故障、网络连接断开等。 最后,通过控制组和实验组之间的状态差异来反驳稳定状态的假说。 破坏稳态的难度越大,我们对系统行为的信心就越强。如果发现了一个弱点,那么我们就有了一个改进目标。避免在系统规模化之后被放大。\n高级原则 # 以下原则描述了应用混沌工程的理想方式,这些原则基于上述实验过程。对这些原则的匹配程度能够增强我们在大规模分布式系统的信心。\n建立一个围绕稳定状态行为的假说 # 要关注系统的可测量输出, 而不是系统的属性。对这些输出在短时间内的度量构成了系统稳定状态的一个代理。 整个系统的吞吐量、错误率、延迟百分点等都可能是表示稳态行为的指标。 通过在实验中的系统性行为模式上的关注, 混沌工程验证了系统是否正常工作, 而不是试图验证它是如何工作的。\n多样化真实世界的事件 # 混沌变量反映了现实世界中的事件。 我们可以通过潜在影响或估计频率排定这些事件的优先级。考虑与硬件故障类似的事件, 如服务器宕机、软件故障 (如错误响应) 和非故障事件 (如流量激增或伸缩事件)。 任何能够破坏稳态的事件都是混沌实验中的一个潜在变量。\n在生产环境中运行实验 # 系统的行为会依据环境和流量模式都会有所不同。 由于资源使用率变化的随时可能发生, 因此通过采集实际流量是捕获请求路径的唯一可靠方法。 为了保证系统执行方式的真实性与当前部署系统的相关性, 混沌工程强烈推荐直接采用生产环境流量进行实验。\n持续自动化运行实验 # 手动运行实验是劳动密集型的, 最终是不可持续的。所以我们要把实验自动化并持续运行,混沌工程要在系统中构建自动化的编排和分析。\n最小化爆炸半径 # 在生产中进行试验可能会造成不必要的客户投诉。虽然对一些短期负面影响必须有一个补偿, 但混沌工程师的责任和义务是确保这些后续影响最小化且被考虑到。\n混沌工程是一个强大的实践, 它已经在世界上一些规模最大的业务系统上改变了软件是如何设计和工程化的。 相较于其他方法解决了速度和灵活性, 混沌工程专门处理这些分布式系统中的系统不确定性。 混沌工程的原则为我们大规模的创新和给予客户他们应得的高质量的体验提供了信心。\n欢迎加入 混沌工程社区的 Google 讨论组和我们一起讨论这些原则的应用。\n« 蓝绿部署、滚动部署和灰度部署\n» 商业画布\n"},{"id":34,"href":"/devops/commercial-canvas/","title":"Commercial Canvas","section":"Devops","content":" 🏠 首页 / DevOps / 商业画布\n商业画布 # 用来描述商业模式、可视化商业模式、评估商业模式以及改变商业模式的通用语言。\n商业模式:通过商业产品创造价值,传递价值,获取价值的一种原理。\n九个模块 # CS客户细分(Customer Segments):企业或机构所服务的一个或多个客户分类群体。\nVP价值主张(Value Propositions):通过价值主张来解决客户难题和满足客户需求。\nCH渠道通路(Channels):通过沟通、分销和销售渠道向客户传递价值主张。\nCR客户关系(Customer Relationships):在每一个客户细分市场建立和维护客户关系。\nR$收入来源(Revenue Streams):收入来源产生于成功提供给客户的价值主张。\nKR核心资源(Key Resoures):核心资源是提供和交付先前描述要素所必备的重要资产。\nKA关键业务(Key Activities):通过执行一些关键业务活动,运转商业模式。\nKP重要合作(Key Partnership):有些业务要外包,而另外一些资源需要从企业外部获得。\nC$成本结构(Cost Structure):商业模式上述要素所引发的成本构成。\n基本认知 # 客户细分 # 客户是商业模式的核心。哪些是重要客户\n« 混沌工程原则 (PRINCIPLES OF CHAOS ENGINEERING)\n» 使用grafana监控5xx服务\n"},{"id":35,"href":"/devops/grafana-monite-service-with-5xx/","title":"Grafana Monite Service With 5xx","section":"Devops","content":" 🏠 首页 / DevOps / 使用grafana监控5xx服务\n使用grafana监控5xx服务 # 1. Grafana信息 # grafana服务: https://devops.example.com/grafana\n如果要注册账号请联系devops组。\n2. Grafana监控预览 # grafana已经配置了对service.hompartners.com域名下的service访问状态返回5xx的监控,可以查看对应的grafana面板 https://devops.example.com/grafana/d/Q_zv-HrWz/cst-service-status?orgId=1\n该监控面板中可以查看如userapi、emailapi等服务是否正常,当面板的网格视图中出现红点,说明访问对应的服务返回了5xx状态,即服务端异常。开发人员等可以根据该视图及时发现服务异常情况。\n3. Grafana添加监控5xx服务 # 如果项继续添加Grafana面板来监控更多的服务,请参照以下教程。\nStep 1 复制模板视图:\n选中并进入xxx service http_status_5xx template面板,按操作如下复制xxx.xxx.com http_status_5xx视图\n(可通过该链接访问: https://devops.example.com/grafana/d/XNnusprWz/xxx-service-http_status_5xx-template?orgId=1)\nStep 2 创建新面板:\n按如下操作创建新面板并粘贴视图。\n随后会在页面呈现一个视图,这时可以先编辑面板信息,并新命名,选择面板分类,并保存面板信息。\nStep 3 定制xxx.xxx.com http_status_5xx视图:\n保存完成之后,点击左上角的回退箭头图标:\u0026lt;\u0026ndash;,回到视图页面,按如下操作编辑视图。\n修改查询sql语句,域名修改为要监控的域名或服务名,比如你想监控www.example.com域名下所有服务,那么你可以定制sql如下:\nSELECT \u0026#34;service_code\u0026#34; FROM \u0026#34;service_status\u0026#34; WHERE (\u0026#34;health_code\u0026#34; = 500 AND \u0026#34;domain_name\u0026#34; = \u0026#39;www.example.com\u0026#39;) AND $timeFilter GROUP BY \u0026#34;service_name\u0026#34; ,当然你可能只想监控某个域名下的其中一个服务,如你想监控www.example.com域名下operationplatgform服务,那么你可以定制sql如下:\nSELECT \u0026#34;service_code\u0026#34; FROM \u0026#34;service_status\u0026#34; WHERE (\u0026#34;health_code\u0026#34; = 500 AND \u0026#34;domain_name\u0026#34; = \u0026#39;www.example.com\u0026#39; AND \u0026#34;service_name\u0026#34; = \u0026#39;operationplatgform\u0026#39;) 另外这里的health_code做了格式化,分为200和500,我们默认监控对我们有用的500状态\n修改视图名称,如果有CloudWatch日志连接的需要,可以定制Panel links。\n视图修改完成后,右上角保存面板。\nStep 4 定制xxx service info视图:\n按照上述操作,将xxx.service.info视图复制,然后粘贴在新的面板中。\n并且编辑粘贴的视图,修改query。\n4. Grafana添加服务信息 # Step 1 复制模板视图:\n选中并进入xxx service http_status_5xx template面板,按操作如下复制xxx service ingo视图\n(可通过该链接访问: https://devops.example.com/grafana/d/XNnusprWz/xxx-service-http_status_5xx-template?orgId=1)\n之后在视图页面,可以通过伸缩视图页面,使展示更合理;通过调整时间段查看想要观察的时间段内的数据。\n如果在定制过程中存在问题,也可以联系DevOps组。\n« 商业画布\n» 使用Grafana监控service\n"},{"id":36,"href":"/devops/grafana-monite-service/","title":"Grafana Monite Service","section":"Devops","content":" 🏠 首页 / DevOps / 使用Grafana监控service\n使用Grafana监控service # 监控live上的应用服务,如果服务http状态为5xx,则反应到grafana图表中,DevOps和开发人员都能及时从图表中获取信息,及时确认和排查问题.\nService Http状态数据来源 # 使用程序定时轮询获取aws elb的日志数据,将日志数据以时序形式存储在influxdb,目前数据结构如下:\ntag keys:\n# TagKeyName Remark 1 domain_name 2 service_name 默认取pathbase,如果pathbase为空,取domain field keys:\n# FieldKeyName Remark 1 domain_name 2 elb_status_code 数字类型,200;500 3 health_code 数字类型,200;500 4 request_url 请求路径 5 service_code 一个域名下的多个service,按序从1自增,作为grafana图表的y轴数据 创建Dashboard # =\u0026gt; Add Query\n目前数据源已经配置完成,选择Influxdb_Elb_Logs作为QUuery DataSource,并且开始配置query\n查询语句可以参考:\nSELECT mean(\u0026#34;service_code\u0026#34;) FROM \u0026#34;service_status\u0026#34; WHERE (\u0026#34;domain_name\u0026#34; = \u0026#39;service.example.com\u0026#39; AND \u0026#34;health_code\u0026#34; = 500) AND $timeFilter GROUP BY time($__interval), \u0026#34;service_name\u0026#34; 根据自身需求修改query即可。\n« 使用grafana监控5xx服务\n» Grafana\n"},{"id":37,"href":"/devops/grafana/","title":"Grafana","section":"Devops","content":" 🏠 首页 / DevOps / Grafana\nGrafana # 官方文档: https://grafana.com/docs/grafana/latest/\n简介 # 特性 # 可视化:通过图表展示指标信息,直观,便于分析\n报警:指标数据超出阈值\n统一:多种数据源可以应用到同一个Dashboard中\n多平台支持:windows、linux、docker、mac\n丰富的插件扩展\n丰富的模板支持\n使用Grafana监控Jenkins # 监控指标包括:jenkins发布状态,jenkins的发布时长等.\n前提条件 # 已安装jenkins\n已安装prometheus\n已安装grafana\nJenkins安装插件 # 登入Jenkins =\u0026gt; Manage Jenkins =\u0026gt; Manage Plugins =\u0026gt; Available页签 搜索Prometheus插件,安装即可.\n此节可以参考: https://medium.com/@eng.mohamed.m.saeed/monitoring-jenkins-with-grafana-and-prometheus-a7e037cbb376\n« 使用Grafana监控service\n» Jaeger\n"},{"id":38,"href":"/devops/jeager/","title":"Jeager","section":"Devops","content":" 🏠 首页 / DevOps / Jaeger\nJaeger # 前言 # 微服务之间的调用关系错综复杂,当你在京东下单时,应用背后的服务调用链可能超你想象。调用链的追踪是微服务绕不过去的技术栈,\n简介 # 关于 # Jaeger,受Dapper和OpenZipkin启发,由Uber开源的一个分布式跟踪系统,用于基于微服务分布式系统的监控和排错,包括:\n分布式上下文传递 分布式事务监控 问题根由分析 服务依赖分析 性能、延迟优化 功能 # 兼容OpenTracing数据模型和工具库 对每个服务、端点使用一致的抽样概率 支持多样的后端数据库:Cassandra,Elasticsearch,Memory 追踪数据拓扑图形展示 基础概念 # Span:\n跨度,是跨服务的一次调用。包含名称,开始时间和截止时间,Span之间可以并列,也可以嵌套。\nTrace:\n是一次完成的分布式调用链,包含多个Span\n技术规格 # 后端Go语言实现 前端React/Javascript 支持的数据库:Cassandra3.4+,Elasticsearch5.x+,Kafka\u0026hellip; 组件介绍 # jaeger-client:\njaeger客户端,可以使用多种主流语言实现OpenTracing协议,将调用链数据收集到agent。\njaeger-agent:\njaeger的代理程序,将收集到的client调用链数据上报到collector。\njaeger-collector:\njaeger调用链数据收集器,对收集到的调用链数据进行校验,处理,存储到后端数据库。\njaeger-query:\njaeger调用链数据查询服务,有独立UI。\nOpenTracing # 分布式的追踪系统其实不止Jaeger一种,但是它们的核心原理都大相径庭,都是从入侵到代码中埋点,然后像追踪系统上报数据信息,最终我们在追踪系统得到数据,从而实现追踪分析。\n为了兼容统一各追踪系统API,OpenTracing规范诞生了,它与平台无关,与厂商无关。有了它的存在,你可以方便的切换你想使用的追踪系统。\n安装 # Docker # docker run -d --name jaeger \\ -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \\ -p 5775:5775/udp \\ -p 6831:6831/udp \\ -p 6832:6832/udp \\ -p 5778:5778 \\ -p 16686:16686 \\ -p 14268:14268 \\ -p 14250:14250 \\ -p 9411:9411 \\ jaegertracing/all-in-one:1.20 Kubernetes # 开发环境:\nkubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-kubernetes/master/all-in-one/jaeger-all-in-one-template.yml 可能需要将文件下载下来,修改Deployment版本。\n生产环境:\n├── install-jaeger.sh ├── jaeger-agent.yaml ├── jaeger-cassandra.yaml ├── jaeger-collector.yaml ├── jaeger-configmap.yaml ├── jaeger-persistent.yaml ├── jaeger-query.yaml └── uninstall-jaeger.sh\n示例 # DotNet Demo:\ndotnet add package Jaeger --version 0.4.2 dotnet add package OpenTracing.Contrib.NetCore --version 0.6.2 public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddOpenTracing(); var httpOption = new HttpHandlerDiagnosticOptions(); httpOption.IgnorePatterns.Add(req =\u0026gt; req.RequestUri.AbsolutePath.Contains(\u0026#34;/api/traces\u0026#34;)); services.AddSingleton(Options.Create(httpOption)); services.AddSingleton\u0026lt;ITracer\u0026gt;(serviceProvider =\u0026gt; { string serviceName = serviceProvider.GetRequiredService\u0026lt;IWebHostEnvironment\u0026gt;().ApplicationName; ILoggerFactory loggerFactory = serviceProvider.GetRequiredService\u0026lt;ILoggerFactory\u0026gt;(); Configuration.SenderConfiguration senderConfiguration = new Configuration.SenderConfiguration(loggerFactory) .WithSender(new UdpSender(\u0026#34;jaeger-agent\u0026#34;, 6831, 0)); var tracer = new Tracer.Builder(serviceName) .WithSampler(new ConstSampler(true)) .WithReporter(new RemoteReporter.Builder().WithSender(senderConfiguration.GetSender()).Build()) .Build(); GlobalTracer.Register(tracer); return tracer; }); } Golang Demo:\ngo get github.com/uber/jaeger-client-go go get github.com/opentracing/opentracing-go func main() { tracer, closer := initJaegerTracer(\u0026#34;jaeger-api-go\u0026#34;, \u0026#34;jaeger-agent.example.dev:6831\u0026#34;) defer closer.Close() opentracing.InitGlobalTracer(tracer) http.HandleFunc(\u0026#34;/api/values\u0026#34;, TraceHandler(valuesHandler)) http.ListenAndServe(\u0026#34;:8082\u0026#34;, nil) } func initJaegerTracer(serviceName, jaegerAgentAddr string) (opentracing.Tracer, io.Closer) { cfg := config.Configuration{ Sampler: \u0026amp;config.SamplerConfig{ Type: jaeger.SamplerTypeConst, Param: 1, }, Reporter: \u0026amp;config.ReporterConfig{ LogSpans: true, LocalAgentHostPort: jaegerAgentAddr, }, } tracer, closer, err := cfg.New(serviceName, config.Logger(jaeger.StdLogger)) if err != nil { log.Printf(\u0026#34;ERROR: Could not initialize jaeger tracer: %s\u0026#34;, err.Error()) } return tracer, closer } func valuesHandler(w http.ResponseWriter, r *http.Request) { time.Sleep(2 * time.Second) fmt.Fprintf(w, \u0026#34;hello from jaeger-go.\u0026#34;) } func TraceHandler(handler func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { spanName := r.URL.Path spanCtx, _ := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header)) span := opentracing.GlobalTracer().StartSpan(spanName, opentracing.ChildOf(spanCtx)) span.SetTag(string(ext.Component), spanName) defer span.Finish() handler(w, r) } } package jaeger_tracer import ( \u0026#34;github.com/opentracing/opentracing-go/ext\u0026#34; \u0026#34;io\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;github.com/opentracing/opentracing-go\u0026#34; \u0026#34;github.com/uber/jaeger-client-go\u0026#34; \u0026#34;github.com/uber/jaeger-client-go/config\u0026#34; ) type TraceHandler struct { OriginalHandler http.Handler } func (handler TraceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { //spanName := runtime.FuncForPC(reflect.ValueOf(handler.OriginalHandler).Pointer()).Name() spanName := r.URL.Path spanCtx, _ := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header)) span := opentracing.GlobalTracer().StartSpan(spanName,opentracing.ChildOf(spanCtx)) span.SetTag(string(ext.Component), spanName) defer span.Finish() handler.OriginalHandler.ServeHTTP(w, r) } func InitJaegerTracer(serviceName, jaegerAgentAddr string) (opentracing.Tracer, io.Closer) { cfg := config.Configuration{ ServiceName: serviceName, Sampler: \u0026amp;config.SamplerConfig{ Type: jaeger.SamplerTypeConst, Param: 1, }, Reporter: \u0026amp;config.ReporterConfig{ LogSpans: true, LocalAgentHostPort: jaegerAgentAddr, }, } tracer, closer, err := cfg.NewTracer(config.Logger(jaeger.StdLogger)) if err != nil { log.Printf(\u0026#34;ERROR: Could not initialize jaeger tracer: %s\u0026#34;, err.Error()) } return tracer, closer } func TracerHandler(handler http.Handler) http.Handler { return jaeger_tracer.TraceHandler{ OriginalHandler: handler, } } 使用 # « Grafana\n» nginx\n"},{"id":39,"href":"/devops/nginx/","title":"Nginx","section":"Devops","content":" 🏠 首页 / DevOps / nginx\nnginx # nginx简介 # 高性能的反向代理工具,负载均衡器;\nnginx配置 # 全局配置 # event配置 # http配置 # 配置反向代理 # 正向代理:\n在国内是\n配置location:\nlocaltion [ = | ~ | ~* | ^~] uri { } =:用于不包含正则表达式的url前,要求请求字符串与uri严格匹配; ~:用于表示uri包含正则表达式,并且区分大小写; ~*:用于表示uri包含正则表达式,并且不区分大小写; ^~:用于不包含正则表达式的uri前,要求nginx服务器找到表示ui和请求字符串匹配度最高的location后,立即使用此location处理请求 配置负载均衡 # 将负载分摊到不同的服务单元,保证服务的快速响应,高可用。\nupstream myserver { server 192.168.0.1:8081; server 192.168.0.2:8082; } server { listen 80; server_name 192.168.0.1; location / { proxy_pass http://myserver; root html; index index.html index.htm; } } 均衡策略:\n轮询策略:\nnginx默认使用的均衡策略,按请求时间先后顺序逐一分配到后端服务器列表,如果某后端服务器down掉了,则自动剔除服务器列表。\n权重策略:\n后端服务器配置weight值,默认值为1,该值配置越大,则均衡到该服务器上的请求越多。\nupstream myserver { server 192.168.0.1:8081 weight=1; server 192.168.0.2:8082 weight=2; } IP-Hash策略:\n根据请求客户端的IP 的hash值给其分配固定的某个服务器,可以解决session问题。\nupstream myserver { ip_hash; server 192.168.0.1:8081; server 192.168.0.2:8082; } Fair策略:\n根据后端服务器的响应时间分配,响应时间短的优先分配。\nupstream myserver { server 192.168.0.1:8081; server 192.168.0.2:8082; fair; } 配置动静分离 # 静态服务器用于响应html、css、js、图片等静态资源的访问,动态服务器用于响应业务处理等。\nlocation /www/ { root /data/; index index.html index.htm; } location /image/ { root /data/; autoindex on; # 列出image目录下的静态文件 } Nginx常用命令 # 重启 # 适用于修改了配置文件之后,重新加载\nnginx -s reload 检查配置文件格式是否正确 # # 检查默认配置文件 /etc/nginx/nginx.conf nginx -t # 检查指定配置文件 nginx -t -c /etc/nginx/conf.d/default.conf « Jaeger\n"},{"id":40,"href":"/docker/","title":"Docker","section":"","content":" 🏠 首页 / Docker\nDocker # container-diff 工具的使用\nDocker in Docker\ndocker buildx\nDocker 常用命令\nDocker Compose 实践\nDocker 容器中安装 PFX 证书\nDocker 主机容器互拷贝文件\n使用 docker manifest 命令构建多架构镜像\n理解 docker run \u0026ndash;link\nDocker 可视化工具 Kitematic\nDockerfile\nLinux 容器\n非 root 账号获取 docker 权限\nsome-apps.md\n"},{"id":41,"href":"/docker/container-diff/","title":"Container Diff","section":"Docker","content":" 🏠 首页 / Docker / container-diff 工具的使用\ncontainer-diff 工具的使用 # 简介 # container-diff 是 google 开源的一款用于分析和比较 Docker 镜像的工具,它可以从多个维度分析一个或者比较两个容器镜像:\n镜像构建历史 镜像文件系统 镜像大小 软件包管理 项目地址: https://github.com/GoogleContainerTools/container-diff\n安装 # macOS # curl -LO https://storage.googleapis.com/container-diff/latest/container-diff-darwin-amd64 \u0026amp;\u0026amp; chmod +x container-diff-darwin-amd64 \u0026amp;\u0026amp; sudo mv container-diff-darwin-amd64 /usr/local/bin/container-diff Linux # curl -LO https://storage.googleapis.com/container-diff/latest/container-diff-linux-amd64 \u0026amp;\u0026amp; chmod +x container-diff-linux-amd64 \u0026amp;\u0026amp; sudo mv container-diff-linux-amd64 /usr/local/bin/container-diff # or curl -LO https://storage.googleapis.com/container-diff/latest/container-diff-linux-amd64 \u0026amp;\u0026amp; chmod +x container-diff-linux-amd64 \u0026amp;\u0026amp; mkdir -p $HOME/bin \u0026amp;\u0026amp; export PATH=$PATH:$HOME/bin \u0026amp;\u0026amp; mv container-diff-linux-amd64 $HOME/bin/container-diff Windows # 下载地址: https://storage.googleapis.com/container-diff/latest/container-diff-windows-amd64.exe\n下载 exe 文件重命名为 container-diff.exe,添加到系统环境变量 PATH 中。\n使用 # 分析单个 Docker 镜像\ncontainer-diff analyze \u0026lt;image-name\u0026gt; 对比两个 Docker 镜像\ncontainer-diff diff \u0026lt;image1-name\u0026gt; \u0026lt;image2-name\u0026gt; 如果不指定 type,默认分析/对比的是镜像大小,即 --type=size\n可以通过指定 type,分析/对比特定维度\ncontainer-diff analyze \u0026lt;image-name\u0026gt; --type=\u0026lt;type-name\u0026gt; container-diff diff \u0026lt;image1-name\u0026gt; \u0026lt;image2-name\u0026gt; --type=\u0026lt;type-name\u0026gt; type 类型支持如下:\nhistory:镜像构建历史 file:镜像文件 size:镜像大小 rpm:rpm 包管理器 pip:pip 包管理器 apt:apt 包管理器 node:node 包管理器 通过设置多组 type,可以一次性分析/对比多个维度,例如:\ncontainer-diff analyze nginx --type=history --type=size 通过设置 --type=file 和 --filename=/path/file,可以比较比较两个 docker 镜像中某目录或文件的区别,例如:\ncontainer-diff diff nginx:v1 nginx:v2 --type=file --filename=/etc/ 通过设置 -j,可以使用 json 格式输出结果。\n通过设置 -w \u0026lt;file-path\u0026gt;,可以将结果输入到文件。\n更多命令参数可以通过 -h 解锁。\n» Docker in Docker\n"},{"id":42,"href":"/docker/dind/","title":"Dind","section":"Docker","content":" 🏠 首页 / Docker / Docker in Docker\nDocker in Docker # Docker-in-Docker 的意思是在 Docker 容器中使用 docker,就像和在宿主机上使用 docker 一样,你可以理解为套娃。\n场景:\n如果你的 Jenkins 是使用 Docker 容器的方式运行的,如果你想使用 Jenkins 的 Docker 插件来为 Jenkins Job 提供运行容器,这时候你就需要用到 Docker-in-Docker;\n一般这个技术使用在应用的程序集成中 CI/CD。\n1. 挂载主机 /var/run/docker.sock # Docker 容器:\ndocker run -v /var/run/docker.sock:/var/run/docker.sock --name docker-in-docker -it docker 在运行起来的容器中使用docker:\n$ docker run -v /var/run/docker.sock:/var/run/docker.sock --name docker-in-docker -it docker / # docker run hello-world Hello from Docker! This message shows that your installation appears to be working correctly. ... 可以看到,在该容器中可以像在宿主机一样运行 docker 容器。\n但是,使用 docker ps -a 命令可以查看到宿主机的运行容器,这说明容器的权限是很大的,存在一定的安全隐患:\nKubernetes Pod:\n准备 docker-in-docker.yaml 文件如下:\napiVersion: v1 kind: Pod metadata: name: docker-in-docker spec: containers: - name: docker image: docker command: [\u0026#34;/bin/sh\u0026#34;, \u0026#34;-c\u0026#34;] args: - sleep 300s; volumeMounts: - name: docker-sock mountPath: /var/run/docker.sock volumes: - name: docker-sock hostPath: path: /var/run/docker.sock kubectl apply -f docker-in-docker.yaml kubectl exec -it docker-in-docker -c docker /bin/sh 同样,这种方式也能获取到宿主机的容器。\n2. 使用 Docker-Dind # Docker 容器:\ndocker run --privileged -d --name docker-in-docker docker:dind docker exec -it docker-in-docker /bin/sh 与上面方式不同的是,这种方式运行起来的 docker-in-docker 无法看到宿主机上的容器。\nKubernetes Pod:\n准备 docker-in-docker.yaml 文件如下:\napiVersion: v1 kind: Pod metadata: name: docker-in-docker spec: containers: - name: docker image: docker:18 imagePullPolicy: Always env: - name: DOCKER_HOST value: tcp://localhost:2375 command: [\u0026#34;/bin/sh\u0026#34;, \u0026#34;-c\u0026#34;] args: - sleep 300s; - name: docker-dind image: docker:18-dind securityContext: privileged: true restartPolicy: OnFailure 注意:\n需要两个容器,第一容器由 docker 镜像运行,第二容器由 docker:dind 镜像运行;\n第一容器需要设置环境变量:DOCKER_HOST=tcp://localhost:2375;\n第二容器使用特权模式运行。\nkubectl apply -f docker-in-docker.yaml kubectl exec -it docker-in-docker -c docker /bin/sh 3. 结束语 # 前面提到了,由于第一种方式挂载主机 /var/run/docker.sock,在容器视角依然能获取到宿主机的容器,也能运行或删除容器,存在一定的安全隐患,更推荐使用第二种方式,Over!\n« container-diff 工具的使用\n» docker buildx\n"},{"id":43,"href":"/docker/docker-buildx/","title":"Docker Buildx","section":"Docker","content":" 🏠 首页 / Docker / docker buildx\ndocker buildx # $ docker buildx Usage: docker buildx [OPTIONS] COMMAND Extended build capabilities with BuildKit Options: --builder string Override the configured builder instance Management Commands: imagetools Commands to work on images in registry Commands: bake Build from a file build Start a build create Create a new builder instance du Disk usage inspect Inspect current builder instance ls List builder instances prune Remove build cache rm Remove a builder instance stop Stop builder instance use Set the current builder instance version Show buildx version information Run \u0026#39;docker buildx COMMAND --help\u0026#39; for more information on a command. 本篇介绍如何使用 docker buildx 命令实现交叉编译不同系统架构下的 Docker 镜像。\n安装 # 如果根据 docker 官网的安装手册安装 docker,会默认安装 docker buildx。\n参照文档: Docker Buildx | Docker Documentation\n手动安装:\n下载地址: https://github.com/docker/buildx/releases/latest\n手动下载对应版本的二进制文件,重命名并拷贝到目标目录,例如:\nwget https://github.com/docker/buildx/releases/download/v0.8.1/buildx-v0.8.1.linux-amd64 mv buildx-v0.8.1.linux-amd64 $HOME/.docker/cli-plugins/docker-buildx QEMU:\ndocker run --privileged --rm tonistiigi/binfmt --install all 将buildx设置成默认的镜像编译器 # 因为 docker buildx build 命令后续的大部分参数与 docker build 完全一致,所以可以通过设置命令别名的方式,将 buildx 作为默认的镜像编译器。\n你只需要执行以下命令即可:\ndocker buildx install 如果需要取消这个命令别名,执行以下命令:\ndocker buildx uninstall 使用 # 开始一个新的构建,执行命令 docker buildx build .。\n创建实例\ndocker buildx create --name demo-builder # 创建并使用 docker buildx create --use --name demo-builder 使用实例\ndocker buildx use demo-builder 交叉编译\ndocker buildx build . -t demo:latest 删除实例\ndocker buildx rm demo-builder « Docker in Docker\n» Docker 常用命令\n"},{"id":44,"href":"/docker/docker-commands/","title":"Docker Commands","section":"Docker","content":" 🏠 首页 / Docker / Docker 常用命令\nDocker 常用命令 # 启动容器命令 # 默认需要sudo权限执行\nsudo docker run -d -p 80:80 --name nginx nginx \u0026ndash;name:容器命名\n-d:在后台启动\n-p:\u0026lt;host端口\u0026gt;:\u0026lt;容器端口\u0026gt;\n\u0026ndash;rm:容器退出即删除\n-it:i-与容器交互,t-终端\n以root权限进入容器 # sudo docker exec -it -u root nginx bash 让容器一直睡眠 # 使用 curlimages/curl 镜像,并让其一直睡眠。\ndocker run -d --name sleep curlimages/curl sleep infinity 操作镜像命令 # 查看镜像 # sudo docker images 删除镜像 # sudo docker rmi \u0026lt;image\u0026gt; # or sudo docker image rm \u0026lt;image\u0026gt; 删除所有镜像 # sudo docker rmi $(docker images -q) 清除未使用镜像 # sudo docker image prune # or sudo docker rmi $(sudo docker images | grep \u0026#34;^\u0026lt;none\u0026gt;\u0026#34; | awk \u0026#34;{print $3}\u0026#34;) 模糊清除镜像 # docker rmi $(docker images | grep \u0026#39;query\u0026#39; | awk \u0026#39;{print $3}\u0026#39;) 操作容器命令 # 查看已经退出的容器 # sudo docker ps -a | grep Exited 清理已经退出的容器 # sudo docker rm $(sudo docker ps -qf status=exited) # or sudo docker rm `sudo docker ps -a | grep Exited | awk \u0026#39;{print $1}\u0026#39;` 清除所有容器 # 使用 -f 参数才能清除所有容器,不使用则只会清理已经退出的容器\nsudo docker rm $(sudo docker ps -a -q) -f 清除孤立容器 # sudo docker container prune 强制删除容器 # 如果某个 Pod 突然不可用,那么运行在该节点上的 Pod 可能会一直处于 Terminating 的状态,无法移除。这时候如果想强制将该 Pod 从 etcd 数据库中删除,可以使用以下命令:\nkubectl delete po \u0026lt;pod-name\u0026gt; -n \u0026lt;namespace\u0026gt; --force --grace-period=0 grace-period 表示过渡存活期,默认 30s,在删除 Pod 之前允许 Pod 慢慢终止其上的容器进程,从而优雅退出,0 表示立即终止 Pod。\n« docker buildx\n» Docker Compose 实践\n"},{"id":45,"href":"/docker/docker-compose-practice/","title":"Docker Compose Practice","section":"Docker","content":" 🏠 首页 / Docker / Docker Compose 实践\nDocker Compose 实践 # 安装 # 如果你安装了 Docker Desktop,那么它已经帮你自动安装了 Docker Compose 插件。否则,需要额外安装插件。\n使用一下命令安装或升级 Docker Compose(linux):\nUbuntu,Debian: sudo apt update sudo apt install docker-compose-plugin 基于 RPM 发行版: sudo yum update sudo yum install docker-compose-plugin 验证安装版本:\ndocker-compose version 常用命令 # 运行\ndocker-compose up 查看运行\ndocker-compose ps 停止\ndocker-compose stop 启动\u0026amp;重启\ndocker-compose start docker-compose restart 退出\ndocker-compose down 使用 docker-compose -h 查看更多命令及参数。\n实践 # 使用 Docker Compose 运行一个简单的 golang web 程序。\n程序初始化 mkdir docker-compose-go-demo cd docker-compose-go-demo go mod init docker-compose-go-demo 创建 main.go 文件,并写入程序代码 package main import ( \u0026#34;fmt\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;time\u0026#34; ) func greet(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, \u0026#34;Hello Docker Compose! %s\u0026#34;, time.Now()) } func main() { http.HandleFunc(\u0026#34;/\u0026#34;, greet) http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil) } 创建 Dockerfile 文件,并编写内容 FROM golang:alpine WORKDIR /app COPY . . EXPOSE 8080 ENTRYPOINT [ \u0026#34;go\u0026#34;,\u0026#34;run\u0026#34;,\u0026#34;main.go\u0026#34; ] 创建 docker-comppose.yml 文件,并编写内容 version: \u0026#34;3.9\u0026#34; services: web: build: . # image: docker-compose-go-demo_web:v1 # image: docker-compose-go-demo_web:v2 ports: - \u0026#34;8080:8080\u0026#34; 启动服务 docker-compose up -d 场景:\nweb 服务业务代码修改了,希望不停机更新服务: docker-compose up -d --build 包含多个服务,例如中间件,但只想重新编译其中业务服务,如 web: docker-compose up -d --no-deps --build web 如果 docker-compose.yml 直接使用的镜像,那么直接更新,再次 docker-compose up -d 即可。\n« Docker 常用命令\n» Docker 容器中安装 PFX 证书\n"},{"id":46,"href":"/docker/docker-container-install-pfx-cert/","title":"Docker Container Install Pfx Cert","section":"Docker","content":" 🏠 首页 / Docker / Docker 容器中安装 PFX 证书\nDocker 容器中安装 PFX 证书 # 如果正在开发 .NetCore 项目,并且你的项目需要使用到 PFX 证书。此时你需要将你的项目发布到 Docker 容器中,那么你就需要在你的 Docker 容器中安装 PFX 证书了。\n代码中编写 # 使用 X509Store Api 编写你的程序\nusing (var certificate = new X509Certificate2(pfxFileBytes, pfxPassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet)) using (var store = new X509Store(storeName, storeLocation, OpenFlags.ReadWrite)) { store.Add(certificate); store.Close(); } Dockerfile 中编写 # 使用 dotnet-certificate-tool 工具安装 pfx 证书。\n首先获取到 Pfx 文件的 Thumbprint,这在 dotnet-certificate-tool 命令中作为参数被使用。\n使用 Powershell Get-PfxCertificate 函数获取 Thumbprint Get-PfxCertificate -FilePath C:\\Pfx\\Hello-to-World.pfx 执行以上命令,会要求输入 pfx 证书密码,输入密码后,你应该可以得到 Thumbprint 了,输出格式大致如下:\nThumbprint Subject ---------- ------- EED5DCA70697648CDFGD7FB1EFDDD683579B436E CN=Hello-to-World, OU=IT, O=Hello 在 Dockerfile 内容大致如下\nFROM mcr.microsoft.com/dotnet/core/sdk:3.1 WORKDIR /app COPY Hello-to-World.pfx ./ ENV PATH \u0026#34;$PATH:/root/.dotnet/tools\u0026#34; RUN dotnet tool install dotnet-certificate-tool -g \u0026amp;\u0026amp; certificate-tool add -f ./Hello-to-World.pfx -p Password123 -t EED5DCA70697648CDFGD7FB1EFDDD683579B436E ENTRYPOINT [\u0026#34;dotnet\u0026#34;, \u0026#34;PfxTest.dll\u0026#34;] « Docker Compose 实践\n» Docker 主机容器互拷贝文件\n"},{"id":47,"href":"/docker/docker-copy-between-host-container/","title":"Docker Copy Between Host Container","section":"Docker","content":" 🏠 首页 / Docker / Docker 主机容器互拷贝文件\nDocker 主机容器互拷贝文件 # 命令:docker cp\n1. 将 Docker 容器内文件拷贝到 Host # 获取 docker 容器的 Container ID 或Name\nsudo docker ps 使用以下命令从容器内拷出文件\nsudo docker cp [CONTAINER ID/NAME]:[CONTAINER_PATH] [HOST_PATH] 例如我需要将容器内 /app/appsettings.json 文件拷贝到宿主机的 ~/temp/ 目录 (该目录必须存在) 下\nsudo docker cp b3e608e28f21:/app/appsettings.json ~/temp/appsettings.json # 不指定文件名亦可,默认使用原文件名 sudo docker cp b3e608e28f21:/app/appsettings.json ~/temp/ 2. 将 Host 文件拷贝至 Docker 容器 # 同样,需要先获取容器的 Container ID 或 Name;\n使用以下命令将文件拷贝至容器内\nsudo docker cp [HOST_PATH] [CONTAINER ID/NAME]:[CONTAINER_PATH] 例如我需要将宿主机的 ~/temp/hello.txt 文件拷贝至容器内 /app/ 目录 (该目录必须存在) 下\nsudo docker cp ~/temp/hello.txt b3e608e28f21:/app/hello.txt # 不指定文件名亦可,默认使用原文件名 sudo docker cp ~/temp/hello.txt b3e608e28f21:/app/ « Docker 容器中安装 PFX 证书\n» 使用 docker manifest 命令构建多架构镜像\n"},{"id":48,"href":"/docker/docker-manifest-build-cross-arch-image/","title":"Docker Manifest Build Cross Arch Image","section":"Docker","content":" 🏠 首页 / Docker / 使用 docker manifest 命令构建多架构镜像\n使用 docker manifest 命令构建多架构镜像 # # 创建 docker manifest create poneding/myimage:v1 poneding/myimage-amd64:v1 poneding/myimage-arm64:v1 # 注解 docker manifest annotate poneding/myimage:v1 poneding/myimage-amd64:v1 --arch amd64 docker manifest annotate poneding/myimage:v1 poneding/asmyimageh-arm64:v1 --arch arm64 # 检查 docker manifest inspect poneding/myimage:v1 # 推送 docker manifest push poneding/myimage:v1 在 x86 机器上构建 arm64 镜像\ndocker run --rm --privileged multiarch/qemu-user-static --reset --persistent yes « Docker 主机容器互拷贝文件\n» 理解 docker run \u0026ndash;link\n"},{"id":49,"href":"/docker/docker-run-link/","title":"Docker Run Link","section":"Docker","content":" 🏠 首页 / Docker / 理解 docker run \u0026ndash;link\n理解 docker run \u0026ndash;link # 使用方式 # # 前提已经存在一个 container2 在运行 docker run img1 --name container1 --link container2 作用 # container1 连接 container2,达到:\n与 container2 直接通信 获取 container2 的环境变量 « 使用 docker manifest 命令构建多架构镜像\n» Docker 可视化工具 Kitematic\n"},{"id":50,"href":"/docker/docker-visiable-tool-kitematic/","title":"Docker Visiable Tool Kitematic","section":"Docker","content":" 🏠 首页 / Docker / Docker 可视化工具 Kitematic\nDocker 可视化工具 Kitematic # 使用 Kitematic,以可视化的方式管理 docker 镜像,容器等。\n安装 Kitematic # 在 ubuntu(desktop)中安装 kitematic 作为示例,其他平台安装下载地址: https://github.com/docker/kitematic/releases\n# download wget https://github.com/docker/kitematic/releases/download/v0.17.11/Kitematic-0.17.11-Ubuntu.zip unzip Kitematic-0.17.11-Ubuntu.zip # install sudo dpkg -i Kitematic-0.17.11_amd64.deb 用户组管理 # ubuntu 已经安装了 docker 了,当我们安装完 Kitematic 之后,第一次打开会遇到\n将当前用户加入到 docker 组:\nsudo usermod -aG docker $USER # 重启 docker sudo systemctl restart docker sudo chmod a+rw /var/run/docker.sock 完成上面操作后,重启主机,应该就可以使用 Kitamatic 了。\n使用 Kitematic # 第一次启动 Kitematic,需要登录 docker 账号,登录完成后,界面如下。\n主界面会列出一些热门的镜像,比如 redis,jenkins。可以通过切换右上角菜单选项,查看我的 Docker 账号中的镜像列表,以及我当前主机中已经拉取的镜像列表。\n点击镜像的 Create 按钮,可以直接以默认方式启动容器,比如我选择 redis 镜像,点击 Create 按钮,之后会在我的左侧 Containers 列表中出现一个 redis 容器,这个过程中包含拉取镜像,启动容器。\n容器运行后,可以在容器界面对容器进行容器配置,例如环境变量配置、端口映射配置、卷映射配置等;\n也可以在容器界面对容器进行容器管理,例如容器停止、重启、和删除。\n这个工具功能还在不断完善,但是使用体验还算不错,推荐给大家,更多使用细节可以自己慢慢挖掘。\n« 理解 docker run \u0026ndash;link\n» Dockerfile\n"},{"id":51,"href":"/docker/dockerfile/","title":"Dockerfile","section":"Docker","content":" 🏠 首页 / Docker / Dockerfile\nDockerfile # 官方文档参考: https://docs.docker.com/engine/reference/builder/\nDockerfile Linter: https://hadolint.github.io/hadolint/\nUsage # docker build [work-dir] -t [image-tag] -f [dockerfile-path] --build-arg [arg-key]=[arg-value] 指令 # Dockerfile reference | Docker Documentation\nFROM # ARG # 由docker build命令传的参数。\nARG在multi-stage的作用范围 # 如果ARG放置在第一个FROM之前,那么作用范围是全局的;如果ARG放在FROM之后,那么只对FROM的stage作用。\nARG USERNAME FROM alpine RUN echo hello, ${USERNAME} FROM alpine RUN echo hi, ${USERNAME} CMD # CMD 指令的目的是为一个可执行容器提供初始运行命令或运行参数。\nCMD 指令有三种形式:\n可执行命令 + 命令参数列表,推荐使用 CMD [\u0026#34;executable\u0026#34;,\u0026#34;param1\u0026#34;,\u0026#34;param2\u0026#34;] 命令参数列表,作为 ENTRYPOINT 的参数 CMD [\u0026#34;param1\u0026#34;,\u0026#34;param2\u0026#34;] Shell 形式,字符串形式的命令 CMD command param1 param2 单个 build stage 只允许存在一个 CMD 指令,如果存在多个 CMD 指令,只有最后一个 CMD 指令生效。\nENTRYPOINT # ENTRYPOINT 指令用于定义容器启动时被调用的可执行程序。\nENTRYPOINT 指令有两种形式,以运行 node 程序示例:\nexec 形式,推荐使用 ENTRYPOINT [\u0026#34;node\u0026#34;,\u0026#34;app.js\u0026#34;] Shell 形式 ENTRYPOINT node app.js 这两种形式的区别在于 shell 会在容器中运行 /bin/sh -c node app.js,而 exec 是直接运行 node app.js 命令,因此采用 exec 形式是更为合适的。\nQ\u0026amp;A # 1. Dockerfile 中 ARG 无法被 CMD 使用? # 可能你需要修改你的CMD:\nFROM alpine ARG USERNAME ENV USERNAME ${USERNAME} RUN echo ${USERNAME} # CMD [\u0026#34;echo\u0026#34;,\u0026#34;${USERNAME}\u0026#34;] # 会原样输出 ${USERNAME} CMD [\u0026#34;/bin/sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;echo ${USERNAME}\u0026#34;] # 输出 dp # 或者 # CMD echo ${USERNAME} # 输出 dp docker build . -t echo-user --build-arg USERNAME=dp docker run echo-user « Docker 可视化工具 Kitematic\n» Linux 容器\n"},{"id":52,"href":"/docker/linux-container/","title":"Linux Container","section":"Docker","content":" 🏠 首页 / Docker / Linux 容器\nLinux 容器 # 容器是轻量级的虚拟化技术。\n资源隔离和限制\n容器镜像 # 联合文件系统 # 允许文件存放在不同的层级上,但是最终可以通过统一的视图查看到这些层级的所有文件。\ncgroup # namespace # mount:文件系统隔离 uts:hostname domain pid:1号进程 network user ipc:进程间通信 cgroup « Dockerfile\n» 非 root 账号获取 docker 权限\n"},{"id":53,"href":"/docker/non-root-account-get-docker-permission/","title":"Non Root Account Get Docker Permission","section":"Docker","content":" 🏠 首页 / Docker / 非 root 账号获取 docker 权限\n非 root 账号获取 docker 权限 # 默认 docker 的命令是需要 sudo 权限的,如果你觉得麻烦,想直接在当前用户下执行 docker 权限,你可以尝试使用下面这个解决方案。\n拢共分两步:\n第一步,将当前用户添加到 docker 组\nsudo usermod -aG docker $USER 第二步,授权\nsudo chmod a+rw /var/run/docker.sock 快去试试吧。\n« Linux 容器\n» some-apps.md\n"},{"id":54,"href":"/docker/some-apps/","title":"Some Apps","section":"Docker","content":" 🏠 首页 / Docker / some-apps.md\nDocker 应用\nCloudreve # 项目地址: https://github.com/cloudreve/Cloudreve\ndocker run -d --name cloudreve \\ -p 5212:5212 \\ --mount type=bind,source=/root/apps/cloudreve/conf.ini,target=/cloudreve/conf.ini \\ --mount type=bind,source=/root/apps/cloudreve/cloudreve.db,target=/cloudreve/cloudreve.db \\ -v /root/apps/cloudreve/uploads:/cloudreve/uploads \\ -v /root/apps/cloudreve/avatar:/cloudreve/avatar \\ cloudreve/cloudreve:latest Etcd # docker run -d --name etcd \\ -p 12379:2379 \\ -p 12380:2380 \\ -e ALLOW_NONE_AUTHENTICATION=yes \\ -e ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 \\ -e ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379,http://0.0.0.0:2379 \\ -v /root/apps/etcd/data:/var/run/etcd \\ quay.io/coreos/etcd:v3.5.6 Minio # docker run -d --name minio \\ -p 9000:9000 \\ -p 9001:9001 \\ -e MINIO_ROOT_USER=minio \\ -e MINIO_ROOT_PASSWORD=\u0026#39;pd1n9@1024\u0026#39; \\ -v /root/apps/minio/data:/data \\ quay.io/minio/minio:latest server /data --console-address \u0026#34;:9001\u0026#34; MySQL # docker run -d --name mysql \\ -v /root/apps/mysql/data:/var/lib/mysql \\ -e MYSQL_ROOT_PASSWORD=\u0026#39;pd1n9@1024\u0026#39; \\ -p 3306:3306 \\ mysql:8.0 Postgres # docker run -d --name postgres \\ -p 5432:5432 \\ -e POSTGRES_USER=root \\ -e POSTGRES_PASSWORD=pd1n9@1024 \\ -v /root/apps/postgres/data:/var/lib/postgresql/data \\ postgre RabbitMQ # docker run -d --name rabbitmq \\ -p 15672:15672 \\ -p 5672:5672 \\ --hostname rabbitmq \\ -e RABBITMQ_DEFAULT_USER=pding \\ -e RABBITMQ_DEFAULT_PASS=P0n3_D1n9 \\ registry.cn-hangzhou.aliyuncs.com/pding/rabbitmq:3-management Redis # docker run -d --name redis \\ -v /root/apps/redis/data:/data \\ -p 6379:6379 \\ redis:7.0 redis-server --requirepass \u0026#34;pd1n9@1024\u0026#34; Transfer # docker run -d --name transfer \\ -p 7080:8080 \\ dutchcoders/transfer.sh:latest --provider local --basedir /tmp/ « 非 root 账号获取 docker 权限\n"},{"id":55,"href":"/ebpf/","title":"Ebpf","section":"","content":" 🏠 首页 / EBPF\nEBPF # eBPF\n"},{"id":56,"href":"/ebpf/ebpf/","title":"Ebpf","section":"Ebpf","content":" 🏠 首页 / EBPF / eBPF\neBPF # 简介 # eBPF (extended Berkeley Packet Filter) 是一项革命性的技术,起源于 Linux 内核,它可以在特权上下文中(如操作系统内核)运行沙盒程序。它用于安全有效地扩展内核的功能,而无需通过更改内核源代码或加载内核模块的方式来实现。\n从历史上看,由于内核具有监督和控制整个系统的特权,操作系统一直是实现可观测性、安全性和网络功能的理想场所。同时,由于操作系统内核的核心地位和对稳定性和安全性的高要求,操作系统内核很难快速迭代发展。因此在传统意义上,与在操作系统本身之外实现的功能相比,操作系统级别的创新速度要慢一些。\neBPF 从根本上改变了这个方式。通过允许在操作系统中运行沙盒程序的方式,应用程序开发人员可以运行 eBPF 程序,以便在运行时向操作系统添加额外的功能。然后在 JIT 编译器和验证引擎的帮助下,操作系统确保它像本地编译的程序一样具备安全性和执行效率。这引发了一股基于 eBPF 的项目热潮,它们涵盖了广泛的用例,包括下一代网络实现、可观测性和安全功能等领域。\n如今,eBPF 被广泛用于驱动各种用例:在现代数据中心和云原生环境中提供高性能网络和负载均衡,以低开销提取细粒度的安全可观测性数据,帮助应用程序开发人员跟踪应用程序,为性能故障排查、预防性的安全策略执行(包括应用层和容器运行时)提供洞察,等等。可能性是无限的,eBPF 开启的创新才刚刚开始。\n钩子 # eBPF 程序是事件驱动的,当内核或应用程序通过某个钩子点时运行。预定义的钩子包括系统调用、函数入口/退出、内核跟踪点、网络事件等。\n如果预定义的钩子不能满足特定需求,则可以创建内核探针(kprobe)或用户探针(uprobe),以便在内核或用户应用程序的几乎任何位置附加 eBPF 程序。\n编写程序 # 在很多情况下,eBPF 不是直接使用,而是通过像 Cilium、 bcc 或 bpftrace 这样的项目间接使用,这些项目提供了 eBPF 之上的抽象,不需要直接编写程序,而是提供了指定基于意图的来定义实现的能力,然后用 eBPF 实现。\n如果不存在更高层次的抽象,则需要直接编写程序。Linux 内核期望 eBPF 程序以字节码的形式加载。虽然直接编写字节码当然是可能的,但更常见的开发实践是利用像 LLVM 这样的编译器套件将伪 c 代码编译成 eBPF 字节码。\n安全性 # 由于 eBPF 允许我们在内核中运行任意代码,需要有一种机制来确保它的安全运行,不会使用户的机器崩溃,也不会损害他们的数据。这个机制就是 eBPF 验证器。\n验证器对 eBPF 程序进行分析,以确保无论输入什么,它都会在一定数量的指令内安全地终止。\n云原生领域 # 屏弃 SideCar 模式,将eBPF 加载到内核,跟随事件触发。\n项目 # Cilium:基于 eBPF 的数据平面的网络,可观测性、安全性的解决方案。 Pixie:基于 eBPF 实现的 Kubernetes 可观测性解决方案。 Hubble Calico 参考 # https://ebpf.io/ "},{"id":57,"href":"/front-end/","title":"Front End","section":"","content":" 🏠 首页 / 前端技术\n前端技术 # 搭建博客站点\nPinia 入门\nVitePress\n认识Vue3\n"},{"id":58,"href":"/front-end/build-blog-site/","title":"Build Blog Site","section":"Front End","content":" 🏠 首页 / 前端技术 / 搭建博客站点\n搭建博客站点 # 1. Hugo 搭建博客 # Hugo 是一个用 Go 语言编写的静态网站生成器。Hugo 的速度非常快,因为它是一个独立的二进制文件,不需要任何运行时依赖。Hugo 的主要特点是速度快、易于安装、易于使用、易于定制。\n1.1 安装 Hugo # 参考: https://gohugo.io/installation\n1.2 创建博客 # hugo new site blog --format yaml cd blog git init 1.3 选择主题 # 使用 hugo-book 主题。\ngit submodule add https://github.com/alex-shpak/hugo-book themes/hugo-book 2. 定制 # 2.1 配置 hugo.yaml # # hugo server --minify --themesDir ../.. --baseURL=http://0.0.0.0:1313/theme/hugo-book/ baseURL: https://blog.poneding.com/ title: 秋河落叶 theme: hugo-book pluralizeListTitles: false defaultContentLanguage: cn # Book configuration disablePathToLower: true enableGitInfo: true # Needed for mermaid/katex shortcodes markup: tableOfContents: startLevel: 2 endLevel: 3 # ordered: true highlight: noClasses: false # style: monokai menu: after: - name: \u0026#34;🔗 GitHub\u0026#34; url: \u0026#34;https://github.com/poneding\u0026#34; weight: 10 params: BookTheme: \u0026#34;auto\u0026#34; BookToC: true BookFavicon: logo.png BookLogo: logo.png BookMenuBundle: /menu BookSection: \u0026#34;none\u0026#34; BookRepo: https://github.com/poneding/blog BookCommitPath: commit BookEditPath: edit/master BookDateFormat: \u0026#34;2006/01/02\u0026#34; BookSearch: true BookComments: true BookPortableLinks: true BookServiceWorker: true BookTranslatedOnly: false 要注意的几个配置点:\nparams.BookSection: 本身指定一个 content 下的文档目录,我们这里设置一个不存在的目录,是为了不在左侧菜单栏展示我们的 N 多的目录树; markup.highlight.noClasses: 本身用来确认是否不使用自定义的 CSS 样式,我们这里设置为 false,因为我们需要使用自定义的 chorma 的代码高亮样式,跟随浏览器或系统自动切换代码高亮主题; 2.2 定制左侧菜单栏 # 创建 content/menu/index.md 文件,并添加如下内容:\nmkdir -p content/menu vim content/menu/index.md 菜单配置内容如下:\n--- headless: true --- - [**🏠 首页**](/) --- - **📌 置顶** - [Golang 编程](/go) - [Kubernetes](/kubernetes) - [Rust 编程](/rust) - [Git](/git) --- - **🔗 外链** 2.3 配置 giscus 评论 # 拷贝 hugo-book 的 layouts/_default/baseof.html 文件到 layouts/_default/baseof.html,命令操作如下:\nmkdir -p layouts/_default cp themes/hugo-book/layouts/_default/baseof.html layouts/_default/baseof.html 通过配置 giscus 获取 js 脚本代码,参考: https://giscus.app\n获取到的 js 脚本代码,在 layouts/_default/baseof.html 文件找到 {{- partial \u0026quot;docs/comments\u0026quot; . -}} 所在行,在其下一行添加 js 脚本代码,最终代码如下:\n... \u0026lt;div class=\u0026#34;book-comments\u0026#34;\u0026gt; {{- partial \u0026#34;docs/comments\u0026#34; . -}} \u0026lt;!-- start giscus --\u0026gt; \u0026lt;script src=\u0026#34;https://giscus.app/client.js\u0026#34; data-repo=\u0026#34;poneding/blog\u0026#34; data-repo-id=\u0026#34;R_kgDOMITIHg\u0026#34; data-category=\u0026#34;General\u0026#34; data-category-id=\u0026#34;DIC_kwDOMITIHs4CgB4x\u0026#34; data-mapping=\u0026#34;url\u0026#34; data-strict=\u0026#34;0\u0026#34; data-reactions-enabled=\u0026#34;1\u0026#34; data-emit-metadata=\u0026#34;0\u0026#34; data-input-position=\u0026#34;top\u0026#34; data-theme=\u0026#34;preferred_color_scheme\u0026#34; data-lang=\u0026#34;zh-CN\u0026#34; data-loading=\u0026#34;lazy\u0026#34; crossorigin=\u0026#34;anonymous\u0026#34; async\u0026gt; \u0026lt;/script\u0026gt; \u0026lt;!-- end giscus --\u0026gt; \u0026lt;/div\u0026gt; ... 2.4 代码主题自动切换 # 生成代码高亮样式文件,命令操作如下:\nmkdir -p static/css # light echo \u0026#34;@media (prefers-color-scheme: light) {\u0026#34; \u0026gt; static/css/syntax.css hugo gen chromastyles --style=monokailight \u0026gt;\u0026gt; static/css/syntax.css echo \u0026#34;}\u0026#34; \u0026gt;\u0026gt; static/css/syntax.css # dark echo \u0026#34;@media (prefers-color-scheme: dark) {\u0026#34; \u0026gt;\u0026gt; static/css/syntax.css hugo gen chromastyles --style=monokai \u0026gt;\u0026gt; static/css/syntax.css echo \u0026#34;}\u0026#34; \u0026gt;\u0026gt; static/css/syntax.css 拷贝 hugo-book 的 layouts/partials/docs/html-head.html 文件到 layouts/partials/docs/html-head.html,命令操作如下:\nmkdir -p layouts/partials/docs cp themes/hugo-book/layouts/partials/docs/html-head.html layouts/partials/docs/html-head.html # 引入样式文件 echo \u0026#39;\u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;/css/syntax.css\u0026#34;\u0026gt;\u0026#39; \u0026gt;\u0026gt; layouts/partials/docs/html-head.html 2.5 Logo # 将 logo.png 图片放到 static 目录下。\n3. 部署 # 3.1 自定义域名 # 我们已经在 hugo.yaml 中配置了 baseURL: https://blog.poneding.com/,我们还要创建一个 CNAME 文件,内容为 blog.poneding.com,然后将该文件放到 static 目录下。\necho \u0026#34;blog.poneding.com\u0026#34; \u0026gt; static/CNAME 3.2 使用 GitHub Actions 自动部署 # 前提:\n在 GitHub 上创建一个新的仓库,例如:poneding/blog; 配置 GitHub 仓库的 Settings -\u0026gt; Secrets:GH_TOKEN,值为 GitHub 个人访问令牌; mkdir -p .github/workflows vim .github/workflows/deploy.yml deploy.yml 文件内容如下:\nname: Deploy on: push: branches: - master # Set a branch to deploy workflow_dispatch: schedule: # Runs everyday at 8:00 AM - cron: \u0026#34;0 0 * * *\u0026#34; pull_request: jobs: deploy: runs-on: ubuntu-22.04 concurrency: group: ${{ github.workflow }}-${{ github.ref }} steps: - uses: actions/checkout@v4 with: submodules: true # Fetch Hugo themes (true OR recursive) fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod - name: Setup Hugo uses: peaceiris/actions-hugo@v3 with: hugo-version: \u0026#39;0.127.0\u0026#39; # extended: true - name: Build run: hugo --minify - name: Deploy uses: peaceiris/actions-gh-pages@v3 if: github.ref == \u0026#39;refs/heads/master\u0026#39; with: github_token: ${{ secrets.GH_TOKEN }} publish_dir: ./public 3.3 代码提交触发部署 # git add . git commit -m \u0026#34;init repo\u0026#34; git remote add origin git@github.com:poneding/blog.git git push -u origin master 3.4 GitHub Pages 配置 # 在 GitHub 仓库的 Settings -\u0026gt; Pages 中配置 Source 为 Deploy from a branch, Branch 为 gh-pages 分支,root 为 /。\n4. SEO 配置 # 参考:\nGoogle 百度 Bing » Pinia 入门\n"},{"id":59,"href":"/front-end/pinia/","title":"Pinia","section":"Front End","content":" 🏠 首页 / 前端技术 / Pinia 入门\nPinia 入门 # 什么是pinia # Pinia 是 Vue 的专属状态管理库,可以实现跨组件或页面共享状态,是 vuex 状态管理工具的替代品,和 Vuex相比,具备以下优势\n提供更加简单的API (去掉了 mutation ) 提供符合组合式API风格的API (和 Vue3 新语法统一) 去掉了modules的概念,每一个store都是一个独立的模块 搭配 TypeScript 一起使用提供可靠的类型推断 创建空Vue项目并安装Pinia # 1. 创建空Vue项目 # npm init vue@latest 2. 安装Pinia并注册 # npm i pinia import { createPinia } from \u0026#39;pinia\u0026#39; const app = createApp(App) // 以插件的形式注册 app.use(createPinia()) app.use(router) app.mount(\u0026#39;#app\u0026#39;) 实现counter # 核心步骤:\n定义store 组件使用store 1- 定义store\nimport { defineStore } from \u0026#39;pinia\u0026#39; import { ref } from \u0026#39;vue\u0026#39; export const useCounterStore = defineStore(\u0026#39;counter\u0026#39;, ()=\u0026gt;{ // 数据 (state) const count = ref(0) // 修改数据的方法 (action) const increment = ()=\u0026gt;{ count.value++ } // 以对象形式返回 return { count, increment } }) 2- 组件使用store\n\u0026lt;script setup\u0026gt; // 1. 导入use方法 import { useCounterStore } from \u0026#39;@/stores/counter\u0026#39; // 2. 执行方法得到store store里有数据和方法 const counterStore = useCounterStore() \u0026lt;/script\u0026gt; \u0026lt;template\u0026gt; \u0026lt;button @click=\u0026#34;counterStore.increment\u0026#34;\u0026gt; {{ counterStore.count }} \u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt; 实现getters # getters直接使用计算属性即可实现\n// 数据(state) const count = ref(0) // getter (computed) const doubleCount = computed(() =\u0026gt; count.value * 2) 异步action # 思想:action函数既支持同步也支持异步,和在组件中发送网络请求写法保持一致 步骤:\nstore中定义action 组件中触发action 1- store中定义action\nconst API_URL = \u0026#39;http://geek.itheima.net/v1_0/channels\u0026#39; export const useCounterStore = defineStore(\u0026#39;counter\u0026#39;, ()=\u0026gt;{ // 数据 const list = ref([]) // 异步action const loadList = async ()=\u0026gt;{ const res = await axios.get(API_URL) list.value = res.data.data.channels } return { list, loadList } }) 2- 组件中调用action\n\u0026lt;script setup\u0026gt; import { useCounterStore } from \u0026#39;@/stores/counter\u0026#39; const counterStore = useCounterStore() // 调用异步action counterStore.loadList() \u0026lt;/script\u0026gt; \u0026lt;template\u0026gt; \u0026lt;ul\u0026gt; \u0026lt;li v-for=\u0026#34;item in counterStore.list\u0026#34; :key=\u0026#34;item.id\u0026#34;\u0026gt;{{ item.name }}\u0026lt;/li\u0026gt; \u0026lt;/ul\u0026gt; \u0026lt;/template\u0026gt; storeToRefs保持响应式解构 # 直接基于store进行解构赋值,响应式数据(state和getter)会丢失响应式特性,使用storeToRefs辅助保持响应式\n\u0026lt;script setup\u0026gt; import { storeToRefs } from \u0026#39;pinia\u0026#39; import { useCounterStore } from \u0026#39;@/stores/counter\u0026#39; const counterStore = useCounterStore() // 使用它storeToRefs包裹之后解构保持响应式 const { count } = storeToRefs(counterStore) const { increment } = counterStore \u0026lt;/script\u0026gt; \u0026lt;template\u0026gt; \u0026lt;button @click=\u0026#34;increment\u0026#34;\u0026gt; {{ count }} \u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt; « 搭建博客站点\n» VitePress\n"},{"id":60,"href":"/front-end/vitepress/","title":"Vitepress","section":"Front End","content":" 🏠 首页 / 前端技术 / VitePress\nVitePress # 搭建项目 # mkdir vitepress-demo npm add -D vitepress npx vitepress init 运行 # npm run docs:dev # 或者直接调用 VitePress npx vitepress dev docs 打包 # npm run docs:build GitHub Action # .github/workflows/deploy.yaml\n# 构建 VitePress 站点并将其部署到 GitHub Pages 的示例工作流程 # name: Deploy VitePress site to Pages on: # 在针对 `main` 分支的推送上运行。如果你 # 使用 `master` 分支作为默认分支,请将其更改为 `master` push: branches: [master] # 允许你从 Actions 选项卡手动运行此工作流程 workflow_dispatch: # 设置 GITHUB_TOKEN 的权限,以允许部署到 GitHub Pages permissions: contents: read pages: write id-token: write # 只允许同时进行一次部署,跳过正在运行和最新队列之间的运行队列 # 但是,不要取消正在进行的运行,因为我们希望允许这些生产部署完成 concurrency: group: pages cancel-in-progress: false jobs: # 构建工作 build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 # 如果未启用 lastUpdated,则不需要 # - uses: pnpm/action-setup@v3 # 如果使用 pnpm,请取消注释 # - uses: oven-sh/setup-bun@v1 # 如果使用 Bun,请取消注释 - name: Setup Node uses: actions/setup-node@v4 with: node-version: 20 cache: npm # 或 pnpm / yarn - name: Setup Pages uses: actions/configure-pages@v4 - name: Install dependencies run: npm ci # 或 pnpm install / yarn install / bun install - name: Build with VitePress run: npm run docs:build # 或 pnpm docs:build / yarn docs:build / bun run docs:build - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: path: .vitepress/dist # 部署工作 deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} needs: build runs-on: ubuntu-latest name: Deploy steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 需要该 GitHub 仓库开启 Action 并且配置 GITHUB_TOKEN。\n« Pinia 入门\n» 认识Vue3\n"},{"id":61,"href":"/front-end/vue3/","title":"Vue3","section":"Front End","content":" 🏠 首页 / 前端技术 / 认识Vue3\n认识Vue3 # 1. Vue3组合式API体验 # 通过 Counter 案例 体验Vue3新引入的组合式API\n\u0026lt;script\u0026gt; export default { data(){ return { count:0 } }, methods:{ addCount(){ this.count++ } } } \u0026lt;/script\u0026gt; \u0026lt;script setup\u0026gt; import { ref } from \u0026#39;vue\u0026#39; const count = ref(0) const addCount = ()=\u0026gt; count.value++ \u0026lt;/script\u0026gt; 特点:\n代码量变少 分散式维护变成集中式维护 2. Vue3更多的优势 # 使用create-vue搭建Vue3项目 # 1. 认识create-vue # create-vue是Vue官方新的脚手架工具,底层切换到了 vite (下一代前端工具链),为开发提供极速响应\n2. 使用create-vue创建项目 # 前置条件 - 已安装16.0或更高版本的Node.js\n执行如下命令,这一指令将会安装并执行 create-vue\nnpm init vue@latest 熟悉项目和关键文件 # 组合式API - setup选项 # 1. setup选项的写法和执行时机 # 写法\n\u0026lt;script\u0026gt; export default { setup(){ }, beforeCreate(){ } } \u0026lt;/script\u0026gt; 执行时机\n在beforeCreate钩子之前执行\n2. setup中写代码的特点 # 在setup函数中写的数据和方法需要在末尾以对象的方式return,才能给模版使用\n\u0026lt;script\u0026gt; export default { setup(){ const message = \u0026#39;this is message\u0026#39; const logMessage = ()=\u0026gt;{ console.log(message) } // 必须return才可以 return { message, logMessage } } } \u0026lt;/script\u0026gt; 3. \u0026lt;script setup\u0026gt; 语法糖 # script标签添加 setup标记,不需要再写导出语句,默认会添加导出语句\n\u0026lt;script setup\u0026gt; const message = \u0026#39;this is message\u0026#39; const logMessage = ()=\u0026gt;{ console.log(message) } \u0026lt;/script\u0026gt; 组合式API - reactive和ref函数 # 1. reactive # 接受对象类型数据的参数传入并返回一个响应式的对象\n\u0026lt;script setup\u0026gt; // 导入 import { reactive } from \u0026#39;vue\u0026#39; // 执行函数 传入参数 变量接收 const state = reactive({ msg:\u0026#39;this is msg\u0026#39; }) const setSate = ()=\u0026gt;{ // 修改数据更新视图 state.msg = \u0026#39;this is new msg\u0026#39; } \u0026lt;/script\u0026gt; \u0026lt;template\u0026gt; {{ state.msg }} \u0026lt;button @click=\u0026#34;setState\u0026#34;\u0026gt;change msg\u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt; 2. ref # 接收简单类型或者对象类型的数据传入并返回一个响应式的对象\n\u0026lt;script setup\u0026gt; // 导入 import { ref } from \u0026#39;vue\u0026#39; // 执行函数 传入参数 变量接收 const count = ref(0) const setCount = ()=\u0026gt;{ // 修改数据更新视图必须加上.value count.value++ } \u0026lt;/script\u0026gt; \u0026lt;template\u0026gt; \u0026lt;button @click=\u0026#34;setCount\u0026#34;\u0026gt;{{count}}\u0026lt;/button\u0026gt; \u0026lt;/template\u0026gt; 3. reactive 对比 ref # 都是用来生成响应式数据 不同点 reactive不能处理简单类型的数据 ref参数类型支持更好,但是必须通过.value做访问修改 ref函数内部的实现依赖于reactive函数 在实际工作中的推荐 推荐使用ref函数,减少记忆负担,小兔鲜项目都使用ref 组合式API - computed # 计算属性基本思想和Vue2保持一致,组合式API下的计算属性只是修改了API写法\n\u0026lt;script setup\u0026gt; // 导入 import {ref, computed } from \u0026#39;vue\u0026#39; // 原始数据 const count = ref(0) // 计算属性 const doubleCount = computed(()=\u0026gt;count.value * 2) // 原始数据 const list = ref([1,2,3,4,5,6,7,8]) // 计算属性list const filterList = computed(item=\u0026gt;item \u0026gt; 2) \u0026lt;/script\u0026gt; 组合式API - watch # 侦听一个或者多个数据的变化,数据变化时执行回调函数,俩个额外参数 immediate控制立刻执行,deep开启深度侦听\n1. 侦听单个数据 # \u0026lt;script setup\u0026gt; // 1. 导入watch import { ref, watch } from \u0026#39;vue\u0026#39; const count = ref(0) // 2. 调用watch 侦听变化 watch(count, (newValue, oldValue)=\u0026gt;{ console.log(`count发生了变化,老值为${oldValue},新值为${newValue}`) }) \u0026lt;/script\u0026gt; 2. 侦听多个数据 # 侦听多个数据,第一个参数可以改写成数组的写法\n\u0026lt;script setup\u0026gt; // 1. 导入watch import { ref, watch } from \u0026#39;vue\u0026#39; const count = ref(0) const name = ref(\u0026#39;cp\u0026#39;) // 2. 调用watch 侦听变化 watch([count, name], ([newCount, newName],[oldCount,oldName])=\u0026gt;{ console.log(`count或者name变化了,[newCount, newName],[oldCount,oldName]) }) \u0026lt;/script\u0026gt; 3. immediate # 在侦听器创建时立即出发回调,响应式数据变化之后继续执行回调\n\u0026lt;script setup\u0026gt; // 1. 导入watch import { ref, watch } from \u0026#39;vue\u0026#39; const count = ref(0) // 2. 调用watch 侦听变化 watch(count, (newValue, oldValue)=\u0026gt;{ console.log(`count发生了变化,老值为${oldValue},新值为${newValue}`) },{ immediate: true }) \u0026lt;/script\u0026gt; 4. deep # 通过watch监听的ref对象默认是浅层侦听的,直接修改嵌套的对象属性不会触发回调执行,需要开启deep\n\u0026lt;script setup\u0026gt; // 1. 导入watch import { ref, watch } from \u0026#39;vue\u0026#39; const state = ref({ count: 0 }) // 2. 监听对象state watch(state, ()=\u0026gt;{ console.log(\u0026#39;数据变化了\u0026#39;) }) const changeStateByCount = ()=\u0026gt;{ // 直接修改不会引发回调执行 state.value.count++ } \u0026lt;/script\u0026gt; \u0026lt;script setup\u0026gt; // 1. 导入watch import { ref, watch } from \u0026#39;vue\u0026#39; const state = ref({ count: 0 }) // 2. 监听对象state 并开启deep watch(state, ()=\u0026gt;{ console.log(\u0026#39;数据变化了\u0026#39;) },{deep:true}) const changeStateByCount = ()=\u0026gt;{ // 此时修改可以触发回调 state.value.count++ } \u0026lt;/script\u0026gt; 组合式API - 生命周期函数 # 1. 选项式对比组合式 # 2. 生命周期函数基本使用 # 导入生命周期函数 执行生命周期函数,传入回调 \u0026lt;scirpt setup\u0026gt; import { onMounted } from \u0026#39;vue\u0026#39; onMounted(()=\u0026gt;{ // 自定义逻辑 }) \u0026lt;/script\u0026gt; 3. 执行多次 # 生命周期函数执行多次的时候,会按照顺序依次执行\n\u0026lt;scirpt setup\u0026gt; import { onMounted } from \u0026#39;vue\u0026#39; onMounted(()=\u0026gt;{ // 自定义逻辑 }) onMounted(()=\u0026gt;{ // 自定义逻辑 }) \u0026lt;/script\u0026gt; 组合式API - 父子通信 # 1. 父传子 # 基本思想\n父组件中给子组件绑定属性 子组件内部通过props选项接收数据 2. 子传父 # 基本思想\n父组件中给子组件标签通过@绑定事件 子组件内部通过 emit 方法触发事件 组合式API - 模版引用 # 概念:通过 ref标识 获取真实的 dom对象或者组件实例对象\n1. 基本使用 # 实现步骤:\n调用ref函数生成一个ref对象 通过ref标识绑定ref对象到标签 2. defineExpose # 默认情况下在 \u0026lt;script setup\u0026gt; 语法糖下组件内部的属性和方法是不开放给父组件访问的,可以通过defineExpose编译宏指定哪些属性和方法容许访问 说明:指定testMessage属性可以被访问到\n组合式API - provide和inject # 1. 作用和场景 # 顶层组件向任意的底层组件传递数据和方法,实现跨层组件通信\n2. 跨层传递普通数据 # 实现步骤\n顶层组件通过 provide 函数提供数据 底层组件通过 inject 函数提供数据 3. 跨层传递响应式数据 # 在调用provide函数时,第二个参数设置为ref对象\n4. 跨层传递方法 # 顶层组件可以向底层组件传递方法,底层组件调用方法修改顶层组件的数据\n综合案例 # 1. 项目地址 # git clone http://git.itcast.cn/heimaqianduan/vue3-basic-project.git 2. 项目说明 # 模版已经配置好了案例必须的安装包 案例用到的接口在 README.MD文件 中 案例项目有俩个分支,main主分支为开发分支,complete分支为完成版分支供开发完参考 « VitePress\n"},{"id":62,"href":"/git/","title":"Git","section":"","content":" 🏠 首页 / Git\nGit # Git 常用\n使用 git-secret 保护仓库敏感数据\nGithub Action 使用最佳实践\n使用 GitHub 托管 helm-chart 仓库\nGitHub 托管 helm-chart 仓库\nGitHub\nGitlab 添加 K8s 集群\nGitlab 跨版本升级\n多 GitHub 账号管理\n搭建最简单的 git 仓库服务\n"},{"id":63,"href":"/git/common-usage/","title":"Common Usage","section":"Git","content":" 🏠 首页 / Git / Git 常用\nGit 常用 # 本篇主要介绍 Git 的常用命令,包括 Git 的基本配置、创建仓库、添加文件、提交文件、查看状态、查看提交历史、撤销修改、删除文件、分支管理、远程仓库等。\nGit 基本配置 # 配置 SSH 密钥 # 提交代码到远程仓库时,需要使用 SSH 密钥进行身份验证,因此需要先配置 SSH 密钥。\n# 生成 SSH 密钥 ssh-keygen -t rsa -C poneding@gmail.com # 查看 SSH 密钥,复制到 GitHub/GitLab 等 SSH Keys 中 cat ~/.ssh/id_rsa.pub 添加 .ssh/config 文件,配置 SSH 密钥的别名,方便管理多个 SSH 密钥。\nvim ~/.ssh/config 以 GitHub 为例,配置如下:\n# GitHub Host github.com HostName github.com IdentityFile ~/.ssh/id_rsa 配置用户名和邮箱 # 提交代码时,需要配置用户名和邮箱。\ngit config --global user.name \u0026#34;poneding\u0026#34; git config --global user.email poneding@gmail.com # 只配置当前仓库的用户名和邮箱 git config user.name \u0026#34;poneding\u0026#34; git config user.email poneding@gmail.com 配置别名 # 通过配置别名,可以简化 Git 命令的输入,例如,将 git status 简化为 git st 获取 Git 仓库的状态。\ngit config --global alias.st status git config --global alias.ci commit git config --global alias.co checkout git config --global alias.br branch git config --global alias.ck checkout 配置默认分支 # 最新版本的 Git 默认分支为 main,如果需要修改为 master,可以通过如下命令进行修改。\ngit config --global init.defaultBranch master 配置 rabase # 当你运行 git pull 时,Git 会从远程仓库拉取最新的更改并将它们合并到你的本地分支。如果设置了 pull.rebase 为 true,Git 会在拉取远程更改后,不使用普通的 merge 操作,而是使用 rebase 将你本地的提交放在远程更改的后面。这样可以有效避免分支合并时产生大量的无用的 merge commit,有助于保持一个更线性、整洁的提交历史。\ngit config --global pull.rebase true Git 仓库 # 初始化仓库 # # 在当前目录初始化仓库 git init # 初始化并指定初始化分支 git init -b dev # 在指定目录创建仓库 git init mydir 克隆远程仓库 # # SSH 方式 git clone git@github.com/poneding/archives.git # HTTPS 方式 git clone https://github.com/kubernetes/kubernetes.git 提交本地更改到远程仓库 # # 拉取远程仓库的最新更改 git pull # 添加所有更改到暂存区 git add . # 提交更改 git commit -m \u0026#34;this commit\u0026#34; # 推送到远程仓库 git push # 首次推送到远程仓库 git push -u origin master # 推送所有本地分支到远程仓库 git push --all # 推送所有标签到远程仓库 git push --tags 添加远程仓库 # # 查看远程仓库 git remote -v # 添加远程仓库 git remote add origin git@github.com/poneding/archives.git # 修改远程仓库地址 git remote set-url origin git@github.com/poneding/archives-new.git # 删除远程仓库 git remote rm origin 暂存更改 # 添加文件 # # 将更改添加到暂存区 git stash # 查看暂存区的更改 git stash list # 取出暂存区的更改 git stash pop 提交修正 # 多次提交的内容其实可以合并成一次提交,这样可以保持提交历史的整洁。 想要修改提交的信息,例如提交信息描述不准确、拼写错误等。 # 修改最近一次提交的信息 git commit --amend -m \u0026#34;new commit message\u0026#34; 提交规范 # 规范化的 commit 提交有一些约定俗称的格式,下面快速枚举一些常见格式:\n# 功能类 git commit -m \u0026#34;feat: xxx\u0026#34; # 修复类 git commit -m \u0026#34;fix: xxx\u0026#34; # 杂事 git commit -m \u0026#34;chore: xxx\u0026#34; 撤销更改 # 还未 git add # 这里其实涉及到了两种更改:\n文件新增:使用 git clean -f 撤销更改 文件修改/删除:使用 git restore .(新版,推荐)或 git checkout .(旧版) 撤销更改 git clean -f \u0026amp;\u0026amp; git restore . 已经 git add,还未 git commit # # 取消所有暂存的更改 git reset git reset . git reset HEAD . # 撤销单个文件的暂存更改 git reset HEAD README.md 已经 git commit # # 获取最近一次提交的 commit_id git log # 撤销最近一次提交 git reset [commit_id] Git 分支 # 查看分支 # # 查看本地分支 git branch # 查看远程分支 git branch -r # 查看所有分支 git branch -a 创建分支 # # 创建新分支 git branch dev # 创建并切换到新分支 git checkout -b dev # 创建孤立分支,新分支没有任何提交历史,但会保留之前分支的所有文件 git checkout --orphan dev 同步远程分支 # 远程分支分两种:\n本地远程分支,可以通过 git branch -r 查看到; 远程仓库分支,可能是其他人创建的,可以通过 git fetch 同步到本地远程分支。 # 远程仓库有了新的分支,将远程分支同步到本地 git fetch # 将远程分支同步到本地 git checkout -b dev origin/dev 合并分支 # # 将当前分支 dev 的更改合并到 master 分支 git checkout master git merge dev git push 删除分支 # # 删除本地分支 git branch -d dev # 删除远程分支 git push origin -d dev 场景:远程仓库已经删除了分支,本地仓库还存在该分支,需要删除本地分支。\n# 1. 查看远程分支和本地分支的对应关系: git remote show origin # 2. 删除远程已经删除过的分支: git remote prune origin Git 标签 # 查看标签 # git tag 创建标签 # # 创建标签(轻量标签,只是某个提交的引用) git tag v1.0.0 # 创建标签(附注标签) git tag -a v1.0.0 -m \u0026#34;release v1.0.0\u0026#34; # 推送标签到远程仓库 git push origin v1.0.0 删除标签 # # 删除本地标签 git tag -d v1.0.0 # 删除远程标签 git push origin :refs/tags/v1.0.0 # 本地批量删除标签 git tag | xargs git tag -d # 本地删除 v1.1.0 开头的标签 git tag | grep \u0026#34;v1.1.0.\\d$\u0026#34; | xargs git tag -d # 批量删除远程标签 git show-ref --tag | awk \u0026#39;/(.*)(\\s+)(.*)$/ {print \u0026#34;:\u0026#34; $2}\u0026#39; | xargs git push origin Git Remote # 查看远程信息 # git remote -v git remote show 新增远程 # git remote add origin git@github.com:poneding/demo.git 更新远程地址 # git remote set-url origin git@github.com:poneding/demo.git 删除远程 # git remote remove origin 多远程 # git remote add origin git@github.com:poneding/demo.git git remote add origin2 git@gitlab.com:poneding/demo.git git push origin master git push origin2 master # 所有分支、tag 等 # git push --all origin # 所有分支 git push --mirror origin # git push --all origin2 # 所有分支 git push --mirror origin2 注意,后续提交到远程时:\n# 默认提交到 origin 远程 git push # 提交到 origin2 远程 git pull origin2 master git push origin2 master Git 信息 # 提交哈希值 # # 完整的提交哈希值 git rev-parse HEAD # 简短的提交哈希值 (7 位) git rev-parse --short HEAD 删除远程仓库提交历史 # rm -rf .git git init git add . git commit -m \u0026#34;first commit\u0026#34; git remote add origin git@github.com/poneding/archives.git # 强制推送到远程仓库,覆盖远程仓库的提交历史 git push -u --force origin master 排错 # Q1:Permissions 0664 for \u0026lsquo;~/.ssh/id_rsa\u0026rsquo; are too open # ssh 私钥文件权限过高,需要修改为 600。\nchmod 600 ~/.ssh/id_rsa Q1:证书颁发者未被识别 # 问题描述:Peer’s Certificate issuer is not recognized.\n解决方法:\n# 仓库级别,在仓库目录下执行 git config http.\u0026#34;sslVerify\u0026#34; false # 全局级别 git config --global http.\u0026#34;sslVerify\u0026#34; false » 使用 git-secret 保护仓库敏感数据\n"},{"id":64,"href":"/git/git-secret/","title":"Git Secret","section":"Git","content":" 🏠 首页 / Git / 使用 git-secret 保护仓库敏感数据\n使用 git-secret 保护仓库敏感数据 # 如何保护 git 仓库中的敏感数据,例如数据库连接字符串,账号密码等?\n首先,最好先将仓库设置成私有仓库!然后,\n第一种方式:带有敏感数据的文件加入到. gitignore,不提交到仓库中; 第二种方式:敏感数据库文件加密后再提交到仓库中,这个就是今天要说的 git-secret。 这两种方式都有优缺点:\n第一种方式,较为靠谱,敏感文件在 git 仓库之外,根本上避免仓库敏感数据的泄露,但是敏感文件不受版本控制了,开发人员需要在其他频道同步敏感文件的更新,而且使用到自动部署时需要另外去拉取敏感数据,最好是有自己的敏感数据配置中心统一管理;\n第二种方式,使用 git-secret 加密敏感文件,这样敏感文件被仓库 ignore 掉,转而提交加密后的文件,但是敏感文件如果更新了开发人员要记得再次加密。\ngit-secret 简介 # git-secret 是一个在 git 仓库中加密文件的工具,将敏感文件加密,得到加密文件,将文件保存到仓库中,这样敏感文件也是版本控制,你可以获取到该文件的所有提交记录。\n使用 gpg 和所有信任用户的公钥加密文件,每个信任用户可以使用个人密钥解密文件,如果用户离开团队,将删除用户的公钥即可,他也就不能再解密文件了。\ngit-secret 使用 # 假设我现在有一个仓库 git-secret-demo,仓库下有一个包含敏感信息的文件 secret.json:\n我现在想做的是使用 git-secret 将 secret.json 文件加密。\n首先得安装 gpg 工具 # Debian \u0026amp; Ubuntu sudo apt install gnupg -y ​ # Macos brew install gnupg 本地创建 gpg RSA 密钥对 gpg --gen-key 在创建时需要输入自己的用户名和邮箱,并且需要输入你的加密密码。\n导出你的公钥文件,你可以拿到这个公钥文件交给 git 仓库管理员,让他把你加入到仓库的信任用户列表:\ngpg --armor --export ding.peng@email.cn \u0026gt; dp-public-key.gpg 导入公钥文件,git 仓库管理员将公钥文件导入本地环境,之后可以将本地环境中的公钥加入到 git-secret 信任用户列表:\ngpg --import other-pulic-key.gpg 查看本地密钥对列表\ngpg --list-key 安装 git-secret # Debian \u0026amp; Ubuntu sudo apt install git-secret -y ​ # Macos brew install git-secret 使用 git secret init 初始化,在仓库根目录下执行 git secret init 默认会在仓库下生成 .gitsecret 目录和 .gitignore 文件。\n将用户加入到 git-secret 信任用户列表 git secret tell ding.peng@email.cn 添加加密文件到 git-secret git secret add -i secret.json 这里 -i 自动将要加密的文件添加到 .gitignore\n此时使用如下命令可以在目录下看到生成的 secret.json.secret 的加密后文件\ngit secret hide 到这时,你的加密文件不会提交到仓库中,从 git 仓库新克隆下来的时候是没有加密文件的,你需要解密文件才能得到文件,例如我现在删除加密文件,然后再通过解密加密过的文件重新得到敏感文件,使用命令:\ngit secret reveal # 第一次使用该命令需要输入信任用户的密码 从以上截图,你可以看到敏感文件 secret.json 失而复得的整个过程。\ngit-secret 常用命令 # 除了上面使用过的命令,你可能还会用到以下命令:\n**git secret usage:**列出 git secret 可用命令。 **git secret whoknows:**查看当前 git 仓库允许访问私密文件的用户邮箱列表。 git secret add:添加加密文件。 **git secret remove:**移除加密文件。 **git secret list:**列出加密文件。 **git secret changes:**查看加密文件的更改记录。 **git secret clean:**清除所有加密文件。 **git secret removeperson:**移除信任用户。 « Git 常用\n» Github Action 使用最佳实践\n"},{"id":65,"href":"/git/github-action-best-practice/","title":"Github Action Best Practice","section":"Git","content":" 🏠 首页 / Git / Github Action 使用最佳实践\nGithub Action 使用最佳实践 # Commit 构建 beta 版本镜像 # 仓库根目录下创建 .github/workflows/commit-cicd.yml 文件,用于提交代码触发 github action。\nbeta 版本的镜像 tag 命名规则:{vx.x.x}-beta-{COMMIT_ID},例如:v1.0.0-beta-f37cfa2\nname: commit-cicd ​ env: BASE_VERSION: v1.0.0 ​ on: push: branches: [main] workflow_dispatch: ​ jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 ​ - name: Set ENV run: | echo \u0026#34;VERSION=${BASE_VERSION}-beta-${GITHUB_SHA::7}\u0026#34; \u0026gt;\u0026gt; $GITHUB_ENV ​ - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 ​ - name: Login to docker hub uses: docker/login-action@v2 with: username: poneding password: ${{ secrets.DOCKER_PASSWORD }} ​ - name: Build and push id: docker_build uses: docker/build-push-action@v3 with: platforms: linux/amd64,linux/arm64 file: Dockerfile.multiarch push: true tags: poneding/demo:${VERSION} - name: Image digest run: echo ${{ steps.docker_build.outputs.digest }} ​ - name: Deploy demo id: deploy uses: actions-hub/kubectl@master env: KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }} with: args: set image deployment/demo demo=poneding/demo:${VERSION} Release 构建 stable 版本镜像 # 仓库根目录下创建 .github/workflows/release-cicd.yml文件,用于 Release 发布触发 github action。\nstable 版本镜像 tag 命名规则:vx.x.x-stable,例如:v1.0.0-stable\nname: release-cicd ​ on: release: types: [created] ​ jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 ​ - name: Set ENV run: | echo \u0026#34;VERSION=${GITHUB_REF#refs/*/}\u0026#34; \u0026gt;\u0026gt; $GITHUB_ENV ​ - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: Login to docker hub uses: docker/login-action@v2 with: username: poneding password: ${{ secrets.DOCKER_PASSWORD }} ​ - name: Build and push id: docker_build uses: docker/build-push-action@v3 env: VERSION: ${GITHUB_REF#refs/*/} with: platforms: linux/amd64,linux/arm64 file: Dockerfile.multiarch push: true tags: poneding/demo:latest,poneding/demo:${VERSION} - name: Image digest run: echo ${{ steps.docker_build.outputs.digest }} ​ - name: Deploy demo id: deploy uses: actions-hub/kubectl@master env: KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }} with: args: set image deployment/demo demo=poneding/demo:${VERSION} 镜像 tag 版本号来自于 git 中创建的 tag,所以如果要发布稳定版本,需要打一个命名为 vx.x.x-stable 的 tag,然后 Release 时,选择该 tag 即可。\n« 使用 git-secret 保护仓库敏感数据\n» 使用 GitHub 托管 helm-chart 仓库\n"},{"id":66,"href":"/git/github-host-helm-chart/","title":"Github Host Helm Chart","section":"Git","content":" 🏠 首页 / Git / 使用 GitHub 托管 helm-chart 仓库\n使用 GitHub 托管 helm-chart 仓库 # helm 官方文档:\nHelm | Chart Releaser Action to Automate GitHub Page Charts 创建 GitHub 仓库,例如:helm-charts,克隆到本地。 git clone git@github.com:[gh_id]/helm-charts.git cd helm-charts 创建干净的 gh-pages 分支。 git checkout --orphan gh-pages git rm -rf . vim README.md # helm-charts ## Usage [Helm](https://helm.sh) must be installed to use the charts. Please refer to Helm\u0026#39;s [documentation](https://helm.sh/docs) to get started. Once Helm has been set up correctly, add the repo as follows: ```bash helm repo add mycharts https://[gh_id].github.io/helm-charts ``` If you had already added this repo earlier, run `helm repo update` to retrieve the latest versions of the packages. You can then run`helm search repo mycharts` to see the charts. To install the mycharts chart: ```bash helm install myapp mycharts/myapp ``` To uninstall the chart: ```bash helm uninstall myapp ``` git add . git commit -am \u0026#34;add README.md\u0026#34; git push -u origin gh-pages 仓库启用 GitHub Pages 功能,选择 gh-pages 分支。\ncharts 开发。\ngit checkout master mkdir charts cd charts helm create myapp cd .. 使用 GitHub Action 搭配 chart-releaser 功能,为我们自动发布 charts 版本。 mkdir -p .github/workflows vim .github/workflows/release-chart.yaml name: Release Charts on: push: branches: - main jobs: release: # depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions # see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token permissions: contents: write runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 with: fetch-depth: 0 - name: Configure Git run: | git config user.name \u0026#34;$GITHUB_ACTOR\u0026#34; git config user.email \u0026#34;$GITHUB_ACTOR@users.noreply.github.com\u0026#34; - name: Install Helm uses: azure/setup-helm@v3 - name: Run chart-releaser uses: helm/chart-releaser-action@v1.5.0 env: CR_TOKEN: \u0026#34;${{ secrets.GITHUB_TOKEN }}\u0026#34; 需要提前将 GitHub Token 生成出来,并配置到仓库的 Secrets 中。\n提交代码即可第一次发版。\n之后对 charts 改动后,修改 Chart.yaml 中的 version 字段,helm-releaser 会检测版本改动,并自动发版。\n参考 # chart-releaser-action « Github Action 使用最佳实践\n» GitHub 托管 helm-chart 仓库\n"},{"id":67,"href":"/git/github-hosting-helm-reop/","title":"Github Hosting Helm Reop","section":"Git","content":" 🏠 首页 / Git / GitHub 托管 helm-chart 仓库\nGitHub 托管 helm-chart 仓库 # 创建 GitHub 仓库 # 创建 GitHub helm charts 仓库,例如:helm-charts,克隆到本地。\ngit clone git@github.com:poneding/helm-charts.git cd helm-charts 创建 gh-pages 孤立分支 # git checkout --orphan gh-pages git rm -rf . vim README.md 编写 README.md 文件,例如:\n# helm-charts ## Usage [Helm](https://helm.sh) must be installed to use the charts. Please refer to Helm\u0026#39;s [documentation](https://helm.sh/docs) to get started. Once Helm has been set up correctly, add the repo as follows: ```bash helm repo add poneding https://poneding.github.io/helm-charts ``` If you had already added this repo earlier, run `helm repo update` to retrieve the latest versions of the packages. You can then run`helm search repo mycharts` to see the charts. To install the mycharts chart: ```bash helm install myapp poneding/myapp ``` To uninstall the chart: ```bash helm uninstall myapp ``` 提交代码:\ngit add . git commit -am \u0026#34;Add README.md\u0026#34; git push -u origin gh-pages 启用 GitHub Pages # 仓库启用 GitHub Pages 功能,选择 gh-pages 分支。\nCharts 开发 # git checkout master mkdir charts cd charts helm create myapp cd .. 配置 GitHub Action # 使用 GitHub Action 搭配 chart-releaser 功能,为我们自动发布 charts 版本。\nmkdir -p .github/workflows vim .github/workflows/release-chart.yaml name: Release Charts on: push: branches: - master jobs: release: # depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions # see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token permissions: contents: write runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 with: fetch-depth: 0 - name: Configure Git run: | git config user.name \u0026#34;$GITHUB_ACTOR\u0026#34; git config user.email \u0026#34;$GITHUB_ACTOR@users.noreply.github.com\u0026#34; - name: Install Helm uses: azure/setup-helm@v3 - name: Run chart-releaser uses: helm/chart-releaser-action@v1.5.0 env: CR_TOKEN: \u0026#34;${{ secrets.GITHUB_TOKEN }}\u0026#34; 需要提前将 GitHub Token 生成出来,并配置到仓库的 Secrets 中。\n提交代码即可第一次发版。\n之后对 charts 改动后,修改 Chart.yaml 中的 version 字段,helm-releaser 会检测版本改动,并自动发版。\n参考 # Helm | Chart Releaser Action to Automate GitHub Page Charts helm/chart-releaser-action « 使用 GitHub 托管 helm-chart 仓库\n» GitHub\n"},{"id":68,"href":"/git/github/","title":"Github","section":"Git","content":" 🏠 首页 / Git / GitHub\nGitHub # GitHub 托管 helm chart 仓库 # GitHub 托管 helm chart 仓库\n获取仓库最新 Release 的版本 # 方法一:\ncurl -s https://api.github.com/repos/ketches/registry-proxy/releases/latest | jq -r .tag_name 方法二:\nbasename $(curl -s -w %{redirect_url} https://github.com/ketches/registry-proxy/releases/latest) « GitHub 托管 helm-chart 仓库\n» Gitlab 添加 K8s 集群\n"},{"id":69,"href":"/git/gitlab-intergrate-k8s/","title":"Gitlab Intergrate K8s","section":"Git","content":" 🏠 首页 / Git / Gitlab 添加 K8s 集群\nGitlab 添加 K8s 集群 # 本文介绍如何在 Gitlab 项目中添加 K8s 集群,以便使用 K8s 集群部署 gitlab-runner 帮我们运行 gitlab 的 CI/CD。\n参考官方文档: https://docs.gitlab.com/ee/user/project/clusters/add_remove_clusters.html#add-existing-cluster\n操作步骤 # 找到添加位置:\n登入 gitlab 后,进入自己的项目主页,菜单栏 Operations =\u0026gt; Kubernetes =\u0026gt; Add Kubernetes cluster,选择页签 Add existing cluster。\n我们只需要获取响应的值填录到该表单即可。Kubernetes cluster name 集群名称随意填,Project namespace 可不填。\n获取 API URL:\n运行以下命令得到输出值:\nkubectl cluster-info | grep \u0026#39;Kubernetes master\u0026#39; | awk \u0026#39;/http/ {print $NF}\u0026#39; 获取 CA Certificate:\n运行以下命令得到输出值:\nkubectl get secret $(kubectl get secret | grep default-token | awk \u0026#39;{print $1}\u0026#39;) -o jsonpath=\u0026#34;{[\u0026#39;data\u0026#39;][\u0026#39;ca\\.crt\u0026#39;]}\u0026#34; | base64 --decode 获取 Token:\n创建文件 gitlab-sa.yaml:\nvim gitlab-sa.yaml 文件写入如下内容:\napiVersion: v1 kind: ServiceAccount metadata: name: gitlab-sa namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: gitlab-sa roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: gitlab-sa namespace: kube-system 运行以下命令得到输出值:\nkubectl apply -f gitlab-sa.yaml kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep gitlab-sa | awk \u0026#39;{print $1}\u0026#39;) 添加完成之后,可以在集群中安装你想用的插件了,例如 gitlab-runner。\n踩坑记录 # Gitlab 与 Kubernetes 版本兼容问题\n在 Gitlab 中添加 Kubernetes 集群,可能存在两者版本不兼容的问题,这会导致 gitlab 调用 K8s 集群的 API 失败,可能是因为 K8s 不同版本的 api 更新的缘故。尽量使用最新版本的 Gitlab,他会支持更多版本的 K8s API。\n可以通过文档查看 Gitlab 支持的 K8s 版本: https://docs.gitlab.com/ee/user/project/clusters/index.html\n如果在添加集群遇到 is blocked: Requests to the local network are not allowed 问题,需要设置\nSettings =\u0026gt; Network =\u0026gt; Outbound requests,将选项 Allow requests to the local network from web hooks and services 勾选即可。\n« GitHub\n» Gitlab 跨版本升级\n"},{"id":70,"href":"/git/gitlab-upgrade-cross-version/","title":"Gitlab Upgrade Cross Version","section":"Git","content":" 🏠 首页 / Git / Gitlab 跨版本升级\nGitlab 跨版本升级 # 本文记录 Gitlab 跨版本升级的具体操作过程。\n按照官方的说法,gitlab 允许小版本直接升级,大版本需要阶段升级。\n跨版本升级示例:11.0.x -\u0026gt; 11.11.x -\u0026gt; 12.0.x -\u0026gt; 12.10.x -\u0026gt; 13.0.x。\n官方推荐的升级路线文档: https://docs.gitlab.com/ee/policy/maintenance.html#upgrade-recommendations\n目的 # 实现 gitlab 版本:11.2.3 到 13.0.0 版本的升级,我选择的升级路线是:11.2.3 =\u0026gt; 11.11.8 =\u0026gt; 12.0.12 =\u0026gt; 12.10.6 =\u0026gt; 13.0.0 =\u0026gt; 13.1.2\n我当前创建 gitlab 容器的脚本如下:\nsudo docker run --detach \\ --hostname gitlab.example.com \\ --publish 8443:443 --publish 8080:80 --publish 8022:22 \\ --name gitlab \\ --restart always \\ --volume /home/ubuntu/Apps/gitlab/etc/gitlab:/etc/gitlab \\ --volume /home/ubuntu/Apps/gitlab/var/log/gitlab/logs:/var/log/gitlab \\ --volume /home/ubuntu/Apps/gitlab/var/opt/gitlab:/var/opt/gitlab \\ gitlab/gitlab-ce:11.2.3-ce.0 我当前的 gitlab 容器已经将 /etc/gitlab,/var/log/gitlab,/var/opt/gitlab 挂载到了宿主机上。\n操作步骤 # 进入 gitlab 容器,停止 gitlab 服务,然后退出容器:\nsudo docker exec -it gitlab /bin/bash # 进入容器后执行 gitlab-ctl stop 退出容器后,删除 gitlab 容器:\nsudo docker rm gitlab -f 使用新版本的脚本运行 gitlab 容器,这里只修改 gitlab 的镜像版本就可以了:\nsudo docker run --detach \\ --hostname gitlab.example.com \\ --publish 8443:443 --publish 8080:80 --publish 8022:22 \\ --name gitlab \\ --restart always \\ --volume /home/ubuntu/Apps/gitlab/etc/gitlab:/etc/gitlab \\ --volume /home/ubuntu/Apps/gitlab/var/log/gitlab/logs:/var/log/gitlab \\ --volume /home/ubuntu/Apps/gitlab/var/opt/gitlab:/var/opt/gitlab \\ gitlab/gitlab-ce:11.2.3-ce.0 启动容器后,查看容器运行状态:\nsudo docker ps | grep gitlab 等到容器状态为 Up xxx (healthy) 后,进入容器,停掉 gitlab 服务。。。\n后面就是重复工作了,直到运行最新版本的gitlab容器。\nGitlab 备份 # 在升级之前,最好先对 gitlab 数据做一个全量备份,避免升级失败造成的不可逆影响。\n具体的还原操作如下:\n如果你和我一样使用 gitlab 容器,首先进入容器,然后执行备份命令:\ngitlab-rake gitlab:backup:create 以上命令执行完后,会在容器的 /var/opt/gitlab/backups 目录下创建文件名类似 1592276197_2020_06_16_11.2.3_gitlab_backup.tar 的备份文件。\nGitlab 还原 # 如果你想利用 gitlab 的备份文件还原,那么你运行还原操作的 gitlab 必须和备份时使用的 gitlab 版本一致,否则可能会出现还原失败的问题。\n具体的还原操作如下:\n如果你和我一样使用 gitlab 容器,首先将 gitlab 的备份文件拷贝到 /var/opt/gitlab/backups/ 目录下,然后进入 gitlab 容器,执行还原命令:\ngitlab-rake gitlab:backup:restore BACKUP=1592276197_2020_06_16_11.2.3 BACKUP 命令参数指定值为 /var/opt/gitlab/backups/ 目录下的备份文件,但是无需携带 _gitlab_backup.tar 后缀。\n« Gitlab 添加 K8s 集群\n» 多 GitHub 账号管理\n"},{"id":71,"href":"/git/multi-github-account-management/","title":"Multi Github Account Management","section":"Git","content":" 🏠 首页 / Git / 多 GitHub 账号管理\n多 GitHub 账号管理 # 实际开发工作中,你有可能多个 GitHub 账号:个人开发账号,工作开发账号。\n在仓库代码管理的过程中你需要重复的使用 git config user.* 来切换代码提交账号,很是麻烦。以下方案可以帮你解决你的烦恼。\n请确保你的 git 版本最低为 2.13\n~/.gitconfig\n[user] name = poneding email = poneding@gmail.com [includeIf \u0026#34;gitdir:~/src/workspace/\u0026#34;] path = ~/src/workspace/.gitconfig [url \u0026#34;git@github-workspace\u0026#34;] insteadOf = git@github.com [pull] rebase = false [init] defaultBranch = master [core] excludesfile = ~/.gitignore_global ~/src/workspace/.gitconfig\n[user] name = dingpeng24001 email = dingpeng24001@talkweb.com.cn [url \u0026#34;git@github-workspace\u0026#34;] insteadOf = git@github.com [pull] rebase = false [init] defaultBranch = master [core] excludesfile = ~/.gitignore_global ~/.ssh/config\nHost github.com HostName github.com IdentityFile ~/.ssh/id_rsa ​ Host github-workspace HostName github.com IdentityFile ~/.ssh/id_rsa_dingpeng24001 ~/.ssh/id_rsa 是个人账号 github ssh key\n~/.ssh/id_rsa_dingpeng24001 是工作账号 github ssh key\n« Gitlab 跨版本升级\n» 搭建最简单的 git 仓库服务\n"},{"id":72,"href":"/git/simplest-git-server/","title":"Simplest Git Server","section":"Git","content":" 🏠 首页 / Git / 搭建最简单的 git 仓库服务\n搭建最简单的 git 仓库服务 # 远端 # 创建仓库服务目录:\ngit init --bare git-server-demo.git 其实也可以直接在终端创建,但是你首先要可以能够通过 ssh 的方式连接远端,例如远端 IP 是 192.168.10.24\nssh root@192.168.10.24 git init --bare git-server-demo.git 执行完命令之后,将在远端目标目录下生成 git-server-demo 目录,子目录结构如下:\ntree git-server-demo.git git-server-demo.git ├── branches ├── config ├── description ├── HEAD ├── hooks │ ├── applypatch-msg.sample │ ├── commit-msg.sample │ ├── fsmonitor-watchman.sample │ ├── post-update.sample │ ├── pre-applypatch.sample │ ├── pre-commit.sample │ ├── pre-merge-commit.sample │ ├── prepare-commit-msg.sample │ ├── pre-push.sample │ ├── pre-rebase.sample │ ├── pre-receive.sample │ ├── push-to-checkout.sample │ └── update.sample ├── info │ └── exclude ├── objects │ ├── info │ └── pack └── refs ├── heads └── tags 终端 # 必要条件:可以 ssh 的方式登录远端服务器账号。\n尝试克隆代码:\ngit clone root@192.168.10.24:git-server-demo.git 剩余就是常规 git 步骤了。 # « 多 GitHub 账号管理\n"},{"id":73,"href":"/go/","title":"Go","section":"","content":" 🏠 首页 / Golang 编程\nGolang 编程 # Go 开发环境配置\nGolang 函数可选参数模式\nGolang 密钥对、数字签名和证书管理\nGolang 不同平台架构编译\nGolang 生成证书\ngo:linkname 指令\nGolang 列表转树\nGolang 实现双向认证\nGolang 发布类库 - 1\nGolang 发布类库 - 2\nGo 程序 SOLID 设计原则\nGolang 标准库\ntesting\nGolang\ngopkg-errors.md\nGoreleaser\nMac M1 交叉编译 CGO\npprof\n使用 Go 生成 OpenSSH 兼容的 RSA 密钥对\n"},{"id":74,"href":"/go/dev-env-config/","title":"Dev Env Config","section":"Go","content":" 🏠 首页 / Golang 编程 / Go 开发环境配置\nGo 开发环境配置 # cobra-cli # 安装:\ngo install github.com/spf13/cobra-cli@latest 自动补全:\ncobra-cli completion zsh \u0026gt; .zfunc/_cobra-cli 在 .zshrc 文件中添加内容(如果已添加,则忽略):\nfpath+=~/.zfunc autoload -Uz compinit \u0026amp;\u0026amp; compinit » Golang 函数可选参数模式\n"},{"id":75,"href":"/go/function-optional-pattern/","title":"Function Optional Pattern","section":"Go","content":" 🏠 首页 / Golang 编程 / Golang 函数可选参数模式\nGolang 函数可选参数模式 # 函数可选参数模式 # type Server struct { Addr string Timeout time.Duration } type Option func(*Server) func newServer(addr string, options ...Option) (*Server, error) { s := \u0026amp;Server{ Addr: addr, } for _, opt := range options { opt(s) } // ... return s, nil } func WithTimeout(timeout time.Duration) Option { return func(s *Server) { s.Timeout = timeout } } 通用函数可选参数模式 # type BasicService struct { redisClient string } type ServiceOption func(*BasicService) func WithRedisClient(redisClient string) ServiceOption { return func(s *BasicService) { s.redisClient = redisClient } } type UserService struct { *BasicService } type OrderService struct { *BasicService } func newUserService(opts ...ServiceOption) *UserService { us := \u0026amp;UserService{BasicService: new(BasicService)} for _, opt := range opts { opt(us.BasicService) } return us } func newOrderService(opts ...ServiceOption) *OrderService { os := \u0026amp;OrderService{BasicService: new(BasicService)} for _, opt := range opts { opt(os.BasicService) } return os } func TestNewService(t *testing.T) { us := newUserService(WithRedisClient(\u0026#34;redis\u0026#34;)) t.Log(us.redisClient) os := newOrderService(WithRedisClient(\u0026#34;redis\u0026#34;)) t.Log(os.redisClient) } « Go 开发环境配置\n» Golang 密钥对、数字签名和证书管理\n"},{"id":76,"href":"/go/go-cert-management/","title":"Go Cert Management","section":"Go","content":" 🏠 首页 / Golang 编程 / Golang 密钥对、数字签名和证书管理\nGolang 密钥对、数字签名和证书管理 # Golang 实现密钥对生成 相当于使用 openssl 生成私钥和公钥:\nopenssl genrsa -out pri.key 2048 openssl rsa -in pri.key -pubout -out pub.key package main import ( \u0026#34;crypto/rand\u0026#34; \u0026#34;crypto/rsa\u0026#34; ) func GenerateKeyPair() (*rsa.PrivateKey, *rsa.PublicKey, error) { prikey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return nil, nil, err } return prikey, \u0026amp;prikey.PublicKey, nil } 实现加密和解密 加密解密:公钥加密,私钥解密\npackage main import ( \u0026#34;crypto/rand\u0026#34; \u0026#34;crypto/rsa\u0026#34; ) func Encrypt(data []byte, publicKey *rsa.PublicKey) ([]byte, error) { return rsa.EncryptPKCS1v15(rand.Reader, publicKey, data) } func Decrypt(data []byte, privateKey *rsa.PrivateKey) ([]byte, error) { return rsa.DecryptPKCS1v15(rand.Reader, privateKey, data) } 实现数字签名 数字签名:私钥签名,公钥验证\npackage main import ( \u0026#34;crypto\u0026#34; \u0026#34;crypto/rand\u0026#34; \u0026#34;crypto/rsa\u0026#34; \u0026#34;crypto/sha256\u0026#34; ) func Sign(data []byte, privateKey *rsa.PrivateKey) ([]byte, error) { hash := sha256.New() hash.Write(data) return rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hash.Sum(nil)) } func Verify(data []byte, sig []byte, publicKey *rsa.PublicKey) error { hash := sha256.New() hash.Write(data) return rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hash.Sum(nil), sig) } « Golang 函数可选参数模式\n» Golang 不同平台架构编译\n"},{"id":77,"href":"/go/go-cross-complie/","title":"Go Cross Complie","section":"Go","content":" 🏠 首页 / Golang 编程 / Golang 不同平台架构编译\nGolang 不同平台架构编译 # 在 MacOS 平台编译成 Windows、Linux 可执行文件:\nCGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build main.go CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go 在 Windows 平台编译成 Linux、MacOS 可执行文件:\n$env:GOOS = \u0026#34;linux\u0026#34;;$env:CGO_ENABLED = \u0026#34;0\u0026#34;;$env:GOARCH = \u0026#34;amd64\u0026#34;;go build carbon/carbon.go $env:GOOS = \u0026#34;linux\u0026#34;;$env:CGO_ENABLED = \u0026#34;0\u0026#34;;$env:GOARCH = \u0026#34;arm64\u0026#34;;go build carbon/carbon.go $env:GOOS = \u0026#34;darwin\u0026#34;;$env:CGO_ENABLED = \u0026#34;0\u0026#34;;$env:GOARCH = \u0026#34;amd64\u0026#34;;go build carbon/carbon.go $env:GOOS = \u0026#34;darwin\u0026#34;;$env:CGO_ENABLED = \u0026#34;0\u0026#34;;$env:GOARCH = \u0026#34;arm64\u0026#34;;go build carbon/carbon.go 在 Linux 平台编译成 Windows、MacOS 可执行文件:\nCGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build main.go CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build main.go « Golang 密钥对、数字签名和证书管理\n» Golang 生成证书\n"},{"id":78,"href":"/go/go-gen-cert/","title":"Go Gen Cert","section":"Go","content":" 🏠 首页 / Golang 编程 / Golang 生成证书\nGolang 生成证书 # 代码实现 # package certutil import ( \u0026#34;bytes\u0026#34; \u0026#34;crypto/rand\u0026#34; \u0026#34;crypto/rsa\u0026#34; \u0026#34;crypto/x509\u0026#34; \u0026#34;crypto/x509/pkix\u0026#34; \u0026#34;encoding/pem\u0026#34; \u0026#34;math/big\u0026#34; \u0026#34;net\u0026#34; \u0026#34;time\u0026#34; ) // CA ca type CA struct { caInfo *x509.Certificate caPrivKey *rsa.PrivateKey caPem, caKeyPem []byte } // GetCAPem get ca pem bytes func (c *CA) GetCAPem() ([]byte, error) { if c.caPem == nil { // create the CA caBytes, err := x509.CreateCertificate(rand.Reader, c.caInfo, c.caInfo, \u0026amp;c.caPrivKey.PublicKey, c.caPrivKey) if err != nil { return nil, err } // pem encode caPEM := new(bytes.Buffer) _ = pem.Encode(caPEM, \u0026amp;pem.Block{ Type: \u0026#34;CERTIFICATE\u0026#34;, Bytes: caBytes, }) c.caPem = caPEM.Bytes() } return c.caPem, nil } // GetCAKeyPem get ca key pem func (c *CA) GetCAKeyPem() ([]byte, error) { if c.caKeyPem == nil { caPrivKeyPEM := new(bytes.Buffer) _ = pem.Encode(caPrivKeyPEM, \u0026amp;pem.Block{ Type: \u0026#34;RSA PRIVATE KEY\u0026#34;, Bytes: x509.MarshalPKCS1PrivateKey(c.caPrivKey), }) c.caKeyPem = caPrivKeyPEM.Bytes() } return c.caKeyPem, nil } // CreateCert make Certificate func (c *CA) CreateCert(ips []string, domains ...string) (certPem, certKey []byte, err error) { var ipAddresses []net.IP for _, ip := range ips { if i := net.ParseIP(ip); i != nil { ipAddresses = append(ipAddresses, i) } } // set up our server certificate cert := \u0026amp;x509.Certificate{ SerialNumber: big.NewInt(2019), Subject: pkix.Name{ Organization: []string{\u0026#34;poneding.com\u0026#34;}, Country: []string{\u0026#34;CN\u0026#34;}, Province: []string{\u0026#34;Beijing\u0026#34;}, Locality: []string{\u0026#34;Beijing\u0026#34;}, StreetAddress: []string{\u0026#34;Beijing\u0026#34;}, PostalCode: []string{\u0026#34;000000\u0026#34;}, }, DNSNames: domains, IPAddresses: ipAddresses, NotBefore: time.Now(), NotAfter: time.Now().AddDate(99, 0, 0), SubjectKeyId: []byte{1, 2, 3, 4, 6}, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, KeyUsage: x509.KeyUsageDigitalSignature, } certPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { return nil, nil, err } certBytes, err := x509.CreateCertificate(rand.Reader, cert, c.caInfo, \u0026amp;certPrivKey.PublicKey, c.caPrivKey) if err != nil { return nil, nil, err } certPEM := new(bytes.Buffer) _ = pem.Encode(certPEM, \u0026amp;pem.Block{ Type: \u0026#34;CERTIFICATE\u0026#34;, Bytes: certBytes, }) certPrivKeyPEM := new(bytes.Buffer) _ = pem.Encode(certPrivKeyPEM, \u0026amp;pem.Block{ Type: \u0026#34;RSA PRIVATE KEY\u0026#34;, Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey), }) return certPEM.Bytes(), certPrivKeyPEM.Bytes(), nil } // CreateCA create ca info func CreateCA() (*CA, error) { // set up our CA certificate ca := \u0026amp;x509.Certificate{ SerialNumber: big.NewInt(2019), Subject: pkix.Name{ Organization: []string{\u0026#34;poneding.com\u0026#34;}, Country: []string{\u0026#34;CN\u0026#34;}, Province: []string{\u0026#34;Beijing\u0026#34;}, Locality: []string{\u0026#34;Beijing\u0026#34;}, StreetAddress: []string{\u0026#34;Beijing\u0026#34;}, PostalCode: []string{\u0026#34;000000\u0026#34;}, }, NotBefore: time.Now(), NotAfter: time.Now().AddDate(99, 0, 0), IsCA: true, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, BasicConstraintsValid: true, } // create our private and public key caPrivKey, err := rsa.GenerateKey(rand.Reader, 4096) if err != nil { return nil, err } return \u0026amp;CA{ caInfo: ca, caPrivKey: caPrivKey, }, nil } // ParseCA parse caPem func ParseCA(caPem, caKeyPem []byte) (*CA, error) { p := \u0026amp;pem.Block{} p, caPem = pem.Decode(caPem) ca, err := x509.ParseCertificate(p.Bytes) if err != nil { return nil, err } p2 := \u0026amp;pem.Block{} p2, caKeyPem = pem.Decode(caKeyPem) caKey, err := x509.ParsePKCS1PrivateKey(p2.Bytes) if err != nil { return nil, err } return \u0026amp;CA{ caInfo: ca, caPrivKey: caKey, caPem: caPem, caKeyPem: caKeyPem, }, nil } // DomainSign create cert func DomainSign(ips []string, domains ...string) ([]byte, []byte, []byte, error) { ca, err := CreateCA() if err != nil { return nil, nil, nil, err } caPem, err := ca.GetCAPem() if err != nil { return nil, nil, nil, err } certPem, certKey, err := ca.CreateCert(ips, domains...) if err != nil { return nil, nil, nil, err } return caPem, certPem, certKey, nil } « Golang 不同平台架构编译\n» go:linkname 指令\n"},{"id":79,"href":"/go/go-linkname/","title":"Go Linkname","section":"Go","content":" 🏠 首页 / Golang 编程 / go:linkname 指令\ngo:linkname 指令 # 背景 # 阅读 Golang 源码时,发现在标准库 time.Sleep 方法没有没有方法体。如下:\n// Sleep pauses the current goroutine for at least the duration d. // A negative or zero duration causes Sleep to return immediately. func Sleep(d Duration) 当我们直接在代码中写一个空方法 func Foo(),编译时会报错:missing function body。所以标准库使用了什么魔法来实现空方法的呢? 进一步研究,得知 time.Sleep 运行时实际调用了 runtime.timeSleep方法,如下:\n// timeSleep puts the current goroutine to sleep for at least ns nanoseconds. // //go:linkname timeSleep time.Sleep func timeSleep(ns int64) { if ns \u0026lt;= 0 { return } gp := getg() t := gp.timer if t == nil { t = new(timer) gp.timer = t } t.f = goroutineReady t.arg = gp t.nextwhen = nanotime() + ns if t.nextwhen \u0026lt; 0 { // check for overflow. t.nextwhen = maxWhen } gopark(resetForSleep, unsafe.Pointer(t), waitReasonSleep, traceBlockSleep, 1) } 那么这是如何实现的呢?细心一点的话你就会发现在 runtime.timeSleep 的注释上发现 //go:linkname 指令,按我们就需要花点时间研究一下这个玩意儿了。\n介绍 # go:linkname指令的规范如下:\n//go:linkname localname importpath.name 这个指令实际是在告诉 Golang 编译器,将本地的变量或方法(localname)链接到导入的变量或方法(importpath.name)。 由于该指令破坏了类型系统和包的模块化原则,只有在引入 unsafe 包的前提下才能使用这个指令。 好了,现在我们知其所以然了,我们尝试来实现一个“空方法”吧!\n示例 # 创建一个项目:\nmake golinkname-demo cd golinkname-demo go mod init golinkname-demo touch main.go 编写 main.go 代码:\npackage main import ( \u0026#34;fmt\u0026#34; _ \u0026#34;unsafe\u0026#34; ) func main() { Foo() } //go:linkname Foo main.myFoo func Foo() func myFoo() { fmt.Println(\u0026#34;myFoo called\u0026#34;) } 运行:\n$ go run main.go myFoo called 完成!\n拓展 # 使用 go:linkname 来引用第三方包中私有的变量和方法:\npackage mypkg import ( _ \u0026#34;unsafe\u0026#34; _ \u0026#34;github.com/xxx/xxx/internal\u0026#34; ) //go:linkname a github.com/xxx/xxx/internal.a var a int //go:linkname Foo github.com/xxx/xxx/internal.foo func Foo() « Golang 生成证书\n» Golang 列表转树\n"},{"id":80,"href":"/go/go-list-to-tree/","title":"Go List to Tree","section":"Go","content":" 🏠 首页 / Golang 编程 / Golang 列表转树\nGolang 列表转树 # 场景介绍 # 从数据库获取到了菜单列表数据,这些菜单数据通过字段 ParentID 表示父子层级关系,现在需要将菜单列表数据转成树状的实例对象。\n数据库取出的初始数据:\nraw := []Menu{ {Name: \u0026#34;一级菜单 1\u0026#34;, ID: 1, PID: 0}, {Name: \u0026#34;一级菜单 2\u0026#34;, ID: 2, PID: 0}, {Name: \u0026#34;一级菜单 3\u0026#34;, ID: 3, PID: 0}, {Name: \u0026#34;二级菜单 1-1\u0026#34;, ID: 11, PID: 1}, {Name: \u0026#34;二级菜单 1-2\u0026#34;, ID: 12, PID: 1}, {Name: \u0026#34;二级菜单 1-3\u0026#34;, ID: 13, PID: 1}, {Name: \u0026#34;二级菜单 2-1\u0026#34;, ID: 21, PID: 2}, {Name: \u0026#34;二级菜单 2-2\u0026#34;, ID: 22, PID: 2}, {Name: \u0026#34;二级菜单 2-3\u0026#34;, ID: 23, PID: 2}, } 需要得到的目标数据:\n{ \u0026#34;name\u0026#34;:\u0026#34;根菜单\u0026#34;, \u0026#34;id\u0026#34;:0, \u0026#34;pid\u0026#34;:0, \u0026#34;SubMenus\u0026#34;:[ { \u0026#34;name\u0026#34;:\u0026#34;一级菜单 1\u0026#34;, \u0026#34;id\u0026#34;:1, \u0026#34;pid\u0026#34;:0, \u0026#34;SubMenus\u0026#34;:[ { \u0026#34;name\u0026#34;:\u0026#34;二级菜单 1-1\u0026#34;, \u0026#34;id\u0026#34;:11, \u0026#34;pid\u0026#34;:1 }, { \u0026#34;name\u0026#34;:\u0026#34;二级菜单 1-2\u0026#34;, \u0026#34;id\u0026#34;:12, \u0026#34;pid\u0026#34;:1 }, { \u0026#34;name\u0026#34;:\u0026#34;二级菜单 1-3\u0026#34;, \u0026#34;id\u0026#34;:13, \u0026#34;pid\u0026#34;:1 } ] }, { \u0026#34;name\u0026#34;:\u0026#34;一级菜单 2\u0026#34;, \u0026#34;id\u0026#34;:2, \u0026#34;pid\u0026#34;:0, \u0026#34;SubMenus\u0026#34;:[ { \u0026#34;name\u0026#34;:\u0026#34;二级菜单 2-1\u0026#34;, \u0026#34;id\u0026#34;:21, \u0026#34;pid\u0026#34;:2 }, { \u0026#34;name\u0026#34;:\u0026#34;二级菜单 2-2\u0026#34;, \u0026#34;id\u0026#34;:22, \u0026#34;pid\u0026#34;:2 }, { \u0026#34;name\u0026#34;:\u0026#34;二级菜单 2-3\u0026#34;, \u0026#34;id\u0026#34;:23, \u0026#34;pid\u0026#34;:2 } ] }, { \u0026#34;name\u0026#34;:\u0026#34;一级菜单 3\u0026#34;, \u0026#34;id\u0026#34;:3, \u0026#34;pid\u0026#34;:0 } ] } 代码实现 # package main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; ) func main() { // 数据库里存储的菜单 rawMenus := []Menu{ {Name: \u0026#34;一级菜单 1\u0026#34;, ID: 1, PID: 0}, {Name: \u0026#34;一级菜单 2\u0026#34;, ID: 2, PID: 0}, {Name: \u0026#34;一级菜单 3\u0026#34;, ID: 3, PID: 0}, {Name: \u0026#34;二级菜单 1-1\u0026#34;, ID: 11, PID: 1}, {Name: \u0026#34;二级菜单 1-2\u0026#34;, ID: 12, PID: 1}, {Name: \u0026#34;二级菜单 1-3\u0026#34;, ID: 13, PID: 1}, {Name: \u0026#34;二级菜单 2-1\u0026#34;, ID: 21, PID: 2}, {Name: \u0026#34;二级菜单 2-2\u0026#34;, ID: 22, PID: 2}, {Name: \u0026#34;二级菜单 2-3\u0026#34;, ID: 23, PID: 2}, } menu := \u0026amp;Menu{ Name: \u0026#34;根菜单\u0026#34;, PID: 0, } for _, rm := range rawMenus { menu.setSubMenus(rm) } // 打印结果 b, _ := json.Marshal(menu) fmt.Println(string(b)) } type Menu struct { Name string `json:\u0026#34;name\u0026#34;` ID int `json:\u0026#34;id\u0026#34;` PID int `json:\u0026#34;pid\u0026#34;` SubMenus []Menu `json:\u0026#34;,omitempty\u0026#34;` } func (m *Menu) setSubMenus(menu Menu) bool { if menu.PID == m.ID { m.SubMenus = append(m.SubMenus, menu) return true } for i := range m.SubMenus { if m.SubMenus[i].setSubMenus(menu) { return true } } return false } 注意事项 # Golang for 遍历使用 for _, item := range slice 时,item 是一份遍历元素的复制,而使用 for i := range slice 时,slice[i] 则是遍历元素本身,使用时需要注意切片扩容带来的地址变化问题。\n« go:linkname 指令\n» Golang 实现双向认证\n"},{"id":81,"href":"/go/go-mtls/","title":"Go Mtls","section":"Go","content":" 🏠 首页 / Golang 编程 / Golang 实现双向认证\nGolang 实现双向认证 # TLS # 传输层安全协议(TLS),在互联网上,通常是由服务器单向的向客户端提供证书,以证明其身份。\nmTLS # 双向 TLS 认证,是指在客户端和服务器之间使用双行加密通道,mTLS 是云原生应用中常用的通信安全协议。\n使用双向TLS连接的主要目的是当服务器应该只接受来自有限的允许的客户端的 TLS 连接时。例如,一个组织希望将服务器的 TLS 连接限制为只来自该组织的合法合作伙伴或客户。显然,为客户端添加IP白名单不是一个好的安全实践,因为IP可能被欺骗。\n为了简化 mTLS 握手的过程,我们这样简单梳理:\n客户端发送访问服务器上受保护信息的请求; 服务器向客户端提供公钥证书; 客户端通过使用 CA 的公钥来验证服务器公钥证书的数字签名,以验证服务器的证书; 如果步骤 3 成功,客户机将其客户端公钥证书发送到服务器; 服务器使用步骤 3 中相同的方法验证客户机的证书; 如果成功,服务器将对受保护信息的访问权授予客户机。 代码实现 # 需要实现客户端验证服务端的公钥证书,服务端验证客户端的公钥证书。\n生成证书 # echo \u0026#39;清理并生成目录\u0026#39; OUT=./certs DAYS=365 RSALEN=2048 CN=poneding rm -rf ${OUT}/* mkdir ${OUT} \u0026gt;\u0026gt; /dev/null 2\u0026gt;\u0026amp;1 cd ${OUT} echo \u0026#39;生成CA的私钥\u0026#39; openssl genrsa -out ca.key ${RSALEN} \u0026gt;\u0026gt; /dev/null 2\u0026gt;\u0026amp;1 echo \u0026#39;生成CA的签名证书\u0026#39; openssl req -new \\ -x509 \\ -key ca.key \\ -subj \u0026#34;/CN=${CN}\u0026#34; \\ -out ca.crt echo \u0026#39;\u0026#39; echo \u0026#39;生成server端私钥\u0026#39; openssl genrsa -out server.key ${RSALEN} \u0026gt;\u0026gt; /dev/null 2\u0026gt;\u0026amp;1 echo \u0026#39;生成server端自签名\u0026#39; openssl req -new \\ -key server.key \\ -subj \u0026#34;/CN=${CN}\u0026#34; \\ -out server.csr echo \u0026#39;签发server端证书\u0026#39; openssl x509 -req -sha256 \\ -in server.csr \\ -CA ca.crt -CAkey ca.key -CAcreateserial \\ -out server.crt -text \u0026gt;\u0026gt; /dev/null 2\u0026gt;\u0026amp;1 echo \u0026#39;删除server端自签名证书\u0026#39; rm server.csr echo \u0026#39;\u0026#39; echo \u0026#39;生成client私钥\u0026#39; openssl genrsa -out client.key ${RSALEN} \u0026gt;\u0026gt; /dev/null 2\u0026gt;\u0026amp;1 echo \u0026#39;生成client自签名\u0026#39; openssl req -new \\ -subj \u0026#34;/CN=${CN}\u0026#34; \\ -key client.key \\ -out client.csr echo \u0026#39;签发client证书\u0026#39; openssl x509 -req -sha256\\ -CA ca.crt -CAkey ca.key -CAcreateserial\\ -days ${DAYS}\\ -in client.csr\\ -out client.crt\\ -text \u0026gt;\u0026gt; /dev/null 2\u0026gt;\u0026amp;1 echo \u0026#39;删除client端自签名\u0026#39; rm client.csr echo \u0026#39;\u0026#39; echo \u0026#39;删除临时文件\u0026#39; rm ca.srl echo \u0026#39;\u0026#39; echo \u0026#39;完成\u0026#39; 服务端 # package main import ( \u0026#34;crypto/tls\u0026#34; \u0026#34;crypto/x509\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;os\u0026#34; \u0026#34;time\u0026#34; ) var ( caCert = \u0026#34;../../certs/ca.crt\u0026#34; serverCert = \u0026#34;../../certs/server.crt\u0026#34; serverKey = \u0026#34;../../certs/server.key\u0026#34; ) type mtlsHandler struct { } func (m *mtlsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, \u0026#34;Hello World! \u0026#34;, time.Now()) } func main() { pool := x509.NewCertPool() caCertBytes, err := os.ReadFile(caCert) if err != nil { panic(err) } pool.AppendCertsFromPEM(caCertBytes) server := \u0026amp;http.Server{ Addr: \u0026#34;:8443\u0026#34;, Handler: \u0026amp;mtlsHandler{}, TLSConfig: \u0026amp;tls.Config{ ClientCAs: pool, ClientAuth: tls.RequireAndVerifyClientCert, // 需要客户端证书 }, } log.Println(\u0026#34;server started...\u0026#34;) log.Fatalln(server.ListenAndServeTLS(serverCert, serverKey)) } 客户端 # package main import ( \u0026#34;crypto/tls\u0026#34; \u0026#34;crypto/x509\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;io\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;os\u0026#34; ) var ( caCert = \u0026#34;../../certs/ca.crt\u0026#34; clientCert = \u0026#34;../../certs/client.crt\u0026#34; clientKey = \u0026#34;../../certs/client.key\u0026#34; ) func main() { pool := x509.NewCertPool() caCertBytes, err := os.ReadFile(caCert) if err != nil { panic(err) } pool.AppendCertsFromPEM(caCertBytes) clientCertBytes, err := tls.LoadX509KeyPair(clientCert, clientKey) if err != nil { panic(err) } tr := \u0026amp;http.Transport{ TLSClientConfig: \u0026amp;tls.Config{ RootCAs: pool, Certificates: []tls.Certificate{clientCertBytes}, InsecureSkipVerify: true, }, } client := http.Client{ Transport: tr, } r, err := client.Get(\u0026#34;https://127.0.0.1:8443\u0026#34;) // server if err != nil { panic(err) } defer r.Body.Close() b, err := io.ReadAll(r.Body) if err != nil { panic(err) } fmt.Println(string(b)) } « Golang 列表转树\n» Golang 发布类库 - 1\n"},{"id":82,"href":"/go/go-publish-package-01/","title":"Go Publish Package 01","section":"Go","content":" 🏠 首页 / Golang 编程 / Golang 发布类库 - 1\nGolang 发布类库 - 1 # 本页介绍如何在 Github 上发布我们自己的 Golang 类库。\n1、创建 github 仓库托管 go 类库代码,例如 common-go::\n2、将仓库克隆至本地::\ngit clone https://github.com/poneding/common-go.git 3、初始化go类库的module::\ncd common-go go mod init github.com/poneding/common-go mkdir hello 注意:\n使用 go env 命令查看是否开启 go-module 功能,如果没开启需要设置环境变量:go env -w GO111MODULE=on;\nmodule 名称需要与 github 仓库一致,这样其他人才能通过 go get github.com/poneding/commmon-go 下载到你的类库。\n4、编写 go 类库代码,例如::\nhell/hello.go:\npackage hello import \u0026#34;fmt\u0026#34; func Say(name string) { fmt.Printf(\u0026#34;Hello, %s\\n\u0026#34;, name) } 5、提交 go 代码到 github::\ngit add . git commit -m \u0026#34;add hello\u0026#34; git push -u origin main 6、发行版本:\n最佳实践是创建对应的版本发布分支,然后使用发布分支创建 tag,发布:\ngit checkout -b v1 git push -u origin v1 git tag v1.0.0 git push --tags 此时,在 github 仓库 release 中可以看到发布的版本。\n7、创建 demo-go 项目,测试使用 go 类库::\ngo mod init demo-go 在 go.mod 引入 github.com/poneding/common-go@v1.0.0:\ngo.mod:\nmodule demo-go go 1.16 require github.com/poneding/common-go v1.0.0 调用 github.com/poneding/common-go 库的 hello.Say 方法:\nmain.go:\npackage main import ( \u0026#34;github.com/poneding/common-go/hello\u0026#34; ) func main() { hello.Say(\u0026#34;Jay\u0026#34;) } 8、运行::\n$ go run main.go Hello, Jay « Golang 实现双向认证\n» Golang 发布类库 - 2\n"},{"id":83,"href":"/go/go-publish-package-02/","title":"Go Publish Package 02","section":"Go","content":" 🏠 首页 / Golang 编程 / Golang 发布类库 - 2\nGolang 发布类库 - 2 # 本页介绍如何在 Github 上升级我们已发布的 Golang 类库。\nGo 类库版本规则 # go 类库版本的规则:主版本号.次版本号.修订号,其中:\n主版本号:类库进行了不可向下兼容的修改,例如功能重构,这时候主版本号往上追加; 次版本号:类库进行了可向下兼容的修改,例如新增功能,这时候次版本号往上追加; 修订号:类库进行了可向下兼容的修改(修改的规模更小),例如修复或优化功能,这时候修订好往上追加。 Go 类库发版示例 # 同样以 github.com/poneding/common-go 类库为示例。\n小版本升级 # 主版本不升级,次版本或修订版本升级。\nv0.x.x 版本升级至 v1.x.x 也是可以直接升级的。\n当前版本是 v1.0.0,现对该类库进行了功能修改,发布 v1.0.1 版本:\n1、切换至 v1 分支:\ngit checkout v1 2、修改类库代码:\nhello/hello.go:\npackage hello import \u0026#34;fmt\u0026#34; func Say(name string) { fmt.Printf(\u0026#34;Hello, %s\\n\u0026#34;, name) fmt.Println(\u0026#34;common-go version: v1.0.1\u0026#34;) } 3、提交代码并发布:\ngit add . git commit -m \u0026#34;update hello\u0026#34; git push git tag v1.0.1 git push --tags 4、使用 demo-go 测试,升级版本:\n升级类库方式:\n使用 go get -u github.com/poneding/common-go 或 go get github.com/poneding/common-go@latest 升级至该主版本号下最新版本; 使用 go get github.com/poneding/common-go@v1.0.0 升级至指定版本。 $ go get - u github.com/poneding/common-go go: downloading github.com/poneding/common-go v1.0.1 go: found github.com/poneding/common-go/hello in github.com/poneding/common-go v1.0.1 go: github.com/poneding/common-go upgrade =\u0026gt; v1.0.1 查看 demo-go 下的 go.mod 文件,确实升级到了新版本:\ngo.mod:\nmodule demo-go go 1.16 require github.com/poneding/common-go v1.0.1 5、运行测试:\n$ go run main.go Hello, Jay common-go version: v1.0.1 大版本升级 # 主版本升级。\n值得注意的是,使用 go get -u github.com/poneding/common-go 升级类库版本时,无法跨主版本升级,只能升级至当前主版本下最新版本;\nv0.x.x 升级至 v1.x.x 是个例外,可以直接使用 go get -u github.com/poneding/common-go 命令升级。\n当前版本是 v1.0.1,现对该类库进行了功能重构,发布 v2.0.0 版本:\n1、继续按照最佳实践,创建 v2 版本的分支:\ngit checkout -b v2 2、修改 module 名称至新版:\ngo mod edit -module github.com/poneding/common-go/v2 3、v2 版本重构功能:\nhello/hello.go:\npackage hello import \u0026#34;fmt\u0026#34; func Say(firstname, lastname string) { fmt.Printf(\u0026#34;Hello, %s %s\\n\u0026#34;, firstname, lastname) fmt.Println(\u0026#34;common-go version: v2.0.0\u0026#34;) } 4、提交代码并发布:\ngit add . git commit -m \u0026#34;refacte hello\u0026#34; git push -u origin v2 git tag v2.0.0 git push --tags 5、使用 demo-go 测试,升级版本:\n注意,此时无法通过 go get -u github.com/poneding/common-go 升级至不同于当前主版本的最新版本,需要使用 go get github.com/poneding/common-go/v2 升级:\ngo get github.com/poneding/common-go/v2 查看 go.mod 文件,已经添加了 v2 版本依赖包\ngo.mod:\nmodule demo-go go 1.16 require github.com/poneding/common-go v1.0.1 require github.com/poneding/common-go/v2 v2.0.0 6、修改测试代码:\n同时使用 v1 版本和 v2 版本的包函数:\nmain.go:\npackage main import ( \u0026#34;github.com/poneding/common-go/hello\u0026#34; hellov2 \u0026#34;github.com/poneding/common-go/v2/hello\u0026#34; ) func main() { hello.Say(\u0026#34;Jay\u0026#34;) hellov2.Say(\u0026#34;Jay Chou\u0026#34;) } 8、运行测试:\n$ go run main.go test hello: Hello, Jay common-go version: v1.0.1 Hello, Jay Chou common-go version: v2.0.0 使用本地 go 类库 # 如果本地的 go 类库暂未维护到远端,如何引用本地类库的包呢?\n在 go.mod 文件中使用 replace 引用本地 go 类库,这个方式有时候更方便于开发。\ncommon-go 的 module 名称为 github.com/poneding/common-go\nreplace 使用 go 类库相对路径替换 module 的引用\n以下示例将 go 类库的引用切换为本地引用。\ngo.mod:\nmodule demo-go go 1.16 require ( github.com/poneding/common-go v1.0.1 github.com/poneding/common-go/v2 v2.0.0 ) replace ( github.com/poneding/common-go/v2 =\u0026gt; ../common-go ) 由于是本地引用,版本号只需在主版本号的范围内即可。\n结束语 # 主版本升级会给代码的维护和版本的维护增加难度,并且需要下游用户迁移版本。最好是当存在令人信服的原因时才对类库主版本进行升级,例如为了优化代码大规模重构。\n« Golang 发布类库 - 1\n» Go 程序 SOLID 设计原则\n"},{"id":84,"href":"/go/go-solid/","title":"Go Solid","section":"Go","content":" 🏠 首页 / Golang 编程 / Go 程序 SOLID 设计原则\nGo 程序 SOLID 设计原则 # 可重用软件设计的五个原则,SOLID 原则:\n单一职责原则(Single Responsibility Principle) 开放 / 封闭原则(Open / Closed Principle) 里氏替换原则(Liskov Substitution Principle) 接口隔离原则(Interface Segregation Principle) 依赖倒置原则(Dependency Inversion Principle) 单一职责原则 # SOLID 的第一个原则,S,是单一责任原则。\nA class should have one, and only one, reason to change. – Robert C Martin\n现在 Go 显然没有 classses - 相反,我们有更强大的组合概念 - 但是如果你能回顾一下 class 这个词的用法,我认为此时会有一定价值。\n为什么一段代码只有一个改变的原因很重要?嗯,就像你自己的代码可能会改变一样令人沮丧,发现您的代码所依赖的代码在您脚下发生变化更痛苦。当你的代码必须改变时,它应该响应直接刺激作出改变,而不应该成为附带损害的受害者。\n因此,具有单一责任的代码修改的原因最少。\n耦合和内聚 # 描述改变一个软件是多么容易或困难的两个词是:耦合和内聚。\n耦合只是一个词,描述了两个一起变化的东西 —— 一个运动诱导另一个运动。 一个相关但独立的概念是内聚,一种相互吸引的力量。 在软件上下文中,内聚是描述代码片段之间自然相互吸引的特性。\n为了描述 Go 程序中耦合和内聚的单元,我们可能会将谈谈函数和方法,这在讨论 SRP 时很常见,但是我相信它始于 Go 的 package 模型。\n库名称的设计 # 在 Go 中,所有的代码都在某个 package 中,一个设计良好的 package 从其名称开始。包的名称既是其用途的描述,也是名称空间前缀。Go 标准库中的一些优秀 package 示例:\nnet/http - 提供 http 客户端和服务端 os/exec - 执行外部命令 encoding/json - 实现 JSON 文档的编码和解码 当你在自己的内部使用另一个 pakcage 的 symbols 时,要使用 import 声明,它在两个 package 之间建立一个源代码级的耦合。 他们现在彼此知道对方的存在。 糟糕的库名称 # 这种对名字的关注可不是迂腐。命名不佳的 package 如果真的有用途,会失去罗列其用途的机会。\nserver package 提供什么? …, 嗯,希望是服务端,但是它使用哪种协议? private package 提供什么?我不应该看到的东西?它应该有公共符号吗? common package,和它的伴儿 utils package 一样,经常被发现和其他’伙伴’一起发现 我们看到所有像这样的包裹,就成了各种各样的垃圾场,因为它们有许多责任,所以经常毫无理由地改变。 Unix 设计理念 # 在我看来,如果不提及 Doug McIlroy 的 Unix 哲学,任何关于解耦设计的讨论都将是不完整的;小而锋利的工具结合起来,解决更大的任务,通常是原始作者无法想象的任务。\n我认为 Go package 体现了 Unix 哲学的精神。实际上,每个 Go package 本身就是一个小的 Go 程序,一个单一的变更单元,具有单一的责任。\n开放 / 封闭原则 # 第二个原则,即 O,是 Bertrand Meyer 的开放 / 封闭原则,他在 1988 年写道:\nSoftware entities should be open for extension, but closed for modification. – Bertrand Meyer, Object-Oriented Software Construction\n该建议如何适用于 21 年后写的语言?\npackage main type A struct { year int } func (a A) Greet() { fmt.Println(\u0026#34;Hello GolangUK\u0026#34;, a.year) } type B struct { A } func (b B) Greet() { fmt.Println(\u0026#34;Welcome to GolangUK\u0026#34;, b.year) } func main() { var a A a.year = 2016 var b B b.year = 2016 a.Greet() // Hello GolangUK 2016 b.Greet() // Welcome to GolangUK 2016 } 我们有一个类型 A ,有一个字段 year 和一个方法 Greet。我们有第二种类型,B 它嵌入了一个 A,因为 A 嵌入,因此调用者看到 B 的方法覆盖了 A 的方法。因为 A 作为字段嵌入 B ,B 可以提供自己的 Greet 方法,掩盖了 A 的 Greet 方法。\n但嵌入不仅适用于方法,还可以访问嵌入类型的字段。如您所见,因为 A 和 B 都在同一个包中定义,所以 B 可以访问 A 的私有 year 字段,就像在 B 中声明一样。\n因此嵌入是一个强大的工具,允许 Go 的类型对扩展开放。\npackage main type Cat struct { Name string } func (c Cat) Legs() int { return 4 } func (c Cat) PrintLegs() { fmt.Printf(\u0026#34;I have %d legs.\u0026#34;, c.Legs()) } type OctoCat struct { Cat } func (o OctoCat) Legs() int { return 5 } func main() { var octo OctoCat fmt.Println(octo.Legs()) // 5 octo.PrintLegs() // I have 4 legs } 在这个例子中,我们有一个 Cat 类型,可以用它的 Legs 方法计算它的腿数。我们将 Cat 类型嵌入到一个新类型 OctoCat 中,并声明 Octocats 有五条腿。但是,虽然 OctoCat 定义了自己的 Legs 方法,该方法返回 5,但是当调用 PrintLegs 方法时,它返回 4。\n这是因为 PrintLegs 是在 Cat 类型上定义的。 它需要 Cat 作为它的接收器,因此它会发送到 Cat 的 Legs 方法。Cat 不知道它嵌入的类型,因此嵌入时不能改变其方法集。\n因此,我们可以说 Go 的类型虽然对扩展开放,但对修改是封闭的。\n事实上,Go 中的方法只不过是围绕在具有预先声明形式参数(即接收器)的函数的语法糖。\nfunc (c Cat) PrintLegs() { fmt.Printf(\u0026#34;I have %d legs.\u0026#34;, c.Legs()) } func PrintLegs(c Cat) { fmt.Printf(\u0026#34;I have %d legs.\u0026#34;, c.Legs()) } 接收器正是你传入它的函数,函数的第一个参数,并且因为 Go 不支持函数重载,OctoCat 不能替代普通的 Cat 。 这让我想到了下一个原则。\n里氏替换原则 # 由 Barbara Liskov 提出的里氏替换原则粗略地指出,如果两种类型表现出的行为使得调用者无法区分,则这两种类型是可替代的。\n在基于类的语言中,里氏替换原则通常被解释为,具有各种具体子类型的抽象基类的规范。 但是 Go 没有类或继承,因此无法根据抽象类层次结构实现替换。\nInterfaces # 相反,替换是 Go 接口的范围。在 Go 中,类型不需要指定它们实现特定接口,而是任何类型实现接口,只要它具有签名与接口声明匹配的方法。\n我们说在 Go 中,接口是隐式地而不是显式地满足的,这对它们在语言中的使用方式产生了深远的影响。\n设计良好的接口更可能是小型接口;流行的做法是一个接口只包含一个方法。从逻辑上讲,小接口使实现变得简单,反之则很难。因此形成了由普通行为的简单实现组成的 package。\nio.Reader # type Reader interface { // Read reads up to len(buf) bytes into buf. Read(buf []byte) (n int, err error) } 这令我很容易想到了我最喜欢的 Go 接口 io.Reader。\nio.Reader 接口非常简单; Read 将数据读入提供的缓冲区,并将读取的字节数和读取期间遇到的任何错误返回给调用者。看起来很简单,但非常强大。\n因为 io.Reader 可以处理任何表示为字节流的东西,所以我们几乎可以在任何东西上创建 Reader; 常量字符串,字节数组,标准输入,网络流,gzip 的 tar 文件,通过 ssh 远程执行的命令的标准输出。\n并且所有这些实现都可以互相替代,因为它们实现了相同的简单契约。\n因此,适用于 Go 的里氏替换原则,可以通过已故 Jim Weirich 的格言来概括。\nRequire no more, promise no less. – Jim Weirich\n接口隔离原则 # 第四个原则是接口隔离原则,其内容如下:\nClients should not be forced to depend on methods they do not use. –Robert C. Martin\n在 Go 中,接口隔离原则的应用可以指的是,隔离功能完成其工作所需的行为的过程。举一个具体的例子,假设我已经完成了‘编写一个将 Document 结构保存到磁盘的函数’的任务。\n// Save writes the contents of doc to the file f. func Save(f *os.File, doc *Document) error 我可以定义此函数,让我们称之为 Save,它将给定的 Document 写入到 *os.File。 但是这样做会有一些问题。\nSave 的签名排除了将数据写入网络位置的选项。假设网络存储可能以后成为需求,此功能的签名必须改变,并影响其所有调用者。\n由于 Save 直接操作磁盘上的文件,因此测试起来很不方便。要验证其操作,测试必须在写入后读取文件的内容。 此外,测试必须确保将 f 写入临时位置并随后将其删除。\n*os.File 还定义了许多与 Save 无关的方法,比如读取目录并检查路径是否是文件链接。 如果 Save 函数的签名能只描述 *os.File 相关的部分,将会很实用。\n我们如何处理这些问题呢?\n// Save writes the contents of doc to the supplied ReadWriterCloser. func Save(rwc io.ReadWriteCloser, doc *Document) error 使用 io.ReadWriteCloser 我们可以应用接口隔离原则,使用更通用的文件类型的接口来重新定义 Save。\n通过此更改,任何实现了 io.ReadWriteCloser 接口的类型都可以代替之前的 *os.File。使得 Save 应用程序更广泛,并向 Save 调用者阐明,*os.File 类型的哪些方法与操作相关。\n做为 Save 的编写者,我不再可以选择调用 *os.File 的那些不相关的方法,因为它隐藏在 io.ReadWriteCloser 接口背后。我们可以进一步采用接口隔离原理。\n首先,如果 Save 遵循单一责任原则,它将不可能读取它刚刚编写的文件来验证其内容 - 这应该是另一段代码的责任。因此,我们可以将我们传递给 Save 的接口的规范缩小,仅写入和关闭。\n// Save writes the contents of doc to the supplied WriteCloser. func Save(wc io.WriteCloser, doc *Document) error 其次,通过向 Save 提供一个关闭其流的机制,我们继续这种机制以使其看起来像文件类型的东西,这就产生一个问题,wc 会在什么情况下关闭。Save 可能会无条件地调用 Close,抑或在成功的情况下调用 Close。\n这给 Save 的调用者带来了问题,因为它可能希望在写入文档之后将其他数据写入流。\ntype NopCloser struct { io.Writer } // Close has no effect on the underlying writer. func (c *NopCloser) Close() error { return nil } 一个粗略的解决方案是定义一个新类型,它嵌入一个 io.Writer 并覆盖 Close 方法,以阻止 Save 方法关闭底层数据流。\n但这样可能会违反里氏替换原则,因为 NopCloser 实际上并没有关闭任何东西。\n// Save writes the contents of doc to the supplied Writer. func Save(w io.Writer, doc *Document) error 一个更好的解决方案是重新定义 Save 只接收 io.Writer,完全剥离它除了将数据写入流之外做任何事情的责任。\n通过应用接口隔离原则,我们的 Save 功能,同时得到了一个在需求方面最具体的函数 - 它只需要一个可写的参数 - 并且具有最通用的功能,现在我们可以使用 Save 保存我们的数据到任何一个实现 io.Writer 的地方。\nA great rule of thumb for Go is accept interfaces, return structs. – Jack Lindamood\n退一步说,这句话是一个有趣的模因,在过去的几年里,它渗透入 Go 思潮。\n这个推特大小的版本缺乏细节,这不是 Jack 的错,但我认为它代表了第一个正当有理的 Go 设计传统\n依赖倒置原则 # 最后一个 SOLID 原则是依赖倒置原则,该原则指出:\nHigh-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions. – Robert C. Martin\n但是,对于 Go 程序员来说,依赖倒置在实践中意味着什么呢?\n如果您已经应用了我们之前谈到的所有原则,那么您的代码应该已经被分解为离散包,每个包都有一个明确定义的责任或目的。您的代码应该根据接口描述其依赖关系,并且应该考虑这些接口以仅描述这些函数所需的行为。 换句话说,除此之外没什么应该要做的。\n所以我认为,在 Go 的上下文中,Martin 所指的是 import graph 的结构。\n在 Go 中,import graph 必须是非循环的。 不遵守这种非循环要求将导致编译失败,但更为严重地是它代表设计中存在严重错误。\n在所有条件相同的情况下,精心设计的 Go 程序的 import graph 应该是宽的,相对平坦的,而不是高而窄的。 如果你有一个 package,其函数无法在不借助另一个 package 的情况下运行,那么这或许表明代码没有很好地沿 pakcage 边界分解。\n依赖倒置原则鼓励您将特定的责任,沿着 import graph 尽可能的推向更高层级,推给 main package 或顶级处理程序,留下较低级别的代码来处理抽象接口。\n总结 # 回顾一下,当应用于 Go 时,每个 SOLID 原则都是关于设计的强有力陈述,但综合起来它们具有中心主题。\n单一职责原则,鼓励您将功能,类型、方法结构化为具有自然内聚的包;类型属于彼此,函数服务于单一目的。 开放 / 封闭原则,鼓励您使用嵌入将简单类型组合成更复杂的类型。 里氏替换原则,鼓励您根据接口而不是具体类型来表达包之间的依赖关系。通过定义小型接口,我们可以更加确信,实现将忠实地满足他们的契约。 接口隔离原则,进一步采用了这个想法,并鼓励您定义仅依赖于他们所需行为的函数和方法。如果您的函数仅需要具有单个接口类型的参数的方法,则该函数更可能只有一个责任。 依赖倒置原则,鼓励您按照从编译时间到运行时间的时序,转移 package 所依赖的知识。在 Go 中,我们可以通过特定 package 使用的 import 语句的数量减少看到了这一点。 如果要总结一下本次演讲,那可能就是这样:interfaces let you apply the SOLID principles to Go programs。\n因为接口让 Go 程序员描述他们的 package 提供了什么 - 而不是它怎么做的。换个说法就是 “解耦”,这确实是目标,因为越松散耦合的软件越容易修改。\n正如 Sandi Metz 所说:\nDesign is the art of arranging code that needs to work today, and to be easy to change forever. – Sandi Metz\n因为如果 Go 想要成为公司长期投资的语言,Go 程序的可维护性,更容易变更,将是他们决策的关键因素。\n« Golang 发布类库 - 2\n» Golang 标准库\n"},{"id":85,"href":"/go/go-stdlib/","title":"Go Stdlib","section":"Go","content":" 🏠 首页 / Golang 编程 / Golang 标准库\nGolang 标准库 # fmt # 格式化打印\n%v 原样输出 %T 打印类型 %t bool %s string %f float %d 10进制整数 %b 2进制整数 %o 8进制整数 %x 16进制整数 0-9,a-f %X 16进制整数 0-9,A-F %c char %p pointer %.2f float 保留两位 path # file := \u0026#34;./logs/2021-01-25/error.log\u0026#34; fileName := path.Base(file) # 返回文件名:error.log fileExt := path.Ext(file) # 返回文件后缀:.log fileDir := path.Dir(file) # 返回文件路径: ./logs/2021-01-25 os/exec # Golang语言有一个包叫做 os/exec,使用该包可以直接在程序中调用主机的命令,使用示例如下:\nfunc OsExecUsage() error { fmt.Println(\u0026#34;docker build...\u0026#34;) cmd := exec.Command(\u0026#34;docker\u0026#34;, \u0026#34;build\u0026#34;, \u0026#34;.\u0026#34; ,\u0026#34;-t\u0026#34; ,\u0026#34;demo\u0026#34;) // 实时打印输出 cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Run() if err != nil { fmt.Println(\u0026#34;cmd.Output: \u0026#34;, err) return err } return nil } « Go 程序 SOLID 设计原则\n» testing\n"},{"id":86,"href":"/go/go-testing/","title":"Go Testing","section":"Go","content":" 🏠 首页 / Golang 编程 / testing\ntesting # 命令 # 测试\ngo test -run=TestCompare -v . 运行测试,测试函数名称中仅包含 TestCompare 前缀。\n列出包内测试文件\ngo list -f={{.GoTestFiles}} . 列出包外测试文件\ngo list -f={{.XTestGoFiles}} . 包内测试:测试文件的包名称与被测包一致,可以访问被测包内所有成员,相当于白盒测试;\n包外测试:测试文件的包名称与被测包不一致,一般在被测包名称后面添加 _test 后缀,只能访问被测包内公开成员,相当于黑盒测试。\n« Golang 标准库\n» Golang\n"},{"id":87,"href":"/go/go/","title":"Go","section":"Go","content":" 🏠 首页 / Golang 编程 / Golang\nGolang # 资料 # Go 安全指南 Go 语言编程规范 Golang channel # golang 实现并发是通过通信共享内存,channel 是 go 语言中goroutine 的通信管道,通过 channel 将值从一个 goroutine 发送到另一个 goroutine。\n语法 # 创建 channel:\n使用 make() 函数创建:\nch := make(chan int) 默认创建一个无缓存 channel。\n发送数据到 channel:\nch \u0026lt;- x 从 channel 读取数据:\nx := \u0026lt;-ch 关闭 channel:\n使用 close() 函数关闭:\nclose(ch) 当你的程序不再需要往 channel 中发送数据时,可以关闭 channel。\n如果往已经关闭的 channal 发送数据,程序发生异常。\n无缓冲 channel # 如果当前没有一个 goroutine 对无缓冲 channel 接收数据,那么无缓冲 channel 会阻止发送数据。\n有缓冲 channel # 类似队列机制,创建时需要设定缓冲大小。\n创建一个有缓冲 channel:\nch := make(chan int ch, 10) 在有 goroutine 接收 channel 数据之前,可以先向 channel 中发送10个数据。\n无缓冲 channel 与有缓冲 channel的区别 # 现在,你可能想知道何时使用这两种类型。 这完全取决于你希望 goroutine 之间的通信如何进行。 无缓冲 channel 同步通信。 它们保证每次发送数据时,程序都会被阻止,直到有人从 channel 中读取数据。\n相反,有缓冲 channel 将发送和接收操作解耦。 它们不会阻止程序,但你必须小心使用,因为可能最终会导致死锁(如前文所述)。 使用无缓冲 channel 时,可以控制可并发运行的 goroutine 的数量。 例如,你可能要对 API 进行调用,并且想要控制每秒执行的调用次数。 否则,你可能会被阻止。\n读取文件 # 一次性全读 # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;io/ioutil\u0026#34; ) func main() { bytes, e := ioutil.ReadFile(\u0026#34;/Users/dp/tmp/0811/03/file.txt\u0026#34;) if e != nil { panic(\u0026#34;read file error.\u0026#34;) } fmt.Println(string(bytes)) } 按行读取 # package main import ( \u0026#34;bufio\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;io\u0026#34; \u0026#34;os\u0026#34; ) func main() { f, e := os.Open(\u0026#34;C:/Users/dp/go/src/0811/03/file.txt\u0026#34;) if e != nil { panic(\u0026#34;read file error.\u0026#34;) } defer f.Close() r := bufio.NewReader(f) for { line, _, eof := r.ReadLine() if eof == io.EOF { break // read last line. } fmt.Println(string(line)) } } 优雅退出 # 不处理优雅退出 # package main import ( \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;os\u0026#34; \u0026#34;os/signal\u0026#34; \u0026#34;time\u0026#34; \u0026#34;github.com/gin-gonic/gin\u0026#34; ) func main() { router := gin.Default() router.GET(\u0026#34;/\u0026#34;, func(c *gin.Context) { time.Sleep(5 * time.Second) c.String(http.StatusOK, \u0026#34;Welcome Gin Server\u0026#34;) }) server := \u0026amp;http.Server{ Addr: \u0026#34;:8080\u0026#34;, Handler: router, } quit := make(chan os.Signal) signal.Notify(quit, os.Interrupt) go func() { \u0026lt;-quit log.Println(\u0026#34;receive interrupt signal\u0026#34;) if err := server.Close(); err != nil { log.Fatal(\u0026#34;Server Close:\u0026#34;, err) } }() if err := server.ListenAndServe(); err != nil { if err == http.ErrServerClosed { log.Println(\u0026#34;Server closed under request\u0026#34;) } else { log.Fatal(\u0026#34;Server closed unexpect\u0026#34;) } } log.Println(\u0026#34;Server exiting\u0026#34;) } 优雅退出 1(with context) # package main import ( \u0026#34;context\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;os/signal\u0026#34; \u0026#34;syscall\u0026#34; \u0026#34;time\u0026#34; \u0026#34;github.com/gin-gonic/gin\u0026#34; ) func main() { // Create context that listens for the interrupt signal from the OS. ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() router := gin.Default() router.GET(\u0026#34;/\u0026#34;, func(c *gin.Context) { time.Sleep(10 * time.Second) c.String(http.StatusOK, \u0026#34;Welcome Gin Server\u0026#34;) }) srv := \u0026amp;http.Server{ Addr: \u0026#34;:8080\u0026#34;, Handler: router, } // Initializing the server in a goroutine so that // it won\u0026#39;t block the graceful shutdown handling below go func() { if err := srv.ListenAndServe(); err != nil \u0026amp;\u0026amp; err != http.ErrServerClosed { log.Fatalf(\u0026#34;listen: %s\\n\u0026#34;, err) } }() // Listen for the interrupt signal. \u0026lt;-ctx.Done() // Restore default behavior on the interrupt signal and notify user of shutdown. stop() log.Println(\u0026#34;shutting down gracefully, press Ctrl+C again to force\u0026#34;) // The context is used to inform the server it has 5 seconds to finish // the request it is currently handling ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatal(\u0026#34;Server forced to shutdown: \u0026#34;, err) } log.Println(\u0026#34;Server exiting\u0026#34;) } 优雅退出 2(without context) # package main import ( \u0026#34;context\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;os\u0026#34; \u0026#34;os/signal\u0026#34; \u0026#34;syscall\u0026#34; \u0026#34;time\u0026#34; \u0026#34;github.com/gin-gonic/gin\u0026#34; ) func main() { router := gin.Default() router.GET(\u0026#34;/\u0026#34;, func(c *gin.Context) { time.Sleep(5 * time.Second) c.String(http.StatusOK, \u0026#34;Welcome Gin Server\u0026#34;) }) srv := \u0026amp;http.Server{ Addr: \u0026#34;:8080\u0026#34;, Handler: router, } // Initializing the server in a goroutine so that // it won\u0026#39;t block the graceful shutdown handling below go func() { if err := srv.ListenAndServe(); err != nil \u0026amp;\u0026amp; err != http.ErrServerClosed { log.Fatalf(\u0026#34;listen: %s\\n\u0026#34;, err) } }() // Wait for interrupt signal to gracefully shutdown the server with // a timeout of 5 seconds. quit := make(chan os.Signal, 1) // kill (no param) default send syscall.SIGTERM // kill -2 is syscall.SIGINT // kill -9 is syscall.SIGKILL but can\u0026#39;t be catch, so don\u0026#39;t need add it signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) \u0026lt;-quit log.Println(\u0026#34;Shutting down server...\u0026#34;) // The context is used to inform the server it has 5 seconds to finish // the request it is currently handling ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatal(\u0026#34;Server forced to shutdown: \u0026#34;, err) } log.Println(\u0026#34;Server exiting\u0026#34;) } 函数式选项模式 # 函数式选项模式:Functional Options Pattern,是 Golang 中一种常用的设计模式,用于解决构造函数参数过多的问题。\n示例:\npackage main func main() { NewServer(\u0026#34;test\u0026#34;, SetHost(\u0026#34;localhost\u0026#34;), SetPort(8081)) } type Server struct { Label string Host string Port int32 } func NewServer(label string, opts ...func(s *Server)) *Server { s := \u0026amp;Server{ Label: label, } for _, opt := range opts { opt(s) } return s } type Option func(s *Server) func SetHost(host string) Option { return func(s *Server) { s.Host = host } } func SetPort(port int32) Option { return func(s *Server) { s.Port = port } } 调用 API # 使用 Golang 调用 API 代码示例:\nGet # func GetApi() { resp, err := http.Get(\u0026#34;https://baidu.com\u0026#34;) if err != nil { fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } defer resp.Body.Close() fmt.Printf(\u0026#34;Status: %s\\n\u0026#34;, resp.Status) fmt.Printf(\u0026#34;StatusCode: %d\\n\u0026#34;, resp.StatusCode) body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } fmt.Printf(\u0026#34;StatusCode: %s\\n\u0026#34;, string(body)) } Post # 1 简单 Post:\nfunc PostApi1() { json := `{\u0026#34;id\u0026#34;:\u0026#34;u-001\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;Jay\u0026#34;,\u0026#34;age\u0026#34;:18}` resp, err := http.Post(\u0026#34;https://example.com/user\u0026#34;, \u0026#34;application/json\u0026#34;, bytes.NewBuffer([]byte(json))) defer resp.Body.Close() fmt.Printf(\u0026#34;Status: %s\\n\u0026#34;, resp.Status) fmt.Printf(\u0026#34;StatusCode: %d\\n\u0026#34;, resp.StatusCode) body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } fmt.Printf(\u0026#34;StatusCode: %s\\n\u0026#34;, string(body)) } 2 附带 Header:\nfunc PostApi2() { // 1. json //json := `{\u0026#34;id\u0026#34;:\u0026#34;u-001\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;Jay\u0026#34;,\u0026#34;age\u0026#34;:18}` //req, _ := http.NewRequest(http.MethodPost, \u0026#34;https://example.com/user\u0026#34;, bytes.NewBuffer([]byte(json))) // 2. map reqBody, _ := json.Marshal(map[string]string{ \u0026#34;id\u0026#34;: \u0026#34;u-001\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Jay\u0026#34;, \u0026#34;age\u0026#34;: \u0026#34;18\u0026#34;, }) req, _ := http.NewRequest(http.MethodPost, \u0026#34;https://example.com/user\u0026#34;, bytes.NewBuffer(reqBody)) req.Header.Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) req.Header.Set(\u0026#34;My_Custom_Header\u0026#34;, \u0026#34;Value\u0026#34;) client := http.Client{} resp, err := client.Do(req) if err != nil { fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } defer resp.Body.Close() fmt.Printf(\u0026#34;Status: %s\\n\u0026#34;, resp.Status) fmt.Printf(\u0026#34;StatusCode: %d\\n\u0026#34;, resp.StatusCode) body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } fmt.Printf(\u0026#34;StatusCode: %s\\n\u0026#34;, string(body)) } 3 构造请求对象:\ntype User struct { Id string `json:\u0026#34;id\u0026#34;` Name string `json:\u0026#34;name\u0026#34;` Age int `json:\u0026#34;age\u0026#34;` } func PostApi3() { user := User{ Id: \u0026#34;u-001\u0026#34;, Name: \u0026#34;Jay\u0026#34;, Age: 18, } buf := new(bytes.Buffer) json.NewEncoder(buf).Encode(user) resp, err := http.Post(\u0026#34;https://example.com/user\u0026#34;, \u0026#34;application/json\u0026#34;, buf) defer resp.Body.Close() fmt.Printf(\u0026#34;Status: %s\\n\u0026#34;, resp.Status) fmt.Printf(\u0026#34;StatusCode: %d\\n\u0026#34;, resp.StatusCode) body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } fmt.Printf(\u0026#34;StatusCode: %s\\n\u0026#34;, string(body)) } 4 OAuth2:\n// go get golang.org/x/oauth2 func getAccessToken()string{ var authCfg = \u0026amp;clientcredentials.Config{ ClientID: \u0026#34;xxx\u0026#34;, ClientSecret: \u0026#34;xxx\u0026#34;, TokenURL: \u0026#34;https://example.com/connect/token\u0026#34;, EndpointParams: url.Values{ \u0026#34;grant_type\u0026#34;: {\u0026#34;client_credentials\u0026#34;}, }, } token, err := authCfg.TokenSource(context.Background()).Token() if err != nil { fmt.Errorf(\u0026#34;get access token failed. ERROR: %s\\n\u0026#34;, err.Error()) } return token.AccessToken } func OAuth2Api(){ json := `{\u0026#34;id\u0026#34;:\u0026#34;u-001\u0026#34;,\u0026#34;name\u0026#34;:\u0026#34;Jay\u0026#34;,\u0026#34;age\u0026#34;:18}` req, _ := http.NewRequest(http.MethodPost, \u0026#34;https://example.com/user\u0026#34;, bytes.NewBuffer([]byte(json))) req.Header.Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) req.Header.Set(\u0026#34;Bearer\u0026#34;, getAccessToken()) client := http.Client{} resp, err := client.Do(req) if err != nil { fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } defer resp.Body.Close() fmt.Printf(\u0026#34;Status: %s\\n\u0026#34;, resp.Status) fmt.Printf(\u0026#34;StatusCode: %d\\n\u0026#34;, resp.StatusCode) body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } fmt.Printf(\u0026#34;StatusCode: %s\\n\u0026#34;, string(body)) } 5 上传文件:\nfunc FileApi(){ file,err:=os.Open(\u0026#34;hello.txt\u0026#34;) if err!=nil{ fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } defer file.Close() var reqBody bytes.Buffer multiPartWriter:=multipart.NewWriter(\u0026amp;reqBody) fileWriter,err:=multiPartWriter.CreateFormFile(\u0026#34;file_field\u0026#34;,\u0026#34;hello.txt\u0026#34;) if err!=nil{ fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } _,err=io.Copy(fileWriter,file) if err!=nil{ fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } fieldWriter,err:=multiPartWriter.CreateFormField(\u0026#34;normal_field\u0026#34;) if err!=nil{ fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } _,err=fieldWriter.Write([]byte(\u0026#34;value\u0026#34;)) if err!=nil{ fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } multiPartWriter.Close() req,err:=http.NewRequest(http.MethodPost,\u0026#34;http://example.com/file\u0026#34;,\u0026amp;reqBody) if err!=nil{ fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } req.Header.Set(\u0026#34;Content-Type\u0026#34;,multiPartWriter.FormDataContentType()) client:=http.Client{} resp,err:=client.Do(req) if err!=nil{ fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } defer resp.Body.Close() fmt.Printf(\u0026#34;Status: %s\\n\u0026#34;, resp.Status) fmt.Printf(\u0026#34;StatusCode: %d\\n\u0026#34;, resp.StatusCode) body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } fmt.Printf(\u0026#34;StatusCode: %s\\n\u0026#34;, string(body)) } 调用 Jenkins API # package main import ( \u0026#34;fmt\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;strings\u0026#34; ) type JenkinsConfig struct { User string `json:\u0026#34;user\u0026#34;` Token string `json:\u0026#34;token\u0026#34;` } var jenkinsConf *JenkinsConfig func main() { jenkinsConf := \u0026amp;JenkinsConfig{ User: \u0026#34;xxx\u0026#34;, Token: \u0026#34;xxxxxx\u0026#34; } job := \u0026#34;jenkins-job-1\u0026#34; disableJenkins(job) } func disableJenkins(job string) { // disabled jenkins job r, err := http.NewRequest(http.MethodPost, fmt.Sprintf(\u0026#34;https://jenkins.example.com/job/%s/disable\u0026#34;, job), nil) if err != nil { log.Errorf(\u0026#34;new jenkins request failed: %s\u0026#34;, err) } r.SetBasicAuth(jenkinsConf.User, jenkinsConf.Token) client := \u0026amp;http.Client{} resp, err := client.Do(r) if err != nil { log.Errorf(\u0026#34;request failed: %s\u0026#34;, err) } if resp.StatusCode == http.StatusOK { log.Healthf(\u0026#34;disable jenkins job [%s] successfully\u0026#34;, job) } else { log.Warnf(\u0026#34;disable jenkins job [%s] status [%d]\u0026#34;, job, resp.StatusCode) } } 位运算 # package main import \u0026#34;fmt\u0026#34; func main() { /* 位运算符: 将数值,转为二进制后,按位操作 按位\u0026amp;: 对应位的值如果都为 1 才为 1,有一个为 0 就为 0 按位|: 对应位的值如果都是 0 才为 0,有一个为 1 就为 1 异或^: 二元:a^b 对应位的值不同为 1,相同为 0 一元:^a 按位取反: 1---\u0026gt;0 0---\u0026gt;1 位清空:\u0026amp;^ 对于 a \u0026amp;^ b 对于 b 上的每个数值 如果为 0,则取 a 对应位上的数值 如果为 1,则结果位就取 0 位移运算符: \u0026lt;\u0026lt;:按位左移,将 a 转为二进制,向左移动 b 位 a \u0026lt;\u0026lt; b \u0026gt;\u0026gt;: 按位右移,将 a 转为二进制,向右移动 b 位 a \u0026gt;\u0026gt; b */ a := 60 b := 13 /* a: 60 0011 1100 b: 13 0000 1101 \u0026amp; 0000 1100 | 0011 1101 ^ 0011 0001 \u0026amp;^ 0011 0000 a : 0000 0000 ... 0011 1100 ^ 1111 1111 ... 1100 0011 */ fmt.Printf(\u0026#34;a:%d, %b\\n\u0026#34;,a,a) fmt.Printf(\u0026#34;b:%d, %b\\n\u0026#34;,b,b) res1 := a \u0026amp; b fmt.Println(res1) // 12 res2 := a | b fmt.Println(res2) // 61 res3 := a ^ b fmt.Println(res3) // 49 res4 := a \u0026amp;^ b fmt.Println(res4) // 48 res5 := ^a fmt.Println(res5) c:=8 /* c : ... 0000 1000 0000 100000 \u0026gt;\u0026gt; 0000 10 */ res6 := c \u0026lt;\u0026lt; 2 fmt.Println(res6) res7 := c \u0026gt;\u0026gt; 2 fmt.Println(res7) } 实现简易网络连接客户端 # 实现类似 nc 的客户端命令工具:\n$ nc www.baidu.com 80 GET / HTTP/1.1 注意:需要连续两次回车,才会连接\n废话少说,上代码:\nmain.go\npackage main import ( \u0026#34;io\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net\u0026#34; \u0026#34;os\u0026#34; ) func main() { if len(os.Args) != 3 { log.Fatalln(\u0026#34;Usage: nc [host] [port]\u0026#34;) } host, port := os.Args[1], os.Args[2] c, err := net.Dial(\u0026#34;tcp\u0026#34;, host+\u0026#34;:\u0026#34;+port) if err != nil { log.Fatalln(err) } go func() { io.Copy(c, os.Stdin) }() io.Copy(os.Stdout, c) } 测试:\n$ go build -o nc main.go $ ./nc www.baidu.com 80 GET / HTTP/1.1 git # package gitutil import ( \u0026#34;fmt\u0026#34; \u0026#34;github.com/go-git/go-git/v5\u0026#34; \u0026#34;github.com/go-git/go-git/v5/plumbing\u0026#34; \u0026#34;github.com/go-git/go-git/v5/plumbing/transport/http\u0026#34; \u0026#34;os\u0026#34; ) func Clone() { _, err := git.PlainClone(\u0026#34;/var/app/ash\u0026#34;, false, \u0026amp;git.CloneOptions{ URL: \u0026#34;https://github.com/poneding/ash.git\u0026#34;, ReferenceName: plumbing.NewBranchReferenceName(\u0026#34;master\u0026#34;), Auth: \u0026amp;http.BasicAuth{ Username: \u0026#34;poneding\u0026#34;, Password: \u0026#34;xxxxxx\u0026#34;, }, Progress: os.Stdout, }) if err != nil { fmt.Errorf(\u0026#34;ERROR: %s\\n\u0026#34;, err.Error()) } } elasticsearch # package esutil import ( \u0026#34;bytes\u0026#34; \u0026#34;encoding/json\u0026#34; \u0026#34;errors\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;github.com/elastic/go-elasticsearch/v7\u0026#34; \u0026#34;strings\u0026#34; ) func NewESClient2(esAddress []string) (*elasticsearch.Client, error) { if len(esAddress) == 0 { panic(\u0026#34;Invalid parameters: esAddress.\u0026#34;) } esConfig := elasticsearch.Config{Addresses: esAddress} esClient, err := elasticsearch.NewClient(esConfig) if err != nil { fmt.Errorf(\u0026#34;GetESClient ERROR: %s\\n\u0026#34;, err) panic(err) } return esClient, nil } func QueryLogs2(esClient *elasticsearch.Client, query QueryLogModel) ([]LogModel, error) { var ( buf bytes.Buffer r map[string]interface{} ) index := query.App + \u0026#34;-*\u0026#34; q := map[string]interface{}{ \u0026#34;query\u0026#34;: map[string]interface{}{ \u0026#34;match_phrase\u0026#34;: map[string]interface{}{ \u0026#34;kubernetes.labels.app\u0026#34;: query.App, }, }, } if err := json.NewEncoder(\u0026amp;buf).Encode(q); err != nil { fmt.Errorf(\u0026#34;QueryLogs ERROR: %s\\n\u0026#34;, err) return nil, err } searchRes, err := esClient.Search( esClient.Search.WithIndex(index), esClient.Search.WithBody(\u0026amp;buf), esClient.Search.WithQuery(query.Filter), esClient.Search.WithFilterPath(\u0026#34;hits.hits\u0026#34;), esClient.Search.WithSize(query.Size), esClient.Search.WithSort(\u0026#34;@timestamp:desc\u0026#34;), esClient.Search.WithSource(\u0026#34;@timestamp\u0026#34;,\u0026#34;level\u0026#34;,\u0026#34;log\u0026#34;,\u0026#34;msg\u0026#34;), ) defer searchRes.Body.Close() if err != nil || searchRes.IsError() { fmt.Errorf(\u0026#34;QueryLogs ERROR: %s\\n\u0026#34;, err.Error()) return nil, errors.New(strings.Join([]string{\u0026#34;es.Search ERROR:\u0026#34;, searchRes.Status(), err.Error()}, \u0026#34; \u0026#34;)) } if err := json.NewDecoder(searchRes.Body).Decode(\u0026amp;r); err != nil { fmt.Errorf(\u0026#34;QueryLogs ERROR: %s\\n\u0026#34;, err.Error()) return nil, err } res := make([]LogModel, 0) for _, hit := range r[\u0026#34;hits\u0026#34;].(map[string]interface{})[\u0026#34;hits\u0026#34;].([]interface{}) { source := hit.(map[string]interface{})[\u0026#34;_source\u0026#34;].(map[string]interface{}) logModel := LogModel{ Timestamp: source[\u0026#34;@timestamp\u0026#34;].(string), Log: source[\u0026#34;log\u0026#34;].(string), } level, ok := source[\u0026#34;level\u0026#34;] if ok { logModel.Level = level.(string) } log, ok := source[\u0026#34;msg\u0026#34;] if ok { logModel.Log = log.(string) } res = append(res, logModel) } return res, nil } package es import ( \u0026#34;context\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;github.com/olivere/elastic/v7\u0026#34; \u0026#34;reflect\u0026#34; \u0026#34;time\u0026#34; ) func NewESClient(esAddresses []string) (*elastic.Client, error) { client, err := elastic.NewClient(elastic.SetSniff(false), elastic.SetURL(esAddresses...)) if err != nil { fmt.Errorf(\u0026#34;NewESClient ERROR: %s\\n\u0026#34;, err) panic(err) } return client, nil } func QueryLogs(esClient *elastic.Client, queryModel QueryLogModel) ([]LogModel, error) { res := make([]LogModel, 0) boolQuery := elastic.NewBoolQuery() index := queryModel.App + \u0026#34;-*\u0026#34; query := esClient.Search(index).FilterPath(\u0026#34;hits.hits\u0026#34;).Sort(\u0026#34;@timestamp\u0026#34;, false) if len(queryModel.Filter) \u0026gt; 0 { boolQuery.Filter(elastic.NewQueryStringQuery(queryModel.Filter)) } if len(queryModel.Level) \u0026gt; 0 { boolQuery.Filter(elastic.NewMatchPhraseQuery(\u0026#34;level\u0026#34;, queryModel.Level)) } if queryModel.Page \u0026lt;= 0 { queryModel.Page = 1 } if queryModel.Size \u0026lt;= 0 { queryModel.Size = 50 } query = query.From((queryModel.Page-1)*queryModel.Size + 1).Size(queryModel.Size) if queryModel.StartAt == (time.Time{}) { queryModel.StartAt = time.Now().Add(-15 * time.Minute).UTC() } if queryModel.EndAt == (time.Time{}) { queryModel.EndAt = time.Now().UTC() } boolQuery.Filter(elastic.NewRangeQuery(\u0026#34;@timestamp\u0026#34;).Gte(queryModel.StartAt).Lte(queryModel.EndAt)) query = query.Query(boolQuery) queryRes, err := query.Do(context.Background()) if err != nil { fmt.Errorf(\u0026#34;QueryLogs ERROR: %s\\n\u0026#34;, err.Error()) return res, err } for _, item := range queryRes.Each(reflect.TypeOf(LogModel{})) { if t, ok := item.(LogModel); ok { res = append(res, LogModel{ Timestamp: t.Timestamp, Level: t.Level, // Msg storing source log here. Log: t.Msg, Msg: t.Log, App: t.Kubernetes.Labels.App, }) } } return res, nil } func QueryErrorLogs(esClient *elastic.Client) error { query := esClient.Search(\u0026#34;dev-core-*\u0026#34;).FilterPath(\u0026#34;hits.hits\u0026#34;).Sort(\u0026#34;@timestamp\u0026#34;, false).Size(100).Query(elastic.NewMatchPhraseQuery(\u0026#34;level\u0026#34;, \u0026#34;error\u0026#34;)) queryRes, err := query.Do(context.Background()) if err != nil { fmt.Errorf(\u0026#34;QueryLogs ERROR: %s\\n\u0026#34;, err.Error()) } for _, item := range queryRes.Each(reflect.TypeOf(LogModel{})) { if t, ok := item.(LogModel); ok { fmt.Println(t.Log) } } return nil } « testing\n» gopkg-errors.md\n"},{"id":88,"href":"/go/gopkg-errors/","title":"Gopkg Errors","section":"Go","content":" 🏠 首页 / Golang 编程 / gopkg-errors.md\ntitle: Go 包 - errors date: 2022-10-19T14:44:06+08:00 lastmod: 2022-11-03T03:32:31.646Z tags:\nGolang gopkg keywords: Golang gopkg errors weight: 1 errors 包为你的 Go 程序提供一种对程序员调试、查看日志更友好的错误处理方式。\nGo 程序中传统的错误处理方法:\nif err != nil { return err } 递归的向上传递错误,这种方式有一个缺陷:最终处理错误的位置无法获取错误的调用上下文信息。\nerrors 包以不破坏错误的原始值的方式向错误中的添加调用上下文信息。\n获取包 # go get github.com/pkg/errors 错误添加上下文 # The errors.Wrap function returns a new error that adds context to the original error. For example\n_, err := ioutil.ReadAll(r) if err != nil { return errors.Wrap(err, \u0026#34;read failed\u0026#34;) } Retrieving the cause of an error # Using errors.Wrap constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by errors.Cause.\ntype causer interface { Cause() error } errors.Cause will recursively retrieve the topmost error which does not implement causer, which is assumed to be the original cause. For example:\nswitch err := errors.Cause(err).(type) { case *MyError: // handle specifically default: // unknown error } Read the package documentation for more information.\nRoadmap # With the upcoming Go2 error proposals this package is moving into maintenance mode. The roadmap for a 1.0 release is as follows:\n0.9. Remove pre Go 1.9 and Go 1.10 support, address outstanding pull requests (if possible) 1.0. Final release. Contributing # Because of the Go2 errors changes, this package is not accepting proposals for new functionality. With that said, we welcome pull requests, bug fixes and issue reports.\nBefore sending a PR, please discuss your change by raising an issue.\nLicense # BSD-2-Clause\n« Golang\n» Goreleaser\n"},{"id":89,"href":"/go/goreleaser/","title":"Goreleaser","section":"Go","content":" 🏠 首页 / Golang 编程 / Goreleaser\nGoreleaser # Go 程序项目的自动化发布工具,简单的发布命令帮助我们省去大量的重复工作。\n安装 # MacOs:\nbrew install goreleaser/tap/goreleaser 源码编译:\ngit clone https://github.com/goreleaser/goreleaser cd goreleaser go get ./... go build -o goreleaser . ./goreleaser --version 初始化 # 在go项目下运行以下命令,生成.goreleaser.yml文件:\ngoreleaser init 生成文件后,自行配置,相信你能看的懂。\n验证 .goreleaser.yml # goreleaser check 使用本地环境构建\ngoreleaser build --single-target 配置 github token # token 必须至少包含 write:package 权限,才能上传到发布资源中。\n从github生成token,写入文件:\nmkdir ~/.config/goreleaser vim ~/.config/goreleaser/github_token 或者直接在终端导入环境配置:\nexport GITHUB_TOKEN=\u0026#34;YOUR_GITHUB_TOKEN\u0026#34; 为项目打上 tag # git tag v0.1.0 -m \u0026#34;release v0.1.0\u0026#34; git push origin v0.1.0 执行 goreleaser # goreleaser --clean # 跳过 git 修改验证 goreleaser build --skip-validate --clean 如果目前还不想打 tag,可以基于最近的一次提交 直接使用一下 gorelease 命令:\ngoreleaser build --snapshot Dry run # 如果你想在真实的发布之前做发布测试,你可以尝试 dry run 。\nBuild only 模式 # 使用如下命令,只会编译当前项目,可以验证项目是否存储编译错误。\ngoreleaser build Release 标识 # 使用 --skip-publish 命令标识来跳过发布到远端。\ngoreleaser release --skip-publish goreleaser release --snapshot --skip-publish --clean 工作原理 # 条件:\n项目下有一个规范的 ·goreleaser.yml 文件 干净的 git 目录 符合 SemVer-compatible 的版本命名 步骤:\ndefaulting:为每个步骤配置合理的默认值 building:构建二进制文件(binaries),归档文件(archives),包文件(packages),Docker镜像等 publishing:将构建的文件发布到配置的 GitHub Release 资源,Docker Hub 等 annoucing:发布完成后,配置通知 步骤可以通过命令标识 --skip-{step_name} 来跳过,如果当中某步骤失败,之后的步骤将不会运行。\nCGO # 很遗憾,如果你的 Go 程序项目需要使用到 CGO 的跨平台编译,Docker 镜像是不支持的,并且你的配置将不会看到 clean 。\n也许这里可以帮助到你: Cross-compiling Go with CGO - GoReleaser\n« gopkg-errors.md\n» Mac M1 交叉编译 CGO\n"},{"id":90,"href":"/go/mac-appl-silicon-cross-compile-cgo/","title":"MAC Appl Silicon Cross Compile Cgo","section":"Go","content":" 🏠 首页 / Golang 编程 / Mac M1 交叉编译 CGO\nMac M1 交叉编译 CGO # 方法一 # 1、安装依赖\nbrew tap messense/macos-cross-toolchains brew install x86_64-unknown-linux-gnu brew install aarch64-unknown-linux-gnu 2、添加到 PATH\nexport PATH=$PATH:/opt/homebrew/Cellar/x86_64-unknown-linux-gnu/11.2.0_1/bin::/opt/homebrew/Cellar/aarch64-unknown-linux-gnu/11.2.0_1/bin 3、编译 CGO 程序\nCGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=x86_64-unknown-linux-gnu-gcc CXX=x86_64-unknown-linux-gnu-g++ go build CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=aarch64-unknown-linux-gnu-gcc CXX=aarch64-unknown-linux-gnu-g++ go build 方法二 # 1、安装依赖\nbrew install FiloSottile/musl-cross/musl-cross 2、添加到 PATH\nexport PATH=$PATH:/opt/homebrew/Cellar/musl-cross/0.9.9_1/bin 3、编译 CGO 程序\n# -tags=musl 不能省略不然会出现其他错误 CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=x86_64-linux-musl-gcc CXX=x86_64-linux-musl-g++ go build -tags=musl # 如果linux不想安装musl支持 CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=x86_64-linux-musl-gcc CXX=x86_64-linux-musl-g++ CGO_LDFLAGS=\u0026#34;-static\u0026#34; go build -tags=musl CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=x86_64-linux-musl-gcc CGO_LDFLAGS=\u0026#34;-static\u0026#34; go build CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=x86_64-linux-musl-gcc CGO_LDFLAGS=\u0026#34;-static\u0026#34; go build 参考 # https://blog.csdn.net/qq_41416964/article/details/129571304 https://zhuanlan.zhihu.com/p/338891206 « Goreleaser\n» pprof\n"},{"id":91,"href":"/go/pprof/","title":"Pprof","section":"Go","content":" 🏠 首页 / Golang 编程 / pprof\npprof # pprof 是性能调试工具,可以生成类似火焰图、堆栈图,内存分析图等。\npackage main import ( \u0026#34;fmt\u0026#34; \u0026#34;math/rand\u0026#34; \u0026#34;net/http\u0026#34; \u0026#34;time\u0026#34; _ \u0026#34;net/http/pprof\u0026#34; ) // 吃内存 type Eater struct { Name string Buffer [][]int } var e Eater func main() { e = Eater{Name: \u0026#34;eater\u0026#34;} http.HandleFunc(\u0026#34;/go\u0026#34;, goHandler) http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil) // 如果不使用默认的 mux(http.DefaultServeMux),可以使用如下方式集成 pprof // mux := http.NewServeMux() // mux.HandleFunc(\u0026#34;/go\u0026#34;, goHandler) // mux.HandleFunc(\u0026#34;/debug/pprof/\u0026#34;, pprof.Index) // mux.HandleFunc(\u0026#34;/debug/pprof/cmdline\u0026#34;, pprof.Cmdline) // mux.HandleFunc(\u0026#34;/debug/pprof/profile\u0026#34;, pprof.Profile) // mux.HandleFunc(\u0026#34;/debug/pprof/symbol\u0026#34;, pprof.Symbol) // mux.HandleFunc(\u0026#34;/debug/pprof/trace\u0026#34;, pprof.Trace) // http.ListenAndServe(\u0026#34;:8080\u0026#34;, mux) fmt.Println(e.Name) } // 模拟创建 goroutine,内存没有及时回收 func goHandler(w http.ResponseWriter, r *http.Request) { for i := 0; i \u0026lt; 10; i++ { go func() { time.Sleep(time.Hour) }() e.EatMem() } w.Write([]byte(\u0026#34;ok\u0026#34;)) } func (e *Eater) EatMem() { e.Buffer = append(e.Buffer, generateWithCap(1024*1024)) } func generateWithCap(n int) []int { rand.Seed(time.Now().UnixNano()) nums := make([]int, 0, n) for i := 0; i \u0026lt; n; i++ { nums = append(nums, rand.Int()) } return nums } 运行:\ngo run main.go 访问 http://localhost:8080/go 模拟业务。\n访问 http://localhost:8080/debug/pprof/ 分析程序性能。\n图形方式分析:\n# 查看cpu go tool pprof -http=:6060 http://127.0.0.1:8080/debug/pprof/profile # 查看heap go tool pprof -http=:6060 http://127.0.0.1:8080/debug/pprof/heap # 查看goroutine go tool pprof -http=:6060 http://127.0.0.1:8080/debug/pprof/goroutine 需要提前安装工具 Graphviz\n内存使用:\ngoroutine 使用:\n« Mac M1 交叉编译 CGO\n» 使用 Go 生成 OpenSSH 兼容的 RSA 密钥对\n"},{"id":92,"href":"/go/ssh-keygen-with-go/","title":"SSH Keygen With Go","section":"Go","content":" 🏠 首页 / Golang 编程 / 使用 Go 生成 OpenSSH 兼容的 RSA 密钥对\n使用 Go 生成 OpenSSH 兼容的 RSA 密钥对 # 我们可以使用 ssh-keygen 命令生成一对用于 SSH 访问的私钥和公钥。本文将介绍如何使用 Go 生成一对 OpenSSH 兼容的 RSA 密钥对。\n以下代码中 GenOpenSSHKeyPair 方法用于生成一对用于 SSH 访问的私钥和公钥。生成的私钥以 PEM 编码,公钥以 OpenSSH authorized_keys 文件中包含的格式进行编码。\npackage util import ( \u0026#34;crypto/rand\u0026#34; \u0026#34;crypto/rsa\u0026#34; \u0026#34;crypto/x509\u0026#34; \u0026#34;encoding/pem\u0026#34; \u0026#34;golang.org/x/crypto/ssh\u0026#34; ) // GenOpenSSHKeyPair make a pair of private and public keys for SSH access. // Private Key generated is PEM encoded // Public key is encoded in the format for inclusion in an OpenSSH authorized_keys file. func GenOpenSSHKeyPair() ([]byte, []byte, error) { privateKey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return nil, nil, err } privateKeyPEM := \u0026amp;pem.Block{ Type: \u0026#34;RSA PRIVATE KEY\u0026#34;, Bytes: x509.MarshalPKCS1PrivateKey(privateKey), } rsaSSHPriKeyBytes := pem.EncodeToMemory(privateKeyPEM) pub, err := ssh.NewPublicKey(\u0026amp;privateKey.PublicKey) if err != nil { return nil, nil, err } rsaSSHPubKeyBytes := ssh.MarshalAuthorizedKey(pub) return rsaSSHPriKeyBytes, rsaSSHPubKeyBytes, nil } « pprof\n"},{"id":93,"href":"/graphql/","title":"Graphql","section":"","content":" 🏠 首页 / Graphql\nGraphql # Graphql\n"},{"id":94,"href":"/graphql/graphql/","title":"Graphql","section":"Graphql","content":" 🏠 首页 / Graphql / Graphql\nGraphql # https://graphql.cn/learn/\n介绍 # 一种 API 查询语言,也是一种满足使用现有数据完成几乎所有数据查询的运行时。\n对 API 数据提供可理解的完整描述,赋予客户端准确获取所需数据,使得 API 的演进更加轻松,也可以使用它来构建强大的开发者工具(LowCode?)。\n特性 # 所得即所取:\n请求什么,返回什么,因为总是根据你请求的结构返回可预见的结果。\n单取多得:\n单次请求,返回多种结构的数据,不仅对资源属性查询,而且还会沿着资源的引用关系进一步查询。\n这也是对比 Restful API 的一种优势,Restful API 对多资源的查询时,往往需要多次访问不同的API,这无疑增加了网络连接的压力。\n类型系统描述所有:\nGraphQL API 基于类型和字段的方式进行组织,而非入口端点。你可以通过一个单一入口端点得到你的访问数据的所有能力。GraphQL 使用类型来保证应用只请求可能的数据,否则会提供了明确的有用的错误信息。应用也可以使用类型,而避免编写手动解析代码。\nAPI无版本:\nAPI 演进只需要往 Graphql 类型系统中添加字段和类型,而不影响现有查询。\n统一共享:\nGraphQL 让你的整个应用共享一套 API,而不用被限制于特定存储引擎。GraphQL 引擎已经有多种语言实现,通过 GraphQL API 能够更好利用你的现有数据和代码。你只需要为类型系统的字段编写函数,GraphQL 就能通过优化并发的方式来调用它们。\n资源定义,请求和响应 # 数据描述:\ntype User { name: String phone: String Friends: [User] } 数据请求:\n{ user(name: \u0026#34;dp\u0026#34;) { phone } } 请求结果:\n{ \u0026#34;user\u0026#34;: { \u0026#34;phone\u0026#34;: \u0026#34;18800001111\u0026#34; } } 变量 # 变量前缀必须为 $,后跟其类型,例如:$user User\n变量必须是标量,枚举型或输入对象类型。\n如果类型后没有跟 !,说明变量是可选的,可以不传入参数,否则必须出传入。\n默认变量:\nquery HeroNameAndFriends($episode: Episode = \u0026#34;JEDI\u0026#34;) { hero(episode: $episode) { name friends { name } } } GraphQLite # 是一个为 GraphQL schema 定义提供基于注释的语法的库。 它与框架无关,可用于 Symfony 和 Laravel。\nSchema # 文档 # learn: https://graphql.cn/learn/\ncode: https://graphql.org/code/\nawesome-graphql: https://github.com/chentsulin/awesome-graphql\n"},{"id":95,"href":"/grpc/","title":"Grpc","section":"","content":" 🏠 首页 / Grpc\nGrpc # gRPC 实战\n"},{"id":96,"href":"/grpc/gRPC/","title":"G Rpc","section":"Grpc","content":" 🏠 首页 / Grpc / gRPC 实战\ngRPC 实战 # 准备 # golang 1.13+ Protoc "},{"id":97,"href":"/istio/","title":"Istio","section":"","content":" 🏠 首页 / Istio\nIstio # Istio\n使用 aws-acm 管理 tls 密钥和证书\n安装 Istio\n授权策略 Authorization Policy\n应用平台实现应用金丝雀发布\nIstio 0-1 使用Istio实现Cors\n使用 Istio 实现服务超时\n应用层级设置访问白名单\n实现 Https 协议的转发\nIstio 0-1 流量管理方案\n"},{"id":98,"href":"/istio/aws-acm-tls-management/","title":"Aws Acm Tls Management","section":"Istio","content":" 🏠 首页 / Istio / 使用 aws-acm 管理 tls 密钥和证书\n使用 aws-acm 管理 tls 密钥和证书 # 参考: https://medium.com/faun/managing-tls-keys-and-certs-in-istio-using-amazons-acm-8ff9a0b99033\n在Ingressgateway service的annotation中添加:\nkind: Service apiVersion: v1 metadata: name: my-ingressgateway namespace: istio-system labels: app: my-ingressgateway annotations: # external-dns.alpha.kubernetes.io/hostname: example.com service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: \u0026#39;3600\u0026#39; service.beta.kubernetes.io/aws-load-balancer-ssl-cert: \u0026gt;- arn:aws:acm:ap-southeast-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx service.beta.kubernetes.io/aws-load-balancer-ssl-ports: https .... service.yaml:\napiVersion: v1 kind: Service metadata: name: demo-api labels: app: demop-api spec: ports: - name: http port: 80 targetPort: 80 selector: app: demo-api pod 所在的 namespace 需要开启 istio-injection,例如:kubectl label namespace default istio-injection=enabled\ngateway.yaml\napiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: demo-api-gateway annotations: ingress.kubernetes.io/force-ssl-redirect: \u0026#34;true\u0026#34; kubernetes.io/ingress.class: \u0026#34;istio-gateway\u0026#34; spec: selector: istio: hpa-ingressgateway # use istio default controller servers: - port: number: 80 name: http protocol: HTTP hosts: - \u0026#34;example.com\u0026#34; tls: httpsRedirect: true # sends 301 redirect for http requests - port: number: 443 name: https protocol: HTTP hosts: - \u0026#34;example.com\u0026#34; example.com 替换成你访问服务的域名;\ntls 设置自动跳转 https;\n将 443 端口设置成 HTTP 协议,作为负载均衡器的 HTTPS 流量的目标端。\nvirtual-service.yaml:\napiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: demo-api spec: hosts: # - \u0026#34;demo-api-01.example.dev\u0026#34; - \u0026#34;example.com\u0026#34; gateways: - demo-api-gateway http: - match: - uri: prefix: /api/demo route: - destination: port: number: 80 host: demo-api example.com 替换成你访问服务的域名。\n« Istio\n» 安装 Istio\n"},{"id":99,"href":"/istio/installation/","title":"Installation","section":"Istio","content":" 🏠 首页 / Istio / 安装 Istio\n安装 Istio # 安装istioctl # Step 1 下载:\n以下操作步骤默认会安装最新版 istioctl。\ncurl -L https://istio.io/downloadIstio | sh - 以上命令默认会安装最新版 istioctl,如果需要安装指定版本例如 1.6.8,使用以下命令。\ncurl -L https://istio.io/downloadIstio | ISTIO_VERSION=1.6.8 TARGET_ARCH=x86_64 sh - Step 2 配置:\nstep 1会下载istio包,目录istio-{ISTIO_VERSION},进入目录\ncd istio-{ISTIO_VERSION} 拷贝bin目录下的istioctl二进制文件到PATH目录下:\ncp bin/istio /usr/local/bin « 使用 aws-acm 管理 tls 密钥和证书\n» 授权策略 Authorization Policy\n"},{"id":100,"href":"/istio/istio-auth-policy/","title":"Istio Auth Policy","section":"Istio","content":" 🏠 首页 / Istio / 授权策略 Authorization Policy\n授权策略 Authorization Policy # 授权架构 # 由于服务网格的 Sidecar 设计模式,每个工作负载都会有一个 Envoy 代理,而每个代理都运行着授权引擎,以此给请求授权。授权引擎依靠授权策略来鉴定请求权限,返回 ALLOW 或 DENY 鉴权结果。\n授权启用 # 将授权策略应用到工作负载即生效访问控制。对于没有应用授权策略的工作负载,则不会对请求做访问控制。\n授权策略 # 资源定义 # selector:\n标签选择器,通过标签选择器选择同命名空间下的目标工作负载,对目标工作负载启用访问控制。\naction:\n当满足rules条件时,控制ALLOW或DENY请求。\nrules:\n访问控制的请求条件:\nfrom:请求来源 to:请求目标 when:应用规则所需的提交 示例 # 以下授权策略允许两个源(服务帐号 cluster.local/ns/default/sa/sleep 和命名空间 dev),在使用有效的 JWT 令牌发送请求时,可以访问命名空间 foo 中的带有标签 app: httpbin 和 version: v1 的工作负载。\napiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: httpbin namespace: foo spec: selector: matchLabels: app: httpbin version: v1 action: ALLOW rules: - from: - source: principals: [\u0026#34;cluster.local/ns/default/sa/sleep\u0026#34;] - source: namespaces: [\u0026#34;dev\u0026#34;] to: - operation: methods: [\u0026#34;GET\u0026#34;] when: - key: request.auth.claims[iss] values: [\u0026#34;https://accounts.google.com\u0026#34;] 下例授权策略,如果请求来源不是命名空间 foo,请求将被拒绝。\napiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: httpbin-deny namespace: foo spec: selector: matchLabels: app: httpbin version: v1 action: DENY rules: - from: - source: notNamespaces: [\u0026#34;foo\u0026#34;] « 安装 Istio\n» 应用平台实现应用金丝雀发布\n"},{"id":101,"href":"/istio/istio-canary-deploy/","title":"Istio Canary Deploy","section":"Istio","content":" 🏠 首页 / Istio / 应用平台实现应用金丝雀发布\n应用平台实现应用金丝雀发布 # 实现思路 # 应用正常的 CI 流程添加请求参数:\nCanaryMode【bool,true | false,缺省值 false】 CanaryWeight 【int,缺省值 10】 使用 Istio 的 DetinationRule + VirtualService 实现流量按权重分配到不同版本应用。\n概念 # Iteration:发布迭代号,。是一个字段值,使用金丝雀发布时累加该值,从 0 累加。\nRetract:动作,弃用金丝雀版本。当金丝雀版本存在不可忽视的问题时撤回金丝雀执行该动作。\nRatify:动作,确认使用金丝雀版本。当金丝雀版本确认可以全面投入使用执行该动作。\n前提 # 使用金丝雀发布的前提条件:\n应用处于发布状态,已经至少发布一次,当前存在稳定的运行版本; 应用已经开启 Istio Gateway,涉及到 VirtualService 的流量转发; 应用当前不是金丝雀状态,要不然乱套了。 发布细节 # CI 发布除了创建或更新 Deployment,Service 之外,默认创建或更新 istio 的 DestinationRule 资源;\ndeployment:\napiVersion: apps/v1 kind: Deployment metadata: name: myapp-pbd3n69-v{iteration} # 出于兼容历史发布,当iteration为0时,name不附带iteration,iteration大于0时,name将附带iteration labels: app: myapp-pbd3n69 version: v{iteration} spec: selector: matchLabels: app: myapp-pbd3n69 version: v{iteration} template: metadata: labels: app: myapp-pbd3n69 version: v{iteration} ... service:\napiVersion: v1 kind: Service metadata: name: myapp-pbd3n69 spec: selector: app: myapp-pbd3n69 ... destination rule:\napiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: myapp-pbd3n69 spec: host: myapp-pbd3n69 # 注意:这里是 Service 的 name subsets: - labels: version: v{iteration} name: subset-v{iteration} virtual service:\napiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: virtual-service-example-com spec: hosts: - example.com http: - match: - uri: prefix: /xxx name: myapp-pbd3n69 route: - destination: host: myapp-pbd3n69 # 注意:这里是 Service 的 name port: number: 80 subset: subset-v{iteration} weight: 100 使用金丝雀发布,涉及到金丝雀版本 Deployment 的创建,DestinationRule 和 VirtualService 的更新:\ndeployment:\napiVersion: apps/v1 kind: Deployment metadata: name: myapp-pbd3n69-v{iteration+1} labels: app: myapp-pbd3n69 version: v{iteration+1} spec: selector: matchLabels: app: myapp-pbd3n69 version: v{iteration+1} template: metadata: labels: app: myapp-pbd3n69 version: v{iteration+1} destination rule:\napiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: ash-pbd3n69 namespace: test spec: host: ash-pbd3n69 # 注意:这里是 Service 的 name subsets: - labels: version: v{iteration} name: subset-v{iteration} - labels: version: v{iteration+1} name: subset-v{iteration+1} virtual service:\napiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: virtual-service-example-com spec: hosts: - example.com http: - match: - uri: prefix: /xxx name: myapp-pbd3n69 route: - destination: host: myapp-pbd3n69 # 注意:这里是 Service 的 name port: number: 80 subset: subset-v{iteration} weight: {canaryWeight} - destination: host: myapp-pbd3n69 # 注意:这里是 Service 的 name port: number: 80 subset: subset-v{iteration+1} weight: {100-canaryWeight} 发布完成后,Iteration+1,应用状态更新为 Canary。在 Canary 状态下,由于存在两个版本的 Deployment,将无法手动应用垂直或水平扩容。\n撤回(Retract) # 删除金丝雀版本的 Deployment,\nDestinationRule 更新\napiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: ash-pbd3n69 namespace: test spec: host: ash-pbd3n69 # 注意:这里是 Service 的 name subsets: - labels: version: v{iteration-1} name: subset-v{iteration-1} VirtualService 更新\napiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: virtual-service-example-com spec: hosts: - example.com http: - match: - uri: prefix: /xxx name: myapp-pbd3n69 route: - destination: host: myapp-pbd3n69 # 注意:这里是 Service 的 name port: number: 80 subset: subset-v{iteration-1} weight: 100 Iteration-1\n批准(Ratify) # 删除之前稳定版本的 Deployment,\nDestinationRule 更新\napiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: ash-pbd3n69 namespace: test spec: host: ash-pbd3n69 # 注意:这里是 Service 的 name subsets: - labels: version: v{iteration} name: subset-v{iteration} VirtualService 更新\napiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: virtual-service-example-com spec: hosts: - example.com http: - match: - uri: prefix: /xxx name: myapp-pbd3n69 route: - destination: host: myapp-pbd3n69 # 注意:这里是 Service 的 name port: number: 80 subset: subset-v{iteration} weight: 100 « 授权策略 Authorization Policy\n» Istio 0-1 使用Istio实现Cors\n"},{"id":102,"href":"/istio/istio-cors/","title":"Istio Cors","section":"Istio","content":" 🏠 首页 / Istio / Istio 0-1 使用Istio实现Cors\nIstio 0-1 使用Istio实现Cors # Cors # Cors(Cross-Origin Resource Sharing):跨域资源共享,是一种基于 HTTP Header 的机制,该机制通过允许服务器标示除了它自己以外的其它 origin(域,协议和端口),这样浏览器可以访问加载这些资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的\u0026quot;预检\u0026quot;请求。在预检中,浏览器发送的头中标示有 HTTP 方法和真实请求中会用到的头。\n跨源HTTP请求的一个例子:运行在 https://a.com 的JavaScript代码使用 XMLHttpRequest来发起一个到 https://b.com/data.js 的请求。\n出于安全性,浏览器限制脚本内发起的跨域 HTTP 请求。 例如,XMLHttpRequest 和 Fetch API 遵循同源策略。 这意味着使用这些 API 的 Web 应用程序只能从加载应用程序的同一个域请求 HTTP 资源,除非响应报文包含了正确 CORS 响应头。\n更多 Cors 知识: 跨域资源共享(CORS)\nIstio 实现 # 基于 Istio VirtualService 的配置实现。\n官方文档: Istio / Virtual Service#CorsPolicy\n在目标服务上设置允许的请求域 Hello:\napiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: b spec: http: - route: - destination: host: b.default.svc.cluster.local corsPolicy: allowOrigins: - exact: https://a.com - exact: https://b.com allowMethods: - GET 配置 allowOrigins 以及 allowMethods。\n« 应用平台实现应用金丝雀发布\n» 使用 Istio 实现服务超时\n"},{"id":103,"href":"/istio/istio-timeout/","title":"Istio Timeout","section":"Istio","content":" 🏠 首页 / Istio / 使用 Istio 实现服务超时\n使用 Istio 实现服务超时 # 参考 # https://www.servicemesher.com/blog/circuit-breaking-and-outlier-detection-in-istio/ 超时 # 为了防止无限期的等待服务,一般都会给服务设置超时时间,AWS 的 LoadBalancer 默认的超时时间是 60s。但是不同的服务,可能需要不同的超时设置,例如 DocumentApi 超时时间可能需要设置的长一点。\nLoadBalancer 的超时是全局的,我们基于 Istio 服务网格集成了针对单个服务的超时功能。\n重试 # 重试也是一个服务很常用的功能,例如某次请求分配到了一个问题节点,请求失败,则自动重试特定次数。\n熔断/限流 # 为了防止大量涌入请求使得服务崩溃,引入熔断功能。熔断,可以限制当前请求连接数在一个特定范围内。\n配置服务 VirtualService 既可实现:\napiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: myapp spec: hosts: - myapp http: - route: - destination: host: myapp subset: v1 timeout: 1s timeout:请求超过设定的超时时间,响应返回 504 请求超时。\n« Istio 0-1 使用Istio实现Cors\n» 应用层级设置访问白名单\n"},{"id":104,"href":"/istio/istio-white-manifest/","title":"Istio White Manifest","section":"Istio","content":" 🏠 首页 / Istio / 应用层级设置访问白名单\n应用层级设置访问白名单 # 需求 # 两个应用,foo 和 bar,应用 foo 只允许 IP 地址为 1.2.3.4 访问,应用 bar 只允许 IP 地址为 5.6.7.8 访问。\n实现 # 基于 istio 的 AuthorizationPolicy 实现。\n假设,现在 K8s 集群中已经安装 istio,并且有一个正在运行着的 istio-ingressgateway 转发应用 foo 和 bar:\n通过: https://www.example.com/foo 访问 foo;\n通过: https://www.example.com/bar 访问 bar;\n插曲 # 按照 istio 官方文档,使用 AuthorizationPolicy 即可实现基于应用层级的访问白名单设置:\napiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: foo spec: selector: matchLabels: app: foo action: ALLOW rules: - from: - source: ipBlocks: [\u0026#34;1.2.3.4\u0026#34;] 想让以上AuthorizationPolicy生效,需要在 foo 工作负载所在的命名空间注入 istio-proxy,通过给命名空间添加 label,添加 lable 后,工作负载启动新的 Pod 会自动注入 istio-proxy 容器:\nkubectl label namespace default istio-injection=enabled 但是,你会发现你使用 https://www.example.com/foo 一直返回的信息都是 RBAC: access denied。为什么会这样呢?\n如果你在 ipBlocks 中加入 istio-ingressgateway(是一个 Deployment)Pod 的 IP,你会发现这时候是可以成功访问的。\n分析流量的转发路径应该就能知道,实际上到达目标应用 foo 的请求,都由istio-ingressgateway转发了,所以源 IP 都会是 istio-ingressgateway Pod 的 IP,从而导致外部访问 IP 的白名单设置无法生效。\n那么如果获取外部IP呢。\n最终方案 # Step 0: 获取客户端源IP:\n如果使用 AWS-EKS 创建 istio-ingressgateway 服务时默认创建的 classic 类型的 LoadBalancer,需要修改成 network类型:\n通过向 istio-ingressgateway Service 添加 annotation:service.beta.kubernetes.io/aws-load-balancer-type: nlb\n更新 istio-gateway Service:spec.externalTrafficPolicy: Local,现在访问工作负载可以获取到源 IP,这就为我们设置 IP 白名单造就了条件。\n现在只需要为这个 istio-ingressgateway 应用上 AuthorizationPolicy 即可:\nStep 1: 默认允许所有 IP 访问 istio-ingressgateway:\napiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: allow-all-for-istio-ingressgateway namespace: istio-system spec: selector: matchLabels: istio: ingressgateway action: ALLOW rules: - from: - source: ipBlocks: [\u0026#34;0.0.0.0/0\u0026#34;] 默认是允许所有 IP 都可以访问 istio-ingressgateway 下转发的服务的,如果有服务例如 foo 和 bar 需要单独添加 IP 访问百名单则继续参考下面的步骤。\nStep 2: 只允许 IP 1.2.3.4 访问 foo:\napiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: foo namespace: istio-system spec: selector: matchLabels: istio: ingressgateway action: DENY rules: - from: - source: notIpBlocks: [\u0026#34;1.2.3.4\u0026#34;] to: - operation: hosts: - www.example.com paths: - /foo - /foo/* 由于默认是允许所有 IP 都可以访问 istio-ingressgateway 下转发的服务,所以为单独应用设置访问百名的时候使用 DENY + notIpBlocks 来完成。\nipBlocks 和 notIpBlocks 允许配置IP和IP的CIDR,例如:1.2.3.4 或 1.2.3.4/24。\nStep 3: 只允许 IP 5.6.7.8 访问 bar:\napiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: bar namespace: istio-system spec: selector: matchLabels: istio: ingressgateway action: DENY rules: - from: - source: notIpBlocks: [\u0026#34;5.6.7.8\u0026#34;] to: - operation: hosts: - www.example.com paths: - /bar - /bar/* 附录 # 如果相同的 LoadBalancer 下的服务都是用同一白名单设置的话,则没必要这么麻烦的设置 AuthorizationPolicy 了,只需要为 Service 设置参数即可:\napiVersion: v1 kind: Service metadata: name: istio-gateway ... spec: type: LoadBalancer loadBalancerSourceRanges: - \u0026#34;1.2.3.4/24\u0026#34; ... « 使用 Istio 实现服务超时\n» 实现 Https 协议的转发\n"},{"id":105,"href":"/istio/Istio/","title":"Istio","section":"Istio","content":" 🏠 首页 / Istio / Istio\nIstio # 简介 # Istio,是一种服务网格的平台。在微服务系统中起着连接,保护,控制和观察服务的作用。它可以降低微服务部署的复杂程度,减轻开发团队压力,无缝接入现有分布式应用程序,可以集成日志,遥测,和策略系统的 API 接口。\n服务网格:\nService Mesh 是一个基础设施层,用于处理服务间通信。云原生应用有着复杂的服务拓扑,Service Mesh 保证请求可以在这些拓扑中可靠地穿梭。在实际应用当中,Service Mesh 通常是由一系列轻量级的网络代理组成的,它们与应用程序部署在一起,但应用程序不需要知道它们的存在。\n用来描述组成应用程序的微服务网络以及它们之间的交互。\n实现需求包括:\n服务发现 负载均衡 故障恢复 指标和监控 A/B 测试 金丝雀发布 速率控制 访问控制 端到端认证 istioctl # 管理 istio 的命令行工具。\n安装 # curl -L https://istio.io/downloadIstio | sh - cp istio-x.x.x /usr/local cd istio-x.x.x export PATH=$PWD/bin:$PATH Istio 的绝大多数治理能力都是在 Sidecar 而非应用程序中实现,因此是非侵入的; Istio 的调用链埋点逻辑也是在 Sidecar 代理中完成,对应用程序非侵入,但应用程序需做适当的修改,即配合在请求头上传递生成的 Trace 相关信息。 关键功能:\n流量管理 可观察性 策略执行 服务身份和安全 平台支持 集成和定制 架构 # 数据面板 # Envoy # Envoy 是用 C++ 开发的高性能代理,用于协调服务网格中所有服务的入站和出站流量。Envoy 代理是唯一与数据平面流量交互的 Istio 组件。\n控制面板 # Pilot # 为 Envoy sidecar 提供服务发现、用于智能路由的流量管理功能(例如,A/B 测试、金丝雀发布等)以及弹性功能(超时、重试、熔断器等)。\n结构:\nAbstract Model Platform Adapter 功能:\n请求路由 服务发现和负载均衡 故障处理 故障注入 规则配置 Citadel # 通过内置的身份和证书管理,可以支持强大的服务到服务以及最终用户的身份验证。\nGalley # 是 Istio 的配置验证、提取、处理和分发组件。它负责将其余的 Istio 组件与从底层平台(例如 Kubernetes)获取用户配置的细节隔离开来。\nMixer # 流量管理 # 服务注册中心,服务发现系统,默认轮询策略 负载均衡池\n虚拟服务(Virtual Service) # 虚拟服务让您配置如何在服务网格内将请求路由到服务,这基于 Istio 和平台提供的基本的连通性和服务发现能力。\n示例:\napiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: reviews spec: hosts: - reviews gateway: http: - match: - headers: end-user: exact: jason route: - destination: host: reviews subset: v2 - route: - destination: host: reviews subset: v3 路由规则 # - match: - headers: end-user: exact: jason - match: - uri: prefix: /reviews 路由规则的优先级:从上往下,满足规则即流向路由目标\nDestination # route: - destination: host: reviews subset: v1 weight: 90 - destination: host: reviews subset: v2 weight: 10 目标规则(Destination Rule) # Deployment =\u0026gt; Pod =\u0026gt; Service =\u0026gt; DestinationRule =\u0026gt; VirtualService=\u0026gt; Gateway\n负载均衡的子集\napiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: my-destination-rule spec: host: my-svc trafficPolicy: loadBalancer: simple: RANDOM subsets: - name: v1 labels: version: v1 - name: v2 labels: version: v2 trafficPolicy: loadBalancer: simple: ROUND_ROBIN - name: v3 labels: version: v3 子集基于一个或多个 labels\nsubsets.trafficPolicy.loadBalancer.simple 指定访问 Service 后端的 endpoint 的流量策略。RANDOM:随机,ROUND_ROBIN:轮询。\n网关(Gateway) # 为网格来管理入站和出站流量,可以让您指定要进入或离开网格的流量。\napiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: ext-host-gwy spec: selector: app: my-gateway-controller servers: - port: number: 443 name: https protocol: HTTPS hosts: - ext-host.example.com tls: mode: SIMPLE serverCertificate: /tmp/tls.crt privateKey: /tmp/tls.key 将指定 host 的流量流入网格,然后将网关绑定到 VirtualService 上进行路由规则的指定。\n服务入口(Service Entry) # apiVersion: networking.istio.io/v1alpha3 kind: ServiceEntry metadata: name: svc-entry spec: hosts: - ext-svc.example.com ports: - number: 443 name: https protocol: HTTPS location: MESH_EXTERNAL resolution: DNS Sidecar # 网络弹性和测试 # 超时 # apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: ratings spec: hosts: - ratings http: - route: - destination: host: ratings subset: v1 timeout: 10s 下面的示例是一个虚拟服务,它对 ratings 服务的 v1 子集的调用指定 10 秒超时。\n重试 # apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: ratings spec: hosts: - ratings http: - route: - destination: host: ratings subset: v1 retries: attempts: 3 perTryTimeout: 2s 下面的示例配置了在初始调用失败后最多重试 3 次来连接到服务子集,每个重试都有 2 秒的超时。\n熔断 # apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: reviews spec: host: reviews subsets: - name: v1 labels: version: v1 trafficPolicy: connectionPool: tcp: maxConnections: 100 到达目标主机的请求超过预设值,则触发熔断,停止请求连接到该主机。\n故障注入(混沌工程) # apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: ratings spec: hosts: - ratings http: - fault: delay: percentage: value: 50 fixedDelay: 5s route: - destination: host: ratings subset: v1 下面的虚拟服务为百分之五十的访问 ratings 服务的请求配置了一个 5 秒的延迟。\napiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: ratings spec: hosts: - ratings http: - fault: abort: httpStatus: 500 percentage: value: 100 route: - destination: host: ratings subset: v1 下面的虚拟服务为所有访问 ratings 服务的请求配置了一个中止响应。\n流量转移 # 逐步调整百分比的流量,完成由部分路由到完全路由的迁移升级。\n如:v1 80% + v2 20% =\u0026gt; v2 100%\n记录 # 管理 ingressgateway https 连接使用AWS的 TLS 密钥和证书。\nkind: Service apiVersion: v1 metadata: name: istio-ingressgateway namespace: istio-system ... annotations: ... service.beta.kubernetes.io/aws-load-balancer-backend-protocol: tcp service.beta.kubernetes.io/aws-load-balancer-ssl-cert: \u0026gt;- arn:aws:acm:ap-southeast-1:314566904004:certificate/379dd055-44a7-42b6-a303-84938146b304 service.beta.kubernetes.io/aws-load-balancer-ssl-ports: https Gateway:\napiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: demo-gateway spec: selector: istio: ingressgateway servers: - port: number: 80 name: http protocol: HTTP hosts: - \u0026#34;*\u0026#34; tls: httpsRedirect: true # sends 301 redirect for http requests - port: number: 443 name: https-443 protocol: HTTP hosts: - \u0026#34;*\u0026#34; » 使用 aws-acm 管理 tls 密钥和证书\n"},{"id":106,"href":"/istio/tls-transform/","title":"Tls Transform","section":"Istio","content":" 🏠 首页 / Istio / 实现 Https 协议的转发\n实现 Https 协议的转发 # apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: demo spec: http: - headers: request: set: X-Forwarded-Proto: https match: - uri: prefix: / name: demo.default « 应用层级设置访问白名单\n» Istio 0-1 流量管理方案\n"},{"id":107,"href":"/istio/traffic-management/","title":"Traffic Management","section":"Istio","content":" 🏠 首页 / Istio / Istio 0-1 流量管理方案\nIstio 0-1 流量管理方案 # 设置 istio-system 命名空间下 istiod Deployment 的环境变量:\nPILOT_ENABLE_VIRTUAL_SERVICE_DELEGATE =true:\n« 实现 Https 协议的转发\n"},{"id":108,"href":"/kubernetes/","title":"Kubernetes","section":"","content":" 🏠 首页 / Kubernetes\nKubernetes # 反亲和性提高服务可用性\napiserver-builder\napiserver\n二进制搭建 K8s - 1 机器准备\n二进制搭建 K8s - 2 部署 etcd 集群\n二进制搭建 K8s - 3 部署 Master\n二进制搭建 K8s - 4 部署 Node\nKubernetes 0-1 尝试理解云原生\n集群联邦\n了解 ConfigMap\n定期删除 ElasticSearch 日志索引\n强制删除 K8s 资源\nGateway API 实践\nKubernetes 0-1 Helm Kubernetes 的包管理工具\nKubernetes 0-1 实现Pod自动扩缩HPA\nHTTP 客户端调用 Kubernetes APIServer\nInformer\n通过 Ingress 进行灰度发布\n安装 Kubernetes\nK3s\nKubernetes 0-1 K8s部署coredns\nKubernetes 0-1 K8s部署Dashboard\nKubernetes 0-1 K8s部署EFK\n可能需要运行多次以下命令,确保k8s资源都创建\nKubernetes 0-1 K8s部署Zookeeper和Kafka\nKubernetes 定制开发 01:K8s API 概念\nKubernetes 定制开发 02:CRD\nKubernetes 定制开发 50:扩展调度器\n简单介绍 K8s\nkubeadm 安装 Kubernetes (Docker)\nkubeadm 安装 k8s (containerd)\nKubeadm 升级 K8s\nkubebuilder 实战\nkubectl\nKubernetes 0-1 Kubernetes最佳实践\nKubernetes Dashboard\nKubernetes 中资源名称规范\nKuberentes\nKubeVirt 创建 Windows 虚拟机\nKubevirt 实践\nKustomize\nKubernetes 0-1 Pod中的livenessProbe和readinessProbe解读\nlocal 存储卷实践\nKubernetes 0-1 K8s自建LoadBalancer\n使用 nfs 持久化存储\nKubernetes 0-1 了解 Pod\nKubernetes 编程\nPrometheus-监控Kong完整操作\nPrometheus\nPVC 扩容\n了解 Secret\n了解 Service\nTelepresence\nKubernetes 0-1 使用preStop优雅终止Pod\nTerraform\nVelero + Minio 备份与恢复\n了解 Volume\nVPA\n"},{"id":109,"href":"/kubernetes/anti-affinity-improves-service-availability/","title":"Anti Affinity Improves Service Availability","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 反亲和性提高服务可用性\n反亲和性提高服务可用性 # 在 Kubernetes 中部署服务时,我们通常会部署多副本来提高服务的可用性。但是当这些副本集中部署在一个节点,而且很不幸,该节点出现故障,那么服务很容易陷入不可用状态。\n下面介绍一种方法,将服务副本分散部署在不同的节点(把鸡蛋放在不同的篮子里),避免单个节点故障导致服务多副本毁坏,提高服务可用性。\n反亲和 # apiVersion: apps/v1 kind: Deployment metadata: name: nginx labels: app: nginx spec: selector: matchLabels: app: nginx replicas: 5 template: metadata: labels: app: nginx spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - nginx topologyKey: kubernetes.io/hostname containers: - name: nginx image: nginx ports: - name: tcp containerPort: 80 使用 kubernetes.io/hostname 作为拓扑域,查看匹配规则,即同一打有同样标签 app=nginx 的 pod 会调度到不同的节点。\npodAntiAffinity 使用场景:\n将一个服务的 Pod 分散在不同的主机或者拓扑域中,提高服务本身的稳定性。 给 Pod 对于一个节点的独占访问权限来保证资源隔离,保证不会有其它 Pod 来分享节点资源。 把可能会相互影响的服务的 Pod 分散在不同的主机上 对于亲和性和反亲和性,每种都有三种规则可以设置:\nRequiredDuringSchedulingRequiredDuringExecution:在调度期间要求满足亲和性或者反亲和性规则,如果不能满足规则,则 Pod 不能被调度到对应的主机上。在之后的运行过程中,如果因为某些原因(比如修改 label)导致规则不能满足,系统会尝试把 Pod 从主机上删除(现在版本还不支持)。 RequiredDuringSchedulingIgnoredDuringExecution:在调度期间要求满足亲和性或者反亲和性规则,如果不能满足规则,则 Pod 不能被调度到对应的主机上。在之后的运行过程中,系统不会再检查这些规则是否满足。 PreferredDuringSchedulingIgnoredDuringExecution:在调度期间尽量满足亲和性或者反亲和性规则,如果不能满足规则,Pod 也有可能被调度到对应的主机上。在之后的运行过程中,系统不会再检查这些规则是否满足。 亲和性/反亲和性调度策略比较 # 调度策略 匹配标签 操作符 拓扑域支持 调度目标 nodeAffinity 主机 In, NotIn, Exists, DoesNotExist, Gt, Lt 否 pod 到指定主机 podAffinity Pod In, NotIn, Exists, DoesNotExist 是 pod 与指定 pod 同一拓扑域 PodAntiAffinity Pod In, NotIn, Exists, DoesNotExist 是 pod 与指定 pod 非同一拓扑域 » apiserver-builder\n"},{"id":110,"href":"/kubernetes/apiserver-builder/","title":"Apiserver Builder","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / apiserver-builder\napiserver-builder # 安装 # go install sigs.k8s.io/apiserver-builder-alpha/cmd/apiserver-boot@v1.23.0 搭建 # 初始化项目:\n⚠️ 注意:由于历史原因需要进入 $(go env GOPATH)/src/\u0026lt;package\u0026gt; 包目录下执行初始化命令。\nmkdir -p $(go env GOPATH)/src/github.com/poneding/apiserver-demo \u0026amp;\u0026amp; cd $(go env GOPATH)/src/github.com/poneding/apiserver-demo apiserver-boot init repo --domain k8sdev.poneding.com 创建 API:\n# apiserver-boot create \u0026lt;group\u0026gt; \u0026lt;version\u0026gt; \u0026lt;resource\u0026gt; apiserver-boot create demo v1alpha1 User apiserver-boot create group version resource --group demo --version v1alpha1 --kind User 参考 # https://github.com/kubernetes-sigs/apiserver-builder-alpha/blob/master/docs/tools_user_guide.md https://github.com/kubernetes-sigs/apiserver-builder-alpha/blob/master/README.md « 反亲和性提高服务可用性\n» apiserver\n"},{"id":111,"href":"/kubernetes/apiserver/","title":"Apiserver","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / apiserver\napiserver # 每一个 api 版本均有一个 apiservice 与之对应\nk api-versions | wc -l 30 k get apiservices.apiregistration.k8s.io| wc -l 30 « apiserver-builder\n» 二进制搭建 K8s - 1 机器准备\n"},{"id":112,"href":"/kubernetes/binary-build-k8s-01-prepare-nodes/","title":"Binary Build K8s 01 Prepare Nodes","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 二进制搭建 K8s - 1 机器准备\n二进制搭建 K8s - 1 机器准备 # 写在前面 # 记录和分享使用二进制搭建 K8s 集群的详细过程,由于操作比较冗长,大概会分四篇写完:\n机器准备: 部署 etcd 集群: 部署 Master: 部署 Node: 整个目标是使用二进制的方式搭建一个小型 K8s 集群(1 个 Master,2 个 Node),供自己学习测试。\n至于为什么要自己去用二进制的方式去搭建 K8s,而不是选用 minikube 或者 kubeadm 去搭建?\n因为使用二进制搭建,K8s 的每个组件,每个工具都需要你手动的安装和配置,帮助你加深对 K8s 组织架构和工作原理的了解。\n准备工作 # 三台 centos7 虚拟机,自己学习使用的话 1 核 1G 应该就够了。\n虚拟机能够连网,相关的安装包文件下载和 Docker 下载镜像需要使用到外网。\n当前虚拟机:\nk8s-master01: 192.168.115.131 k8s-node01: 192.168.115.132 k8s-node02: 192.168.115.133 虚拟机初始化 # 不做特殊说明的话:\n以下操作需要在 Master 和 Node 的所有机器上执行\n使用 sudo 权限执行命令\n配置网络接口 # # 使用 ip addr 获取不到机器的 IP 时执行 dhclient 命令 dhclient 安装基础软件 # yum install vim ntp wget -y 修改主机名并添加 hosts # 在k8s-master01上执行\nhostnamectl set-hostname \u0026#34;k8s-master01\u0026#34; 在k8s-node01上执行\nhostnamectl set-hostname \u0026#34;k8s-node01\u0026#34; 在k8s-node02上执行\nhostnamectl set-hostname \u0026#34;k8s-node02\u0026#34; 添加 hosts # vim /etc/hosts 执行上行命令,在文件中追加以下内容:\n192.168.115.131 k8s-master01 192.168.115.132 k8s-node01 192.168.115.133 k8s-node02 关闭防火墙、selinux、swap # systemctl stop firewalld systemctl disable firewalld setenforce 0 sed -i \u0026#39;s/enforcing/disabled/\u0026#39; /etc/selinux/config swapoff -a vim /etc/fstab # 编辑 etc/fstab 文件,注释 swap 所在的行 同步时间 # ntpdate time.windows.com Master 准备文件 # 在 Master 机器执行:\nmkdir /root/kubernetes/resources -p cd /root/kubernetes/resources wget https://dl.k8s.io/v1.18.3/kubernetes-server-linux-amd64.tar.gz wget https://github.com/etcd-io/etcd/releases/download/v3.4.9/etcd-v3.4.9-linux-amd64.tar.gz wget https://pkg.cfssl.org/R1.2/cfssl_linux-amd64 wget https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64 wget https://pkg.cfssl.org/R1.2/cfssl-certinfo_linux-amd64 wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml Node 准备文件 # 在 Node 机器执行:\nmkdir /root/kubernetes/resources -p cd /root/kubernetes/resources wget https://dl.k8s.io/v1.18.3/kubernetes-node-linux-amd64.tar.gz wget https://github.com/etcd-io/etcd/releases/download/v3.4.9/etcd-v3.4.9-linux-amd64.tar.gz wget https://github.com/containernetworking/plugins/releases/download/v0.8.6/cni-plugins-linux-amd64-v0.8.6.tgz wget https://download.docker.com/linux/static/stable/x86_64/docker-19.03.9.tgz 有些文件的较大,下载花费时间可能较长。可以提前下载好之后,拷贝到虚拟机。\n第一段落机器准备愉快结束。\n« apiserver\n» 二进制搭建 K8s - 2 部署 etcd 集群\n"},{"id":113,"href":"/kubernetes/binary-build-k8s-02-deploy-etcd/","title":"Binary Build K8s 02 Deploy Etcd","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 二进制搭建 K8s - 2 部署 etcd 集群\n二进制搭建 K8s - 2 部署 etcd 集群 # 写在前面 # 记录和分享使用二进制搭建 K8s 集群的详细过程,由于操作比较冗长,大概会分四篇写完:\n机器准备: 部署 etcd 集群: 部署 Master: 部署 Node: etcd 作为 K8s 的数据库,需要首先安装,为其他组件做服务基础。\netcd 是一个分布式的数据库系统,为了模拟 etcd 的高可用,我们将 etcd 部署在三台虚拟机上,正好就部署在 K8s 集群所使用的三台机器上吧。\netcd 集群,K8s 组件之间通信,为了安全可靠,我们最好启用 HTTPS 安全机制。K8s 提供了基于 CA 签名的双向数字证书认证方式和简单的基于 HTTP Base 或 Token 的认证方式,其中 CA 证书方式的安全性最高。我们使用 cfssl 为我们的 K8s 集群配置 CA 证书,此外也可以使用 openssl。\n安装 cfssl # 在 Master 机器执行:\ncd /root/kubernetes/resources cp cfssl_linux-amd64 /usr/bin/cfssl cp cfssljson_linux-amd64 /usr/bin/cfssljson cp cfssl-certinfo_linux-amd64 /usr/bin/cfssl-certinfo chmod +x /usr/bin/cfssl /usr/bin/cfssljson /usr/bin/cfssl-certinfo 在所有机器执行:\nmkdir /etc/etcd/ssl -p 制作 etcd 证书 # 在 Master 机器执行:\nmkdir /root/kubernetes/resources/cert/etcd -p cd /root/kubernetes/resources/cert/etcd 编辑 ca-config.json\nvim ca-config.json 写入文件内容如下:\n{ \u0026#34;signing\u0026#34;: { \u0026#34;default\u0026#34;: { \u0026#34;expiry\u0026#34;: \u0026#34;87600h\u0026#34; }, \u0026#34;profiles\u0026#34;: { \u0026#34;etcd\u0026#34;: { \u0026#34;expiry\u0026#34;: \u0026#34;87600h\u0026#34;, \u0026#34;usages\u0026#34;: [ \u0026#34;signing\u0026#34;, \u0026#34;key encipherment\u0026#34;, \u0026#34;server auth\u0026#34;, \u0026#34;client auth\u0026#34; ] } } } } 编辑 ca-csr.json:\nvim ca-csr.json 写入文件内容如下:\n{ \u0026#34;CN\u0026#34;: \u0026#34;etcd ca\u0026#34;, \u0026#34;key\u0026#34;: { \u0026#34;algo\u0026#34;: \u0026#34;rsa\u0026#34;, \u0026#34;size\u0026#34;: 2048 }, \u0026#34;names\u0026#34;: [ { \u0026#34;C\u0026#34;: \u0026#34;CN\u0026#34;, \u0026#34;L\u0026#34;: \u0026#34;Hunan\u0026#34;, \u0026#34;ST\u0026#34;: \u0026#34;Changsha\u0026#34; } ] } 生成 ca 证书和密钥:\ncfssl gencert -initca ca-csr.json | cfssljson -bare ca 编辑 server-csr.json:\nvim server-csr.json 写入文件内容如下:\n{ \u0026#34;CN\u0026#34;: \u0026#34;etcd\u0026#34;, \u0026#34;hosts\u0026#34;: [ \u0026#34;192.168.115.131\u0026#34;, \u0026#34;192.168.115.132\u0026#34;, \u0026#34;192.168.115.133\u0026#34; ], \u0026#34;key\u0026#34;: { \u0026#34;algo\u0026#34;: \u0026#34;rsa\u0026#34;, \u0026#34;size\u0026#34;: 2048 }, \u0026#34;names\u0026#34;: [ { \u0026#34;C\u0026#34;: \u0026#34;CN\u0026#34;, \u0026#34;L\u0026#34;: \u0026#34;Hunan\u0026#34;, \u0026#34;ST\u0026#34;: \u0026#34;Changsha\u0026#34; } ] } hosts 中配置所有 Master 和 Node 的 IP 列表。\n生成 etcd 证书和密钥\ncfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=etcd server-csr.json | cfssljson -bare server # 此时目录下会生成 7 个文件 ls ca-config.json ca.csr ca-csr.json ca-key.pem ca.pem server.csr server-csr.json server-key.pem server.pem 拷贝证书\ncp ca.pem server-key.pem server.pem /etc/etcd/ssl scp ca.pem server-key.pem server.pem 192.168.115.132:/etc/etcd/ssl scp ca.pem server-key.pem server.pem 192.168.115.133:/etc/etcd/ssl 安装 etcd 集群 # 在所有机器执行:\ncd /root/kubernetes/resources tar -zxvf /root/kubernetes/resources/etcd-v3.4.9-linux-amd64.tar.gz cp ./etcd-v3.4.9-linux-amd64/etcd ./etcd-v3.4.9-linux-amd64/etcdctl /usr/bin 配置 etcd # 这里开始命令需要分别在 Master 和 Node 机器执行,配置 etcd.conf\nvim /etc/etcd/etcd.conf k8s-master01 写入文件内容如下:\n[Member] ETCD_NAME=\u0026#34;etcd01\u0026#34; ETCD_DATA_DIR=\u0026#34;/var/lib/etcd/default.etcd\u0026#34; ETCD_LISTEN_PEER_URLS=\u0026#34;https://192.168.115.131:2380\u0026#34; ETCD_LISTEN_CLIENT_URLS=\u0026#34;https://192.168.115.131:2379,https://127.0.0.1:2379\u0026#34; [Clustering] ETCD_INITIAL_ADVERTISE_PEER_URLS=\u0026#34;https://192.168.115.131:2380\u0026#34; ETCD_ADVERTISE_CLIENT_URLS=\u0026#34;https://192.168.115.131:2379\u0026#34; ETCD_INITIAL_CLUSTER=\u0026#34;etcd01=https://192.168.115.131:2380,etcd02=https://192.168.115.132:2380,etcd03=https://192.168.115.133:2380\u0026#34; ETCD_INITIAL_CLUSTER_TOKEN=\u0026#34;etcd-cluster\u0026#34; ETCD_INITIAL_CLUSTER_STATE=\u0026#34;new\u0026#34; k8s-node01 写入文件内容如下:\n[Member] ETCD_NAME=\u0026#34;etcd02\u0026#34; ETCD_DATA_DIR=\u0026#34;/var/lib/etcd/default.etcd\u0026#34; ETCD_LISTEN_PEER_URLS=\u0026#34;https://192.168.115.132:2380\u0026#34; ETCD_LISTEN_CLIENT_URLS=\u0026#34;https://192.168.115.132:2379,https://127.0.0.1:2379\u0026#34; [Clustering] ETCD_INITIAL_ADVERTISE_PEER_URLS=\u0026#34;https://192.168.115.132:2380\u0026#34; ETCD_ADVERTISE_CLIENT_URLS=\u0026#34;https://192.168.115.132:2379\u0026#34; ETCD_INITIAL_CLUSTER=\u0026#34;etcd01=https://192.168.115.131:2380,etcd02=https://192.168.115.132:2380,etcd03=https://192.168.115.133:2380\u0026#34; ETCD_INITIAL_CLUSTER_TOKEN=\u0026#34;etcd-cluster\u0026#34; ETCD_INITIAL_CLUSTER_STATE=\u0026#34;new\u0026#34; k8s-node02 写入文件内容如下:\n[Member] ETCD_NAME=\u0026#34;etcd03\u0026#34; ETCD_DATA_DIR=\u0026#34;/var/lib/etcd/default.etcd\u0026#34; ETCD_LISTEN_PEER_URLS=\u0026#34;https://192.168.115.133:2380\u0026#34; ETCD_LISTEN_CLIENT_URLS=\u0026#34;https://192.168.115.133:2379,https://127.0.0.1:2379\u0026#34; [Clustering] ETCD_INITIAL_ADVERTISE_PEER_URLS=\u0026#34;https://192.168.115.133:2380\u0026#34; ETCD_ADVERTISE_CLIENT_URLS=\u0026#34;https://192.168.115.133:2379\u0026#34; ETCD_INITIAL_CLUSTER=\u0026#34;etcd01=https://192.168.115.131:2380,etcd02=https://192.168.115.132:2380,etcd03=https://192.168.115.133:2380\u0026#34; ETCD_INITIAL_CLUSTER_TOKEN=\u0026#34;etcd-cluster\u0026#34; ETCD_INITIAL_CLUSTER_STATE=\u0026#34;new\u0026#34; 这里开始在所有机器执行,设置 etcd 服务配置文件\nmkdir -p /var/lib/etcd vim /usr/lib/systemd/system/etcd.service 执行上行命令,写入文件内容如下:\n[Unit] Description=Etcd Server After=network.target After=network-online.target Wants=network-online.target [Service] Type=notify EnvironmentFile=/etc/etcd/etcd.conf ExecStart=/usr/bin/etcd \\ --cert-file=/etc/etcd/ssl/server.pem \\ --key-file=/etc/etcd/ssl/server-key.pem \\ --peer-cert-file=/etc/etcd/ssl/server.pem \\ --peer-key-file=/etc/etcd/ssl/server-key.pem \\ --trusted-ca-file=/etc/etcd/ssl/ca.pem \\ --peer-trusted-ca-file=/etc/etcd/ssl/ca.pem Restart=on-failure LimitNOFILE=65536 [Install] WantedBy=multi-user.target etcd3.4 版本会自动 EnvironmentFile 文件中的环境变量,不需要再 ExecStart 的命令参数重复设置,否则会报:\u0026quot;xxx\u0026quot; is shadowed by corresponding command-line flag 的错误信息。\n启动 etcd,并且设置开机自动运行 etcd\nsystemctl daemon-reload systemctl start etcd.service systemctl enable etcd.service 检查 etcd 集群的健康状态\netcdctl endpoint health --cacert=/etc/etcd/ssl/ca.pem --cert=/etc/etcd/ssl/server.pem --key=/etc/etcd/ssl/server-key.pem --endpoints=\u0026#34;https://192.168.115.131:2379,https://192.168.115.132:2379,https://192.168.115.133:2379\u0026#34; 输出如下,说明 etcd 集群已经部署成功。\nhttps://192.168.115.133:2379 is healthy: successfully committed proposal: took = 15.805605ms https://192.168.115.132:2379 is healthy: successfully committed proposal: took = 22.127986ms https://192.168.115.131:2379 is healthy: successfully committed proposal: took = 24.829669ms 第二段落部署 etcd 集群愉快结束。\n« 二进制搭建 K8s - 1 机器准备\n» 二进制搭建 K8s - 3 部署 Master\n"},{"id":114,"href":"/kubernetes/binary-build-k8s-03-deploy-master/","title":"Binary Build K8s 03 Deploy Master","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 二进制搭建 K8s - 3 部署 Master\n二进制搭建 K8s - 3 部署 Master # 写在前面 # 记录和分享使用二进制搭建 K8s 集群的详细过程,由于操作比较冗长,大概会分四篇写完:\n机器准备: 部署 etcd 集群: 部署 Master: 部署 Node: 我们已经知道在 K8s 的 Master 上存在着 kube-apiserver、kube-controller-manager、kube-scheduler 三大组件。本篇介绍在 Master 机器安装这些组件,除此之外,如果想在 Master 机器上操作集群,还需要安装 kubectl 工具。\n安装 kubectl # kubernetes 的安装包里已经将 kubectl 包含进去了,部署很简单:\ncd /root/kubernetes/resources/ tar -zxvf ./kubernetes-server-linux-amd64.tar.gz cp kubernetes/server/bin/kubectl /usr/bin kubectl api-versions 制作 kubernetes 证书 # mkdir /root/kubernetes/resources/cert/kubernetes /etc/kubernetes/{ssl,bin} -p cp kubernetes/server/bin/kube-apiserver kubernetes/server/bin/kube-controller-manager kubernetes/server/bin/kube-scheduler /etc/kubernetes/bin cd /root/kubernetes/resources/cert/kubernetes 接下来都在 Master 机器上执行,编辑 ca-config.json\nvim ca-config.json 写入文件内容如下:\n{ \u0026#34;signing\u0026#34;: { \u0026#34;default\u0026#34;: { \u0026#34;expiry\u0026#34;: \u0026#34;87600h\u0026#34; }, \u0026#34;profiles\u0026#34;: { \u0026#34;kubernetes\u0026#34;: { \u0026#34;expiry\u0026#34;: \u0026#34;87600h\u0026#34;, \u0026#34;usages\u0026#34;: [ \u0026#34;signing\u0026#34;, \u0026#34;key encipherment\u0026#34;, \u0026#34;server auth\u0026#34;, \u0026#34;client auth\u0026#34; ] } } } } 编辑 ca-csr.json:\nvim ca-csr.json 写入文件内容如下:\n{ \u0026#34;CN\u0026#34;: \u0026#34;kubernetes\u0026#34;, \u0026#34;key\u0026#34;: { \u0026#34;algo\u0026#34;: \u0026#34;rsa\u0026#34;, \u0026#34;size\u0026#34;: 2048 }, \u0026#34;names\u0026#34;: [ { \u0026#34;C\u0026#34;: \u0026#34;CN\u0026#34;, \u0026#34;L\u0026#34;: \u0026#34;Hunan\u0026#34;, \u0026#34;ST\u0026#34;: \u0026#34;Changsha\u0026#34;, \u0026#34;O\u0026#34;: \u0026#34;kubernetes\u0026#34;, \u0026#34;OU\u0026#34;: \u0026#34;System\u0026#34; } ] } 生成 ca 证书和密钥:\ncfssl gencert -initca ca-csr.json | cfssljson -bare ca 制作 kube-apiserver、kube-proxy、admin 证书,编辑 kube-apiserver-csr.json:\nvim kube-apiserver-csr.json 写入文件内容如下:\n{ \u0026#34;CN\u0026#34;: \u0026#34;kubernetes\u0026#34;, \u0026#34;hosts\u0026#34;: [ \u0026#34;10.0.0.1\u0026#34;, \u0026#34;127.0.0.1\u0026#34;, \u0026#34;kubernetes\u0026#34;, \u0026#34;kubernetes.default\u0026#34;, \u0026#34;kubernetes.default.svc\u0026#34;, \u0026#34;kubernetes.default.svc.cluster\u0026#34;, \u0026#34;kubernetes.default.svc.cluster.local\u0026#34;, \u0026#34;192.168.115.131\u0026#34;, \u0026#34;192.168.115.132\u0026#34;, \u0026#34;192.168.115.133\u0026#34; ], \u0026#34;key\u0026#34;: { \u0026#34;algo\u0026#34;: \u0026#34;rsa\u0026#34;, \u0026#34;size\u0026#34;: 2048 }, \u0026#34;names\u0026#34;: [ { \u0026#34;C\u0026#34;: \u0026#34;CN\u0026#34;, \u0026#34;L\u0026#34;: \u0026#34;Hunan\u0026#34;, \u0026#34;ST\u0026#34;: \u0026#34;Changsha\u0026#34;, \u0026#34;O\u0026#34;: \u0026#34;kubernetes\u0026#34;, \u0026#34;OU\u0026#34;: \u0026#34;System\u0026#34; } ] } 编辑 kube-proxy-csr.json:\nvim kube-proxy-csr.json 写入文件内容如下:\n{ \u0026#34;CN\u0026#34;: \u0026#34;system:kube-proxy\u0026#34;, \u0026#34;hosts\u0026#34;: [], \u0026#34;key\u0026#34;: { \u0026#34;algo\u0026#34;: \u0026#34;rsa\u0026#34;, \u0026#34;size\u0026#34;: 2048 }, \u0026#34;names\u0026#34;: [ { \u0026#34;C\u0026#34;: \u0026#34;CN\u0026#34;, \u0026#34;L\u0026#34;: \u0026#34;Hunan\u0026#34;, \u0026#34;ST\u0026#34;: \u0026#34;Changsha\u0026#34;, \u0026#34;O\u0026#34;: \u0026#34;kubernetes\u0026#34;, \u0026#34;OU\u0026#34;: \u0026#34;System\u0026#34; } ] } 编辑 admin-csr.json:\nvim admin-csr.json 写入文件内容如下:\n{ \u0026#34;CN\u0026#34;: \u0026#34;admin\u0026#34;, \u0026#34;hosts\u0026#34;: [], \u0026#34;key\u0026#34;: { \u0026#34;algo\u0026#34;: \u0026#34;rsa\u0026#34;, \u0026#34;size\u0026#34;: 2048 }, \u0026#34;names\u0026#34;: [ { \u0026#34;C\u0026#34;: \u0026#34;CN\u0026#34;, \u0026#34;L\u0026#34;: \u0026#34;Hunan\u0026#34;, \u0026#34;ST\u0026#34;: \u0026#34;Changsha\u0026#34;, \u0026#34;O\u0026#34;: \u0026#34;system:masters\u0026#34;, \u0026#34;OU\u0026#34;: \u0026#34;System\u0026#34; } ] } 生成证书和密钥\ncfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes kube-apiserver-csr.json | cfssljson -bare kube-apiserver cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes kube-proxy-csr.json | cfssljson -bare kube-proxy cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=kubernetes admin-csr.json | cfssljson -bare admin # 此时目录下生成的文件 ll -rw-r--r--. 1 root root 1001 May 28 00:32 admin.csr -rw-r--r--. 1 root root 282 May 28 00:32 admin-csr.json -rw-------. 1 root root 1679 May 28 00:32 admin-key.pem -rw-r--r--. 1 root root 1407 May 28 00:32 admin.pem -rw-r--r--. 1 root root 294 May 28 00:30 ca-config.json -rw-r--r--. 1 root root 1013 May 28 00:31 ca.csr -rw-r--r--. 1 root root 284 May 28 00:30 ca-csr.json -rw-------. 1 root root 1675 May 28 00:31 ca-key.pem -rw-r--r--. 1 root root 1383 May 28 00:31 ca.pem -rw-r--r--. 1 root root 1273 May 28 00:32 kube-apiserver.csr -rw-r--r--. 1 root root 597 May 28 00:31 kube-apiserver-csr.json -rw-------. 1 root root 1679 May 28 00:32 kube-apiserver-key.pem -rw-r--r--. 1 root root 1655 May 28 00:32 kube-apiserver.pem -rw-r--r--. 1 root root 1009 May 28 00:32 kube-proxy.csr -rw-r--r--. 1 root root 287 May 28 00:31 kube-proxy-csr.json -rw-------. 1 root root 1679 May 28 00:32 kube-proxy-key.pem -rw-r--r--. 1 root root 1411 May 28 00:32 kube-proxy.pem 将 kube-proxy 证书拷贝到 Node:\n前提,需要在 Node 机器创建目录,以下命令在 Node 机器上执行:\nmkdir /etc/kubernetes/ -p 然后再在Master机器执行拷贝操作。\ncp ca.pem ca-key.pem kube-apiserver.pem kube-apiserver-key.pem kube-proxy.pem kube-proxy-key.pem /etc/kubernetes/ssl scp -r /etc/kubernetes/ssl 192.168.115.132:/etc/kubernetes scp -r /etc/kubernetes/ssl 192.168.115.133:/etc/kubernetes 创建 TLSBootstrapping Token # cd /etc/kubernetes head -c 16 /dev/urandom | od -An -t x | tr -d \u0026#39; \u0026#39; # 执行上一步会得到一个 token,例如 d5c5d767b64db39db132b433e9c45fbc,编辑文件 token.csv 时需要 vim token.csv 写入文件内容,替换生成的 token\nd5c5d767b64db39db132b433e9c45fbc,kubelet-bootstrap,10001,\u0026#34;system:node-bootstrapper\u0026#34; 安装 kube-apiserver # 准备 kube-apiserver 配置文件\nvim apiserver 执行上行命令,写入文件内容如下:\nKUBE_API_ARGS=\u0026#34;--logtostderr=false \\ --v=2 \\ --log-dir=/var/log/kubernetes \\ --etcd-servers=https://192.168.115.131:2379,https://192.168.115.132:2379,https://192.168.115.133:2379 \\ --bind-address=192.168.115.131 \\ --secure-port=6443 \\ --advertise-address=192.168.115.131 \\ --allow-privileged=true \\ --service-cluster-ip-range=10.0.0.0/24 \\ --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,ResourceQuota,NodeRestriction \\ --authorization-mode=RBAC,Node \\ --enable-bootstrap-token-auth=true \\ --token-auth-file=/etc/kubernetes/token.csv \\ --service-node-port-range=30000-32767 \\ --kubelet-client-certificate=/etc/kubernetes/ssl/kube-apiserver.pem \\ --kubelet-client-key=/etc/kubernetes/ssl/kube-apiserver-key.pem \\ --tls-cert-file=/etc/kubernetes/ssl/kube-apiserver.pem \\ --tls-private-key-file=/etc/kubernetes/ssl/kube-apiserver-key.pem \\ --client-ca-file=/etc/kubernetes/ssl/ca.pem \\ --service-account-key-file=/etc/kubernetes/ssl/ca-key.pem \\ --etcd-cafile=/etc/etcd/ssl/ca.pem \\ --etcd-certfile=/etc/etcd/ssl/server.pem \\ --etcd-keyfile=/etc/etcd/ssl/server-key.pem \\ --audit-log-maxage=30 \\ --audit-log-maxbackup=3 \\ --audit-log-maxsize=100 \\ --audit-log-path=/var/logs/kubernetes/k8s-audit.log\u0026#34; 准备 kube-apiserver 服务配置文件\nvim /usr/lib/systemd/system/kube-apiserver.service 执行上行命令,写入文件内容如下:\n[Unit] Description=Kubernetes API Server Documentation=https://github.com/GoogleCloudPlatform/kubernetes After=etcd.service Wants=etcd.service [Service] Type=notify EnvironmentFile=/etc/kubernetes/apiserver ExecStart=/etc/kubernetes/bin/kube-apiserver $KUBE_API_ARGS Restart=on-failure LimitNOFILE=65536 [Install] WantedBy=multi-user.target 启动 kube-apiserver:\nsystemctl daemon-reload systemctl start kube-apiserver systemctl enable kube-apiserver systemctl status kube-apiserver 安装 kube-controller-manager # 准备 kube-controller-manger 配置文件\nvim controller-manager 执行上行命令,写入文件内容如下:\nKUBE_CONTROLLER_MANAGER_ARGS=\u0026#34;--logtostderr=false \\ --v=2 \\ --log-dir=/var/log/kubernetes \\ --leader-elect=true \\ --master=127.0.0.1:8080 \\ --bind-address=127.0.0.1 \\ --allocate-node-cidrs=true \\ --cluster-cidr=10.244.0.0/16 \\ --service-cluster-ip-range=10.0.0.0/24 \\ --cluster-signing-cert-file=/etc/kubernetes/ssl/ca.pem \\ --cluster-signing-key-file=/etc/kubernetes/ssl/ca-key.pem \\ --root-ca-file=/etc/kubernetes/ssl/ca.pem \\ --service-account-private-key-file=/etc/kubernetes/ssl/ca-key.pem \\ --experimental-cluster-signing-duration=87600h0m0s\u0026#34; 准备 kube-controller-manger 服务配置文件\nvim /usr/lib/systemd/system/kube-controller-manager.service 执行上行命令,写入文件内容如下:\n[Unit] Description=Kubernetes Controller Manager Documentation=https://github.com/GoogleCloudPlatform/kubernetes After=kube-apiserver.service Requires=kube-apiserver.service [Service] EnvironmentFile=/etc/kubernetes/controller-manager ExecStart=/etc/kubernetes/bin/kube-controller-manager $KUBE_CONTROLLER_MANAGER_ARGS Restart=on-failure LimitNOFILE=65536 [Install] WantedBy=multi-user.target 启动kube-controller-manager:\nsystemctl daemon-reload systemctl start kube-controller-manager systemctl enable kube-controller-manager systemctl status kube-controller-manager 安装 kube-scheduler # 准备 kube-scheduler 配置文件\nvim scheduler 执行上行命令,写入文件内容如下:\nKUBE_SCHEDULER_ARGS=\u0026#34;--logtostderr=false \\ --v=2 \\ --log-dir=/var/log/kubernetes \\ --master=127.0.0.1:8080 \\ --leader-elect \\ --bind-address=127.0.0.1\u0026#34; 准备 kube-scheduler 服务配置文件\nvim /usr/lib/systemd/system/kube-scheduler.service 执行上行命令,写入文件内容如下:\n[Unit] Description=Kubernetes Scheduler Documentation=https://github.com/GoogleCloudPlatform/kubernetes After=kube-apiserver.service Requires=kube-apiserver.service [Service] EnvironmentFile=/etc/kubernetes/scheduler ExecStart=/etc/kubernetes/bin/kube-scheduler $KUBE_SCHEDULER_ARGS Restart=on-failure LimitNOFILE=65536 [Install] WantedBy=multi-user.target 启动 kube-scheduler:\nsystemctl daemon-reload systemctl start kube-scheduler systemctl enable kube-scheduler systemctl status kube-scheduler kubelet-bootstrap 授权 # kubectl create clusterrolebinding kubelet-bootstrap \\ --clusterrole=system:node-bootstrapper \\ --user=kubelet-bootstrap 查看 Master 状态\nkubectl get cs 如果 Master 部署成功,应该输出:\nNAME STATUS MESSAGE ERROR scheduler Healthy ok controller-manager Healthy ok etcd-2 Healthy {\u0026#34;health\u0026#34;:\u0026#34;true\u0026#34;} etcd-1 Healthy {\u0026#34;health\u0026#34;:\u0026#34;true\u0026#34;} etcd-0 Healthy {\u0026#34;health\u0026#34;:\u0026#34;true\u0026#34;} apiserver 授权 kubelet # 准备 apiserver-to-kubelet-rbac.yaml 文件\ncd /root/kubernetes/resources vim apiserver-to-kubelet-rbac.yaml 执行上行命令,写入文件内容如下:\napiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: annotations: rbac.authorization.kubernetes.io/autoupdate: \u0026#34;true\u0026#34; labels: kubernetes.io/bootstrapping: rbac-defaults name: system:kube-apiserver-to-kubelet rules: - apiGroups: - \u0026#34;\u0026#34; resources: - nodes/proxy - nodes/stats - nodes/log - nodes/spec - nodes/metrics - pods/log verbs: - \u0026#34;*\u0026#34; --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: system:kube-apiserver namespace: \u0026#34;\u0026#34; roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:kube-apiserver-to-kubelet subjects: - apiGroup: rbac.authorization.k8s.io kind: User name: kubernetes # This role allows full access to the kubelet API apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: kubelet-api-admin labels: addonmanager.kubernetes.io/mode: Reconcile rules: - apiGroups: - \u0026#34;\u0026#34; resources: - nodes/proxy - nodes/log - nodes/stats - nodes/metrics - nodes/spec verbs: - \u0026#34;*\u0026#34; # This binding gives the kube-apiserver user full access to the kubelet API --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: kube-apiserver-kubelet-api-admin labels: addonmanager.kubernetes.io/mode: Reconcile roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: kubelet-api-admin subjects: - apiGroup: rbac.authorization.k8s.io kind: User name: kube-apiserver 执行以下命令:\nkubectl apply -f apiserver-to-kubelet-rbac.yaml 第三段落部署 Master顺利结束。\n« 二进制搭建 K8s - 2 部署 etcd 集群\n» 二进制搭建 K8s - 4 部署 Node\n"},{"id":115,"href":"/kubernetes/binary-build-k8s-04-deploy-worker/","title":"Binary Build K8s 04 Deploy Worker","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 二进制搭建 K8s - 4 部署 Node\n二进制搭建 K8s - 4 部署 Node # 写在前面 # 记录和分享使用二进制搭建 K8s 集群的详细过程,由于操作比较冗长,大概会分四篇写完:\n机器准备: 部署 etcd 集群: 部署 Master: 部署 Node: K8s 的 Node 上需要运行 kubelet 和 kube-proxy。本篇介绍在 Node 机器安装这两个组件,除此之外,安装通信需要的 cni 插件。\n本篇的执行命令需要在准备的两台Node机器上执行。\n安装 docker # 可以参照官网: https://docs.docker.com/engine/install/\n# 卸载老版本或重装 docker 时执行第一行 yum remove docker \\ docker-client \\ docker-client-latest \\ docker-common \\ docker-latest \\ docker-latest-logrotate \\ docker-logrotate \\ docker-engine -y # 安装 docker yum install -y yum-utils yum-config-manager \\ --add-repo \\ https://download.docker.com/linux/centos/docker-ce.repo yum install docker-ce docker-ce-cli containerd.io -y # 查看 Docker 版本 docker version 启动 Docker\nsystemctl enable docker systemctl start docker 安装 kubelet # cd /root/kubernetes/resources tar -zxvf ./kubernetes-node-linux-amd64.tar.gz mkdir /etc/kubernetes/{ssl,bin} -p cp kubernetes/node/bin/kubelet ./kubernetes/node/bin/kube-proxy /etc/kubernetes/bin cd /etc/kubernetes 准备 kubelet 配置文件\nvim kubelet 执行上行命令,在 k8s-node01 写入文件内容如下:\nKUBELET_ARGS=\u0026#34;--logtostderr=false \\ --v=2 \\ --log-dir=/var/log/kubernetes \\ --enable-server=true \\ --kubeconfig=/etc/kubernetes/kubelet.kubeconfig \\ --hostname-override=k8s-node01 \\ --network-plugin=cni \\ --bootstrap-kubeconfig=/etc/kubernetes/bootstrap.kubeconfig \\ --config=/etc/kubernetes/kubelet-config.yml \\ --cert-dir=/etc/kubernetes/ssl\u0026#34; 在 k8s-node02 写入文件内容如下:\nKUBELET_ARGS=\u0026#34;--logtostderr=false \\ --v=2 \\ --log-dir=/var/log/kubernetes \\ --enable-server=true \\ --kubeconfig=/etc/kubernetes/kubelet.kubeconfig \\ --hostname-override=k8s-node02 \\ --network-plugin=cni \\ --bootstrap-kubeconfig=/etc/kubernetes/bootstrap.kubeconfig \\ --config=/etc/kubernetes/kubelet-config.yml \\ --cert-dir=/etc/kubernetes/ssl\u0026#34; 准备 bootstrap.kubeconfig 文件\nvim /etc/kubernetes/bootstrap.kubeconfig 执行上行命令,写入文件内容如下:\napiVersion: v1 clusters: - cluster: certificate-authority: /etc/kubernetes/ssl/ca.pem server: https://192.168.115.131:6443 name: kubernetes contexts: - context: cluster: kubernetes user: kubelet-bootstrap name: default current-context: default kind: Config preferences: {} users: - name: kubelet-bootstrap user: token: d5c5d767b64db39db132b433e9c45fbc 注意:token 的值需要替换为 master 生成的 token.csv 中所用的 token。\n准备 kubelet-config.yml 文件\nvim kubelet-config.yml 执行上行命令,写入文件内容如下:\nkind: KubeletConfiguration apiVersion: kubelet.config.k8s.io/v1beta1 address: 0.0.0.0 port: 10250 readOnlyPort: 10255 cgroupDriver: cgroupfs clusterDNS: - 10.0.0.2 clusterDomain: cluster.local failSwapOn: false authentication: anonymous: enabled: false webhook: cacheTTL: 2m0s enabled: true x509: clientCAFile: /etc/kubernetes/ssl/ca.pem authorization: mode: Webhook webhook: cacheAuthorizedTTL: 5m0s cacheUnauthorizedTTL: 30s evictionHard: imagefs.available: 15% memory.available: 100Mi nodefs.available: 10% nodefs.inodesFree: 5% maxOpenFiles: 1000000 maxPods: 110 准备 kubelet.kubeconfig 文件\nvim kubelet.kubeconfig 执行上行命令,写入文件内容如下:\nkubelet.kubeconfig apiVersion: v1 clusters: - cluster: certificate-authority: /etc/kubernetes/ssl/ca.pem server: https://192.168.115.131:6443 name: kubernetes contexts: - context: cluster: kubernetes namespace: default user: default-auth name: default-context current-context: default-context kind: Config preferences: {} users: - name: default-auth user: client-certificate: /etc/kubernetes/ssl/kubelet-client-current.pem client-key: /etc/kubernetes/ssl/kubelet-client-current.pem 准备kubelet服务配置文件\nvim /usr/lib/systemd/system/kubelet.service 执行上行命令,写入文件内容如下:\n[Unit] Description=Kubelet Documentation=https://github.com/GoogleCloudPlatform/kubernetes After=docker.service Requires=docker.service [Service] EnvironmentFile=/etc/kubernetes/kubelet ExecStart=/etc/kubernetes/bin/kubelet $KUBELET_ARGS Restart=on-failure [Install] WantedBy=multi-user.target 启动 kubelet:\nsystemctl daemon-reload systemctl start kubelet systemctl enable kubelet systemctl status kubelet 给 Node 颁发证书,在 Master 上执行:\nkubectl get csr # 输出如下 NAME AGE SIGNERNAME REQUESTOR CONDITION node-csr-a-BmW9xMglOXlUdwBjD2QQphXLdu4iwtamEIIbhJKcY 10m kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending node-csr-zDDrVyKH7ug8fTUcDjdvDgh-f9rVCyoHuLMGaWbykAQ 10m kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Pending 得到证书的 NAME,给其 Approve:\nkubectl certificate approve node-csr-a-BmW9xMglOXlUdwBjD2QQphXLdu4iwtamEIIbhJKcY kubectl certificate approve node-csr-zDDrVyKH7ug8fTUcDjdvDgh-f9rVCyoHuLMGaWbykAQ 再次查看证书,证书的 CONDITION 就会更新了\nkubectl get csr # 输出如下 NAME AGE SIGNERNAME REQUESTOR CONDITION node-csr-a-BmW9xMglOXlUdwBjD2QQphXLdu4iwtamEIIbhJKcY 10m kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Approved,Issued node-csr-zDDrVyKH7ug8fTUcDjdvDgh-f9rVCyoHuLMGaWbykAQ 10m kubernetes.io/kube-apiserver-client-kubelet kubelet-bootstrap Approved,Issued 接下来使用查看 Node 的命令,应该可以获取到 Node 信息:\nkubectl get node # 输出如下 NAME STATUS ROLES AGE VERSION k8s-node01 NotReady \u0026lt;none\u0026gt; 50s v1.18.3 k8s-node02 NotReady \u0026lt;none\u0026gt; 56s v1.18.3 安装 kube-proxy # 准备 kube-proxy 配置文件\nvim kube-proxy 执行上行命令,写入文件内容如下:\nKUBE_PROXY_ARGS=\u0026#34;--logtostderr=false \\ --v=2 \\ --log-dir=/var/log/kubernetes \\ --config=/etc/kubernetes/kube-proxy-config.yml\u0026#34; 准备 kube-proxy-config.yml 文件\nvim /etc/kubernetes/kube-proxy-config.yml 执行上行命令,在 k8s-node01 写入文件内容如下:\nkind: KubeProxyConfiguration apiVersion: kubeproxy.config.k8s.io/v1alpha1 address: 0.0.0.0 metricsBindAddress: 0.0.0.0:10249 iclientConnection: kubeconfig: /etc/kubernetes/kube-proxy.kubeconfig hostnameOverride: k8s-node01 clusterCIDR: 10.0.0.0/24 mode: ipvs ipvs: scheduler: \u0026#34;rr\u0026#34; iptables: masqueradeAll: true 在 k8s-node02 写入文件内容如下:\nkind: KubeProxyConfiguration apiVersion: kubeproxy.config.k8s.io/v1alpha1 address: 0.0.0.0 metricsBindAddress: 0.0.0.0:10249 clientConnection: kubeconfig: /etc/kubernetes/kube-proxy.kubeconfig hostnameOverride: k8s-node02 clusterCIDR: 10.0.0.0/24 mode: ipvs ipvs: scheduler: \u0026#34;rr\u0026#34; iptables: masqueradeAll: true 准备 kube-proxy.kubeconfig 文件\nvim /etc/kubernetes/kube-proxy.kubeconfig 执行上行命令,写入文件内容如下:\napiVersion: v1 clusters: - cluster: certificate-authority: /etc/kubernetes/ssl/ca.pem server: https://192.168.115.131:6443 name: kubernetes contexts: - context: cluster: kubernetes user: kube-proxy name: default current-context: default kind: Config preferences: {} users: - name: kube-proxy user: client-certificate: /etc/kubernetes/ssl/kube-proxy.pem client-key: /etc/kubernetes/ssl/kube-proxy-key.pem 准备 kube-proxy 服务配置文件\nvim /usr/lib/systemd/system/kube-proxy.service 执行上行命令,写入文件内容如下:\n[Unit] Description=Kube-Proxy Documentation=https://github.com/GoogleCloudPlatform/kubernetes After=network.target Requires=network.target [Service] EnvironmentFile=/etc/kubernetes/kube-proxy ExecStart=/etc/kubernetes/bin/kube-proxy $KUBE_PROXY_ARGS Restart=on-failure [Install] WantedBy=multi-user.target 启动 kubelet:\nsystemctl daemon-reload systemctl start kube-proxy systemctl enable kube-proxy systemctl status kube-proxy 部署 cni 网络插件 # cd /root/kubernetes/resources mkdir -p /opt/cni/bin /etc/cni/net.d tar -zxvf cni-plugins-linux-amd64-v0.8.6.tgz -C /opt/cni/bin 部署 Flannel 集群网络 # 需要在 Master 机器上执行\ncd /root/kubernetes/resources kubectl apply -f kube-flannel.yml 创建角色绑定\nkubectl create clusterrolebinding kube-apiserver:kubelet-apis --clusterrole=system:kubelet-api-admin --user kubernetes K8s 集群测试 # 部署一个 nginx 的 deployment:\nkubectl create deployment nginx --image=nginx # 在等待几秒后,获取 deployment kubectl get deployment ifconfig cni0 kubectl expose deployment nginx --port=80 --type=NodePort kubectl get svc 可以看到 nginx 已经启动成功。\nNAME READY UP-TO-DATE AVAILABLE AGE nginx 1/1 1 1 7m7s 注意:如果启动失败,可能是由于网络原因拉取镜像失败导致。可以通过 kubectl describe pod \u0026lt;pod-name\u0026gt; 查看。\n使用 service 暴露 K8s 集群内部 Pod 服务:\nkubectl expose deployment nginx --port=80 --type=NodePort # 获取 service kubectl get svc 可以看到,service 将 nginx 的服务转发到了 31839 端口\nNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.0.0.1 \u0026lt;none\u0026gt; 443/TCP 10h nginx NodePort 10.0.0.101 \u0026lt;none\u0026gt; 80:31839/TCP 10s 此时,我们在 Node 机器上使用该端口访问 nginx,可以看到成功访问。\n[root@k8s-node01]# curl 192.168.115.132:31839 \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html\u0026gt; \u0026lt;head\u0026gt; \u0026lt;title\u0026gt;Welcome to nginx!\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;h1\u0026gt;Welcome to nginx!\u0026lt;/h1\u0026gt; \u0026lt;p\u0026gt;If you see this page, the nginx web server is successfully installed and working. Further configuration is required.\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;For online documentation and support please refer to \u0026lt;a href=\u0026#34;http://nginx.org/\u0026#34;\u0026gt;nginx.org\u0026lt;/a\u0026gt;.\u0026lt;br/\u0026gt; Commercial support is available at \u0026lt;a href=\u0026#34;http://nginx.com/\u0026#34;\u0026gt;nginx.com\u0026lt;/a\u0026gt;.\u0026lt;/p\u0026gt; \u0026lt;p\u0026gt;\u0026lt;em\u0026gt;Thank you for using nginx.\u0026lt;/em\u0026gt;\u0026lt;/p\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; 好了,至此第四段落部署 Node也顺利结束。\n结束语 # 在使用二进制搭建 K8s 集群的过程中,搭建的过程参考了很多园友的博客。由于我是使用最新的 K8s、etcd 版本搭建的,遇到了很多的问题,但没有关系,好事多磨。\n在遇到问题的时候,几乎都是通过查看 K8s 中组件的运行状态和日志来寻找问题根源和解决方案的。\n大部分问题都是出在配置方面,或是文件路径配置问题,或是新版本的配置不兼容问题。\n« 二进制搭建 K8s - 3 部署 Master\n» Kubernetes 0-1 尝试理解云原生\n"},{"id":116,"href":"/kubernetes/cloud-native-understood/","title":"Cloud Native Understood","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 尝试理解云原生\nKubernetes 0-1 尝试理解云原生 # 最初的云原生定义:\n应用容器化 面向微服务架构 应用支持容器编排调度 重新定义云原生:\n云原生技术有利于各组织在公有云、私有云和混合云等新型动态环境中,构建和运行可弹性扩展的应用。云原生的代表技术包括容器、服务网格、微服务、不可变基础设施和声明式API。\n这些技术能够构建容错性好、易于管理和便于观察的松耦合系统。结合可靠的自动化手段,云原生技术使工程师能够轻松地对系统作出频繁和可预测的重大变更。\n云原生本身不能称为是一种架构,它首先是一种基础设施,运行在其上的应用称作云原生应用,只有符合云原生设计哲学的应用架构才叫云原生应用架构。\n« 二进制搭建 K8s - 4 部署 Node\n» 集群联邦\n"},{"id":117,"href":"/kubernetes/cluster-federation/","title":"Cluster Federation","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 集群联邦\n集群联邦 # 云服务提供商的集群联邦是一种将多个独立的 Kubernetes 集群组合在一起的方法。这种方法允许用户在多个集群之间共享资源,例如 Pods、Services、Deployments 等。集群联邦的目标是在多个集群上引入新的控制面板,提供一个统一的视图,使用户可以在多个集群之间无缝地部署和管理应用程序。\n概念 # 数据中心:Region,是一个物理位置,包含多个可用性区域。 可用性区域:Availability Zone(AZ),是一个独立的数据中心,包含 N 多服务器节点。 管理集群:或者宿主集群,是一个集群联邦的核心,用于管理多个工作集群。 联邦集群:或者工作集群,是一个普通的 Kubernetes 集群,用于部署工作负载。 集群联邦需要解决的问题 # 跨集群服务发现:连通多个集群,使得服务可以在多个集群之间发现,让请求跨越集群边界。 跨集群调度:将负载调度到多个集群,保证服务的稳定性以及可用性。 集群联邦开源项目 # Kubefed # 项目地址\n之前由 Kubernetes 官方多集群兴趣小组开发,目前已经停止维护。\n架构原理:\n将联邦资源(FederationResource)从管理集群同步到工作集群。\n这其中通过三个概念来实现:\nTemplate:定义了联邦资源的模板,用于指定联邦资源的属性 Placement:定义了联邦集群资源的部署位置,用于指定联邦资源的部署位置。 Overrides:定义了联邦集群资源的覆盖规则,用于覆盖联邦资源的属性。 kubefed 为所有的 Kubernetes 原生资源提供了对应的联邦资源,例如 FederatedService、FederatedDeployment 等。\n联邦资源中定义了原生资源的 Template、又通过 Overrides 定义了资源同步到不同的工作集群时需要做的变更,例如:\nkind: FederatedDeployment ... spec: ... overrides: # Apply overrides to cluster1 - clusterName: cluster1 clusterOverrides: # Set the replicas field to 5 - path: \u0026#34;/spec/replicas\u0026#34; value: 5 # Set the image of the first container - path: \u0026#34;/spec/template/spec/containers/0/image\u0026#34; value: \u0026#34;nginx:1.17.0-alpine\u0026#34; # Ensure the annotation \u0026#34;foo: bar\u0026#34; exists - path: \u0026#34;/metadata/annotations\u0026#34; op: \u0026#34;add\u0026#34; value: foo: bar # Ensure an annotation with key \u0026#34;foo\u0026#34; does not exist - path: \u0026#34;/metadata/annotations/foo\u0026#34; op: \u0026#34;remove\u0026#34; # Adds an argument `-q` at index 0 of the args list # this will obviously shift the existing arguments, if any - path: \u0026#34;/spec/template/spec/containers/0/args/0\u0026#34; op: \u0026#34;add\u0026#34; value: \u0026#34;-q\u0026#34; Karmada # 项目地址 管理集群包含三个主要组件:\nAPIServer ControllerManager:将联邦资源同步到工作集群并管理联邦资源的生命周期。 Scheduler Karmada 将资源模板转换成成员集群的资源需要经过以下几个步骤:\nDeployment、Service、ConfigMap 等资源模板经过 PropagationPolicy 生成一组 ResourceBinding,每个 ResourceBinding 都对应特定的成员集群; ResourceBinding 根据 OverridePolicy 改变一些资源以适应的不同成员集群,例如:集群名等参数,这些资源定义会存储在 Work 对象中; Work 对象中存储的资源定义会被提交到成员集群中,成员集群中的 Controller Manager 等控制面板组件会负责这些资源的处理,例如:根据 Deployment 创建 Pod 等。 如下示例:\n# propagationpolicy.yaml apiVersion: policy.karmada.io/v1alpha1 kind: PropagationPolicy metadata: name: example-policy spec: resourceSelectors: - apiVersion: apps/v1 kind: Deployment name: nginx placement: clusterAffinity: clusterNames: - member1 # overridepolicy.yaml apiVersion: policy.karmada.io/v1alpha1 kind: OverridePolicy metadata: name: example-override namespace: default spec: resourceSelectors: - apiVersion: apps/v1 kind: Deployment name: nginx overrideRules: - targetCluster: clusterNames: - member1 overriders: plaintext: - path: \u0026#34;/metadata/annotations\u0026#34; operator: add value: foo: bar « Kubernetes 0-1 尝试理解云原生\n» 了解 ConfigMap\n"},{"id":118,"href":"/kubernetes/configmap-understood/","title":"Configmap Understood","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 了解 ConfigMap\n了解 ConfigMap # 几乎所有的应用都需要配置信息,在 K8s 部署应用,最佳实践是将应用的配置信息(环境变量或者配置文件)和程序本身分离,这样配置信息的更新和复用都可以更简单,也使得程序更加灵活。\nKubernetes 允许将配置选项分离到单独的资源对象 ConfigMap 中,本质上是一个键值对映射,值可以是一个短 string 串,也可以是一个完整的配置文件。\n本篇主要介绍 ConfigMap 资源的创建和使用。\nConfigMap 的创建 # 可以直接通过 kubectl create configmap 命令创建,也可以先编写 configmap 的 yaml 文件再使用kubectl apply -f \u0026lt;filename\u0026gt;创建,推荐使用后者。\n单行命令创建 ConfigMap # 创建一个键值对的 ConfigMap: kubectl create configmap first-config --from-literal=user=admin 创建完成之后,使用 kubectl describe configmap first-config 查看,可以看到这个 configmap 的键值内容。\n可以使用多组 --from-literal=\u0026lt;key\u0026gt;=\u0026lt;value\u0026gt; 参数,在 configmap 中定义多组键值对。\n创建一个文件内容的 ConfigMap 假如我当前有一个配置文件 app.json,文件内容如下:\n{ \u0026#34;App\u0026#34;: \u0026#34;MyApp\u0026#34;, \u0026#34;Version\u0026#34;: \u0026#34;v1.0\u0026#34; } 使用以下命令创建 ConfigMap:\nkubectl create configmap second-config --from-file=app.json 创建完成之后,使用 kubectl describe configmap second-config 查看 configmap 的键值内容:\n默认使用文件名称 app.json 作为键值对的 key,也可以通过 --from-file=app_config=app.json 指定key为 app_config;\n可以使用多组 --from-file=\u0026lt;key\u0026gt;=\u0026lt;filename\u0026gt; 参数,在 configmap 中定义多组文件;\n--from-file= 后面可以直接跟某个文件路径,这样会将目录下的所有文件引入到 ConfigMap;\n--from-literal 和 --from-file 可以共同使用,键值合并。\n删除创建的 first-config 和 second-config:\nkubectl delete configmap first-config kubectl delete configmap second-config 基于资源清单文件创建 ConfigMap # 创建一个键值对的 ConfigMap: 首先定义 ConfigMap 的资源文件 first-config.yaml,定义如下:\nvim first-config.yaml apiVersion: v1 kind: ConfigMap metadata: name: first-config data: user: \u0026#34;admin\u0026#34; 使用 kubectl apply 命令创建 ConfigMap 资源:\nkubectl apply -f first-config.yaml 创建完成之后,使用 kubectl describe configmap first-config 查看,可以看到这个 configmap 的键值内容,结果与上文第一次创建的 first-configmap 是一致的。\n可以在 data 下定义多组键值对。\n创建一个文件内容的 ConfigMap 首先定义 ConfigMap 的资源文件 second-config.yaml,定义如下:\nvim second-config.yaml apiVersion: v1 kind: ConfigMap metadata: name: second-config data: app.json: | { \u0026#34;App\u0026#34;: \u0026#34;MyApp\u0026#34;, \u0026#34;Version\u0026#34;: \u0026#34;v1.0\u0026#34; } 使用 kubectl apply 命令创建 ConfigMap 资源:\nkubectl apply -f second-config.yaml 创建完成之后,使用 kubectl describe configmap second-config 查看,可以看到这个 configmap 的键值内容,结果与上文第一次创建的 second-configmap 是一致的。\n可以在 data 下定义多组文件,也可以和键值对一起定义;\n其本质也是键值对,只是 value 为多行格式的文本内容;\n对 value 有格式要求,”|“接换行并且文件内容的每行按照 key 都往后退格。\n删除创建的 first-config 和 second-config:\nkubectl delete configmap first-config kubectl delete configmap second-config ConfigMap 的使用 # 我们创建了 ConfigMap 后,我们的 Pod 资源就可以利用了。主要有两种方式使用 ConfigMap:\n使用 ConfigMap 作为容器的环境变量 使用 ConfigMap 作为 Volume 向容器提供文件 使用 ConfigMap 作为容器的环境变量 # 假如有一个名为 first-config的 ConfigMap,里面包含了一个键为 user,我想将这个 ConfigMap 中 user 键用到我的环境变量 USER_NAME 中,可以使用如下方式:\n... env: - name: USER_NAME valueFrom: configMapKeyRef: name: first-config key: user ... 如果有一个名为 second-config ConfigMap 中包含多个键如 HOST,PORT,USER_NAME,PASSWORD,我想将这个 ConfigMap 中所有的键都用到我的环境变量中,可以使用如下方式:\n... spec: container: - image: \u0026lt;some-image\u0026gt; envFrom: - prefix: DB_ configMapRef: name: second-config ... 容器将会生成 DB_HOST,DB_PORT,DB_USER_NAME,DB_PASSWORD 四个环境变量,prefix 也可以不配置,则直接使用 ConfigMap 的键。\n注意:\nconfigMapRef 与上面 configMapKeyRef 的区别; 如果ConfigMap中有一个为 USER-NAME 键,那么将不会生成 DB_USER-NAME 的环境变量,因为 DB_USER-NAME 不是一个合法的环境变量名称。 使用 ConfigMap 作为 Volume 向容器提供文件 # 从这里开始,结合实际演练可能效果会好一点。\n我仍然使用 poneding/mockapi:v1 镜像运行模拟应用程序,程序的代码在这: https://github.com/poneding/for-docker\n这个应用程序根目录下存在 mysettings/app.json 和 mysettings/secret.json 两个配置文件,需要提前说明的是这个镜像中已经存在了这两个文件,并且文件内容如下:\nmysettings/app.json:\n{ \u0026#34;App\u0026#34;: \u0026#34;Hello Web\u0026#34;, \u0026#34;Version\u0026#34;: \u0026#34;v1\u0026#34; } mysettings/secret.json:\n{ \u0026#34;UserName\u0026#34;: \u0026#34;admin\u0026#34;, \u0026#34;Password\u0026#34;: \u0026#34;123456\u0026#34; } 我现在将这两个配置文件放在我的 ConfigMap 中,并且内容与镜像中略微区别,用于区分程序读取配置源自 ConfigMap,然后使用 Volume 的形式将文件挂载到应用中去。\n首先,定义 ConfigMap 文件如下:\nvim mockapi-mysettings.yaml apiVersion: v1 kind: ConfigMap metadata: name: mockapi-mysettings data: app.json: | { \u0026#34;App\u0026#34;:\u0026#34;Hello Web [From ConfigMap]\u0026#34;, \u0026#34;Version\u0026#34;:\u0026#34;v1 [From ConfigMap]\u0026#34; } secret.json: | { \u0026#34;UserName\u0026#34;:\u0026#34;admin [From ConfigMap]\u0026#34;, \u0026#34;Password\u0026#34;:\u0026#34;123456 [From ConfigMap]\u0026#34; } 定义 Pod 文件如下:\nvim mockapi-pod.yaml apiVersion: v1 kind: Pod metadata: name: mockapi spec: containers: - name: mockapi image: poneding/mockapi:v1 ports: - containerPort: 80 volumeMounts: - mountPath: /app/mysettings name: mockapi-mysettings volumes: - name: mockapi-mysettings configMap: name: mockapi-mysettings Apply 资源:\nkubectl apply -f mockapi-mysettings.yaml kubectl apply -f mockapi-pod.yaml 等待 mockapi 的 Pod 起来之后,我们调用 http://\u0026lt;pod-ip\u0026gt;/configuration/mysettings 查看挂载的配置文件是否生效:\nmockapi 应用访问 /configuration/mysettings 会获取 app.json 和 secret.json 里面的总共四个配置项,然后输出到客户端。\n可以看到,已经获取到了 ConfigMap 里面的配置,进入容器也可以看到里面的问题件内容:\n修改 ConfigMap 自动更新挂载文件:\n现在,如果我想要修改我的配置,比如我的密码修改成了 abcdefg,我不用重新启动 Pod,我只需要修改 ConfigMap 文件:\napiVersion: v1 kind: ConfigMap metadata: name: mockapi-mysettings data: app.json: | { \u0026#34;App\u0026#34;:\u0026#34;Hello Web [From ConfigMap]\u0026#34;, \u0026#34;Version\u0026#34;:\u0026#34;v1 [From ConfigMap]\u0026#34; } secret.json: | { \u0026#34;UserName\u0026#34;:\u0026#34;admin [From ConfigMap]\u0026#34;, \u0026#34;Password\u0026#34;:\u0026#34;abcdefg [From ConfigMap]\u0026#34; } 然后重新 Apply 资源:\nkubectl apply -f mockapi-mysettings.yaml 配置文件的更新需要 1-2 分钟的时间,我们可以连续观察文件的更新情况:\n可以看到,过段时间后配置文件更新了。\n这里遇到了一个小问题,继续访问 /configuration/mysettings,应用程序并没有热更新,但是我使用 hostPath 的 Volume 形式挂载时,修改配置文件是可以热更新的,希望有缘人能给我解答吧。\n多文件目录下挂载单个文件:\n在实际的应用中,可能 secret.json 的变动更为频繁,而 app.json 文件几乎不会变动,我现在只想使用 ConfigMap 传递 secret.json 文件,而不再传递 app.json,这时,修改 ConfigMap 文件如下:\napiVersion: v1 kind: ConfigMap metadata: name: mockapi-mysettings data: secret.json: | { \u0026#34;UserName\u0026#34;:\u0026#34;admin [From ConfigMap]\u0026#34;, \u0026#34;Password\u0026#34;:\u0026#34;123456 [From ConfigMap]\u0026#34; } 然后,重新 Apply 资源:\nkubectl apply -f mockapi-mysettings.yaml 这时候,我们继续访问 /configuration/mysettings:\n很遗憾的发现,我们的 app.json 的配置项都失效了,我们接着去容器中一探究竟:\n原来,app.json 文件已经不存在了。\n大概推测一下可以知道,**如果使用 ConfigMap 挂载到容器的一个目录,那么该目录会被 ConfigMap 所覆盖。**那么如果只挂载单个目录呢?使用 subPath!\n修改 Pod 文件如下:\napiVersion: v1 kind: Pod metadata: name: mockapi spec: containers: - name: mockapi image: poneding/mockapi:v1 ports: - containerPort: 80 volumeMounts: - mountPath: /app/mysettings/secret.json name: mockapi-mysettings subPath: secret.json volumes: - name: mockapi-mysettings configMap: name: mockapi-mysettings mountPath 是容器中目标文件路径;\nsubPath 是ConfgMap文件的 Key 值。\n然后,重新 Apply 资源:\nkubectl delete -f mockapi-pod.yaml kubectl apply -f mockapi-pod.yaml 等待Pod运行后,我们访问 /configuration/mysettings,并查看 mysettings 目录下文件:\n可以看到成功的挂载 secret.json 文件而没有挂载 app.json 文件,[From ConfigMap] 就是证明。\n需要额外注意的是,在使用过程中发现,使用 subPath 挂载单文件的话,ConfigMap 的更新不会同步更新到容器对应文件中。这时候的解决办法是一个目录只存放一个配置文件,然后 ConfigMap 挂载到目录。\n如果一个 ConfigMap(app-config)中定义了多个文件(app1.json、app2.json),但是 app1-pod 中只会使用到 app1.json,可以使用如下方式:\n... volumeMounts: - mountPath: /config/path name: mockapi-mysettings volumes: - name: app-config configMap: name: app-config items: - key: app1.json path: app.json items 的 key 用于指定 ConfigMap 的 key,path 则可以定义为程序中的文件名。\n« 集群联邦\n» 定期删除 ElasticSearch 日志索引\n"},{"id":119,"href":"/kubernetes/delete-es-log-index-scheduler/","title":"Delete Es Log Index Scheduler","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 定期删除 ElasticSearch 日志索引\n定期删除 ElasticSearch 日志索引 # 背景 # 当前在 K8s 集群中部署了一套 EFK 日志监控系统,日复一日,ElasticSearch收集的数据越来越多,内存以及存储占用越来越高,需要定期来删除老旧的日志数据,来解放内存和存储空间,考虑到 K8s 中 cronjob 的功能特性,打算使用它制定一个es日志索引清除脚本,定时清除日志数据。\nConfigMap # 这个configMap用来存储一个shell脚本,该shell脚本执行日志索引清除操作:\napiVersion: v1 kind: ConfigMap metadata: name: es-log-indices-clear-configmap namespace: efk data: clean-indices.sh: | #/bin/bash LAST_MONTH_DATE=`date -d \u0026#34;1 month ago\u0026#34; +\u0026#34;%Y.%m.%d\u0026#34;` echo Start clear es indices *-${LAST_MONTH_DATE} curl -XDELETE http://elasticsearch:9200/*-${LAST_MONTH_DATE} --- 说明:这里我配置的configmap所在命名空间和efk部署的命名空间一致,并且es的Service的名称是elasticsearch,所以可以使用 http://elasticsearch:9200访问到es服务,否则的话需要是无法访问到的,所以这里需要根据具体情况配置es的服务地址;\nCronJob # CronJob使用了 poneding/sparrow\napiVersion: batch/v1beta1 kind: CronJob metadata: name: clean-indices namespace: efk spec: schedule: \u0026#34;0 0 1/1 * *\u0026#34; jobTemplate: spec: template: spec: containers: - name: auto-recycle-job image: poneding/sparrow args: [\u0026#34;/bin/sh\u0026#34;, \u0026#34;/job/clean-indices.sh\u0026#34;] volumeMounts: - name: vol mountPath: /job volumes: - name: vol configMap: name: es-log-indices-clear-configmap restartPolicy: OnFailure 说明:\n配置 cronjob 镜像为 poneding/sparrow,是自制的镜像,因为 busybox 镜像里面没有集成 curl 命令并且 date 的命令也不完全; 将 configmap 挂载到 job 目录下; 配置 cronjob 调度时间为 0 0 1/1 * *,每天的零点执行job目录下的 clean-indices.sh 脚本文件; 遇到的问题 # 在实际测试过程中遇到了一个问题,我们的脚本是通过 curl DElETE 方法来调用 es 的删除索引 api,我们使用 *-yyyy.MM.dd 通配符匹配并删除符合的 es 索引,但是 ES 默认是有自己的保护机制的,默认禁止使用通配符或 _all 删除索引,这个做法我们也很容易理解,毕竟不能允许如此轻易的大量删除。\n/var/www/app # curl -XDELETE http://elasticsearch:9200/*-2020.10.09 {\u0026#34;error\u0026#34;:{\u0026#34;root_cause\u0026#34;:[{\u0026#34;type\u0026#34;:\u0026#34;illegal_argument_exception\u0026#34;,\u0026#34;reason\u0026#34;:\u0026#34;Wildcard expressions or all indices are not allowed\u0026#34;}],\u0026#34;type\u0026#34;:\u0026#34;illegal_argument_exception\u0026#34;,\u0026#34;reason\u0026#34;:\u0026#34;Wildcard expressions or all indices are not allowed\u0026#34;},\u0026#34;status\u0026#34;:400} 解决方案是设置 elasticsearch 配置项:action.destructive_requires_name 为 false.\n遗憾的是无论我通过修改es的配置文件 elasticsearch.yml(位于 /usr/share/elasticsearch/config 路径下)还是增加环境变量都没起到效果:\n... # es-configmap data: elasticsearch.yml: | cluster.name: \u0026#34;es-cluster\u0026#34; network.host: 0.0.0.0 action.destructive_requires_name: false ... ... # es-statefulset env: - name: cluster.name value: es-cluster - name: action.destructive_requires_name value: \u0026#34;false\u0026#34; ... 最后,又找到了一个方法:使用 ES 语法修改配置。\nPUT /_cluster/settings { \u0026#34;persistent\u0026#34; : { \u0026#34;action.destructive_requires_name\u0026#34; : \u0026#34;false\u0026#34; } } 这时候再次使用通配符执行删除:\n/var/www/app# curl -XDELETE http://elasticsearch:9200/*-2020.10.09 {\u0026#34;acknowledged\u0026#34;:true} 虽然问题是解决了,但是修改配置文件不生效,还是令人困扰,可能是由于 es 集群的关系,单节点的配置并未对集群生效。\n追加,貌似可以从这得到答案 =\u0026gt; 官方文档: https://www.elastic.co/guide/en/elasticsearch/reference/current/settings.html\n« 了解 ConfigMap\n» 强制删除 K8s 资源\n"},{"id":120,"href":"/kubernetes/delete-k8s-resource-force/","title":"Delete K8s Resource Force","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 强制删除 K8s 资源\n强制删除 K8s 资源 # 强制删除 Pod # kubectl delete po \u0026lt;pod\u0026gt; -n \u0026lt;namespace\u0026gt; --force --grace-period=0 强制删除 PVC # kubectl patch pv \u0026lt;pv\u0026gt; -n \u0026lt;namespace\u0026gt; -p \u0026#39;{\u0026#34;metadata\u0026#34;:{\u0026#34;finalizers\u0026#34;:null}}\u0026#39; 强制删除 PV # kubectl patch pvc \u0026lt;pvc\u0026gt; -n \u0026lt;namespace\u0026gt; -p \u0026#39;{\u0026#34;metadata\u0026#34;:{\u0026#34;finalizers\u0026#34;:null}}\u0026#39; 强制删除命名空间 # 在删除 kubesphere 的命名空间时遇到无法删除成功的现象,命名空间一直处于 Terminating 状态。\n$ kubectl get ns |grep kubesphere NAME STATUS AGE kubesphere-controls-system Terminating 22d kubesphere-monitoring-system Terminating 21d 在网上找到了一种解决方案。\n首先获取命名空间的 json 文件,\nkubectl get ns kubesphere-controls-system -o json \u0026gt; temp.json 修改 temp.json 如下:\n{ \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;kind\u0026#34;: \u0026#34;Namespace\u0026#34;, \u0026#34;metadata\u0026#34;: { ... \u0026#34;finalizers\u0026#34;: [ \u0026#34;finalizers.kubesphere.io/namespaces\u0026#34; //删除此行 ], ... } 还有一种情况不同,可能需要删除的地方如下:\n{ \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;kind\u0026#34;: \u0026#34;Namespace\u0026#34;, ... \u0026#34;spec\u0026#34;: { \u0026#34;finalizers\u0026#34;: [ // 删除 \u0026#34;kubernetes\u0026#34; // 删除 ] // 删除 }, ... } 完成文件修改后,本地代理 kubernetes service:\nkubectl proxy --port 8081 以上操作会占用当前终端窗口,另外开启终端,执行命令:\ncurl -k -H \u0026#34;Content-Type:application/json\u0026#34; -X PUT --data-binary @temp.json http://127.0.0.1:8081/api/v1/namespaces/kubesphere-controls-system/finalize 执行以上命令大致输出如下:\n$ curl -k -H \u0026#34;Content-Type:application/json\u0026#34; -X PUT --data-binary @kcs.json http://127.0.0.1:8081/api/v1/namespaces/kubesphere-controls-system/finalize { \u0026#34;kind\u0026#34;: \u0026#34;Namespace\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;metadata\u0026#34;: { ... \u0026#34;deletionGracePeriodSeconds\u0026#34;: 0, \u0026#34;labels\u0026#34;: { \u0026#34;kubesphere.io/namespace\u0026#34;: \u0026#34;kubesphere-controls-system\u0026#34;, \u0026#34;kubesphere.io/workspace\u0026#34;: \u0026#34;system-workspace\u0026#34; }, ... \u0026#34;finalizers\u0026#34;: [ \u0026#34;finalizers.kubesphere.io/namespaces\u0026#34; ] }, \u0026#34;spec\u0026#34;: { }, \u0026#34;status\u0026#34;: { \u0026#34;phase\u0026#34;: \u0026#34;Terminating\u0026#34;, \u0026#34;conditions\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;NamespaceDeletionDiscoveryFailure\u0026#34;, \u0026#34;status\u0026#34;: \u0026#34;False\u0026#34;, \u0026#34;lastTransitionTime\u0026#34;: \u0026#34;2021-01-26T07:22:06Z\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;ResourcesDiscovered\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;All resources successfully discovered\u0026#34; }, { \u0026#34;type\u0026#34;: \u0026#34;NamespaceDeletionGroupVersionParsingFailure\u0026#34;, \u0026#34;status\u0026#34;: \u0026#34;False\u0026#34;, \u0026#34;lastTransitionTime\u0026#34;: \u0026#34;2021-01-26T07:22:06Z\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;ParsedGroupVersions\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;All legacy kube types successfully parsed\u0026#34; }, { \u0026#34;type\u0026#34;: \u0026#34;NamespaceDeletionContentFailure\u0026#34;, \u0026#34;status\u0026#34;: \u0026#34;False\u0026#34;, \u0026#34;lastTransitionTime\u0026#34;: \u0026#34;2021-01-26T07:23:13Z\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;ContentDeleted\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;All content successfully deleted\u0026#34; } ] } } 这时候顽固的的命名空间已经清除掉了。\n« 定期删除 ElasticSearch 日志索引\n» Gateway API 实践\n"},{"id":121,"href":"/kubernetes/gateway-api-practice/","title":"Gateway API Practice","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Gateway API 实践\nGateway API 实践 # Gateway API 还在积极开发中,目前已经发布了 v1.0.0 版本。可以通过 gateway-api 文档 获取最新进展。\nGateway API 概述 # Gateway API 是一个 Kubernetes 的扩展 API,它定义了一套 API 来管理网关、路由、TLS 等资源对象,可以用来替代传统的 Ingress。\n和 Ingress 一样,Gateway API 也是一个抽象层,它定义了一套 API 接口,这些接口由社区中的不同厂商来实现,比如 nginx、envoy、traefik 等。\nAPI 清单 # GatewayClass Gateway HTTPRoute GRPCRoute BackendTLSPolicy ReferenceGrant 安装 Gateway API # # 安装最新版 gateway-api CRDs export LATEST=$(curl -s https://api.github.com/repos/kubernetes-sigs/gateway-api/releases/latest | jq -r .tag_name) kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/$LATEST/crds.yaml 安装 Gateway Controller # gateway-api 只是定义了一套 API,需要 gateway-controller 来实现这些 API。目前已经有许多厂商实现了 gateway-controller,比如 nginx、envoy、traefik 等。\n下面介绍如何安装 nginx-gateway-fabric:\n# 安装最新版 gateway-controller export LATEST=$(curl -s https://api.github.com/repos/nginxinc/nginx-gateway-fabric/releases/latest | jq -r .tag_name) kubectl apply -f https://github.com/nginxinc/nginx-gateway-fabric/releases/download/$LATEST/nginx-gateway.yaml 配置 nginx-gateway 使用主机网络:\nkubectl patch deployment nginx-gateway -n nginx-gateway --type=\u0026#39;json\u0026#39; -p=\u0026#39;[{\u0026#34;op\u0026#34;: \u0026#34;add\u0026#34;, \u0026#34;path\u0026#34;: \u0026#34;/spec/template/spec/hostNetwork\u0026#34;, \u0026#34;value\u0026#34;: true}]\u0026#39; 注意:配置使用主机网络之后,宿主机的 80 端口将被占用,因此一个节点只允许调度一个 nginx-gateway Pod,当更新 nginx-gateway 时,您可能需要先停止旧的 Pod,再启动新的 Pod。\n在使用过程中,发现有可能由于宿主机的差异导致 nginx-gateway 健康检查无法通过,Pod 不能正常启动。可以通过修改配置 allowPrivilegeEscalation 字段来解决:\n# 使用主机网络,端口被占用,需要先停止 nginx-gateway kubectl scale deployment nginx-gateway -n nginx-gateway --replicas=0 kubectl patch deployment nginx-gateway -n nginx-gateway --type=\u0026#39;json\u0026#39; -p=\u0026#39;[{\u0026#34;op\u0026#34;: \u0026#34;replace\u0026#34;, \u0026#34;path\u0026#34;: \u0026#34;/spec/template/spec/containers/0/securityContext/allowPrivilegeEscalation\u0026#34;, \u0026#34;value\u0026#34;: true}]\u0026#39; kubectl scale deployment nginx-gateway -n nginx-gateway --replicas=1 实践 # 简单网关路由 # 部署 nginx deployment,并且暴露服务:\nkubectl create deployment nginx --image=nginx kubectl expose deployment nginx --name=nginx --port=80 --target-port=80 部署 nginx gateway \u0026amp; httpRoute:\n```bash kubectl apply -f - \u0026lt;\u0026lt;EOF apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: gateway spec: gatewayClassName: nginx listeners: - name: http port: 80 protocol: HTTP hostname: \u0026#34;*.mydomain.com\u0026#34; --- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: nginx spec: parentRefs: - name: gateway sectionName: http hostnames: - \u0026#34;nginx.mydomain.com\u0026#34; rules: - matches: - path: type: PathPrefix value: / backendRefs: - name: nginx port: 80 TLS 网关路由:CertManager + Let\u0026rsquo;s Encrypt # 特定域名网关:\napiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: mydomain-cert-issuer spec: acme: email: poneding@gmail.com server: https://acme-v02.api.letsencrypt.org/directory privateKeySecretRef: name: mydomain-cert-issuer-account-key solvers: - http01: gatewayHTTPRoute: parentRefs: - name: nginx-gateway namespace: nginx-gateway kind: Gateway selector: dnsNames: - \u0026#34;nginx.mydomain.com\u0026#34; --- apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: nginx-gateway namespace: nginx-gateway annotations: cert-manager.io/cluster-issuer: mydomain-cert-issuer spec: gatewayClassName: nginx listeners: - name: nginx-mydomain-https hostname: \u0026#39;nginx.mydomain.com\u0026#39; protocol: HTTPS port: 443 allowedRoutes: namespaces: from: All tls: certificateRefs: - group: \u0026#34;\u0026#34; kind: Secret name: nginx-mydomain-https-cert mode: Terminate --- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: nginx spec: parentRefs: - name: nginx-gateway namespace: nginx-gateway sectionName: nginx-mydomain-https hostnames: - \u0026#34;nginx.mydomain.com\u0026#34; rules: - matches: backendRefs: - name: nginx port: 80 通配符域名(泛域名)网关:\napiVersion: v1 kind: Secret metadata: name: cloudflare-api-token namespace: cert-manager type: Opaque stringData: api-token: [your_cloudflare_api_token] --- apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: mydomain-cert-issuer spec: acme: email: poneding@gmail.com server: https://acme-v02.api.letsencrypt.org/directory privateKeySecretRef: name: mydomain-cert-issuer-account-key solvers: - dns01: cloudflare: apiTokenSecretRef: name: cloudflare-api-token key: api-token selector: dnsNames: - \u0026#34;*.gateway.mydomain.com\u0026#34; --- apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: name: nginx-gateway namespace: nginx-gateway annotations: cert-manager.io/cluster-issuer: mydomain-cert-issuer spec: gatewayClassName: nginx listeners: # encrypted by cloudflare - name: wildcard-mydomain-http hostname: \u0026#39;*.mydomain.com\u0026#39; protocol: HTTP port: 80 allowedRoutes: namespaces: from: All - name: wildcard-gateway-mydomain-http hostname: \u0026#39;*.gateway.mydomain.com\u0026#39; protocol: HTTP port: 80 allowedRoutes: namespaces: from: All - name: wildcard-gateway-mydomain-https hostname: \u0026#39;*.gateway.mydomain.com\u0026#39; protocol: HTTPS port: 443 allowedRoutes: namespaces: from: All tls: certificateRefs: - group: \u0026#34;\u0026#34; kind: Secret name: wildcard-gateway-mydomain-cert mode: Terminate --- apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: nginx spec: parentRefs: - name: nginx-gateway namespace: nginx-gateway sectionName: wildcard-gateway-mydomain-https hostnames: - \u0026#34;nginx.gateway.mydomain.com\u0026#34; rules: - matches: backendRefs: - name: nginx port: 80 « 强制删除 K8s 资源\n» Kubernetes 0-1 Helm Kubernetes 的包管理工具\n"},{"id":122,"href":"/kubernetes/helm-k8s-package-management-tool/","title":"Helm K8s Package Management Tool","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 Helm Kubernetes 的包管理工具\nKubernetes 0-1 Helm Kubernetes 的包管理工具 # Helm is the best way to find, share, and use software built for Kubernetes.\nHelm是为Kubernetes寻找,共享和使用软件构建的最佳方式。\n简介 # Helm帮助管理Kubernetes应用程序,即使是面对复杂的K8s引用,Helm Charts也可以轻松实现定义,安装和升级。\nHelm是CNCF的毕业项目,由Helm社区维护。\nCharts:\nCharts可以看作是Helm的程序包,一个Chart是描述Kubernetes资源集的文件集合。\nRepository:\n存储和共享Charts,可以看作是Kubernetes程序包的存储中心。\nRelease:\n由一个Chart运行起来的实例,这将在kubernetes集群中生成或更新一组资源,可以使用同一个chart运行成多个release。例如,如果你想运行多个redis服务,你可以通过多次安装redis的chart得到。\n看到以上三个概念,你可能会觉得似曾相识,没错,与docker三个概念——image,registry,container如出一辙,也许现在你会加深点理解了。\n安装 # 可以方便的使用脚本安装\ncurl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 chmod 700 ./get-helm.sh ./get-helm.sh 也可以下载指定版本手动安装\n下载地址: https://github.com/helm/helm/releases\nwget https://get.helm.sh/helm-v3.5.2-linux-amd64.tar.gz tar -zxvf ./helm-v3.5.2-linux-amd64.tar.gz sudo mv linux-amd64/helm /usr/local/bin/helm 第一个helm命令\nhelm help 更多安装方式可查看: https://helm.sh/docs/intro/install/\n使用 # 初始化Helm Chart仓库:\n首先添加helm官方的helm stable charts仓库\nhelm repo add stable https://charts.helm.sh/stable 查询charts仓库:\n例如你想查找prometheus应用仓库,打开 https://artifacthub.io/站点,在查询输入框中输入prometheus之后,搜索可以得到官方或民间的仓库。\n一般选择具有官方标志的仓库,进入该仓库的主页,里面会有charts的基本信息,以及安装命令的帮助。\n添加Prometheus的官方Charts仓库\nhelm repo add prometheus-community https://prometheus-community.github.io/helm-charts 添加仓库后,就可以在当前已经添加的仓库中搜索你需要的kubernetes应用了\n$ helm search repo prometheus NAME CHART VERSION APP VERSION DESCRIPTION prometheus-community/kube-prometheus-stack 13.5.0 0.45.0 kube-prometheus-stack collects Kubernetes manif... prometheus-community/prometheus 13.2.1 2.24.0 Prometheus is a monitoring system and time seri... prometheus-community/prometheus-adapter 2.11.1 v0.8.3 A Helm chart for k8s prometheus adapter ... 创建release:\n使用特定的chart安装kubernetes应用\n$ helm install prometheus prometheus-community/prometheus NAME: prometheus LAST DEPLOYED: Sun Feb 7 02:04:59 2021 NAMESPACE: default STATUS: deployed REVISION: 1 ... 查看release:\n$ helm ls NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION prometheus default 1 2021-02-07 02:04:59 deployed prometheus-13.2.1 2.24.0 $ helm status prometheus NAME: prometheus LAST DEPLOYED: Sun Feb 7 02:04:59 2021 NAMESPACE: default STATUS: deployed REVISION: 1 并且可以查看K8s集群是否正在创建你需要的应用\n$ kubectl get pod NAME READY STATUS RESTARTS AGE prometheus-alertmanager-5dbfffbbc4-jppgj 0/2 Pending 0 36s prometheus-kube-state-metrics-66c4db67ff-r65tg 0/1 ContainerCreating 0 36s prometheus-node-exporter-tvjnx 1/1 Running 0 36s prometheus-pushgateway-798b886754-x4fvq 0/1 ContainerCreating 0 36s prometheus-server-74656c5bbc-k4m6x 0/2 Pending 0 36s 删除 release:\n以下命令会删除所有install创建的所有Kubernetes资源\n$ helm uninstall prometheus release \u0026#34;prometheus\u0026#34; uninstalled 如果你想保存安装的历史信息,方便以后查看发布审计或者回滚发布,可以使用--keep-history命令标识。\n$ helm uninstall prometheus --keep-history release \u0026#34;prometheus\u0026#34; uninstalled # 此时查看安装状态将变成uninstalled $ helm ls NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION $ helm ls -a NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION prometheus default 1 2021-02-07 02:12:46 uninstalled prometheus-13.2.1 2.24.0 $ helm status prometheus NAME: prometheus LAST DEPLOYED: Sat Feb 6 02:49:41 2021 NAMESPACE: default STATUS: uninstalled REVISION: 1 ... 将删除release重新安装/回滚回来:\n每次成功的install或upgrade release后,我们都会得到release的REVISION,在回滚release时需要使用到这个版本号\n$ helm rollback prometheus 1 Rollback was a success! Happy Helming! 定制 # Charts是允许用户定制的,例如你想修改应用端口,或者应用的副本数量,可以通过修改charts的资源文件,然后依据资源文件创建release。\n首先,下载应用的charts文件集合到本地\n$ helm repo add bitnami https://charts.bitnami.com/bitnami \u0026#34;bitnami\u0026#34; has been added to your repositories $ mkdir nginx $ helm show values bitnami/nginx \u0026gt; ./nginx/values.yaml 修改values.yaml文件,如果你对K8s资源的定义有所了解,那么你能轻松的修改该文件。\n$ vim ./nginx/values.yaml ... # 修改nginx deployment副本数为2 replicaCount: 2 ... 文件修改完成后,使用如下命令安装\n$ helm install nginx -f ./nginx/values.yaml bitnami/nginx NAME: nginx LAST DEPLOYED: Sun Feb 7 02:59:48 2021 NAMESPACE: default STATUS: deployed REVISION: 1 ... 以上操作可以使用如下命令代替:\nhelm install nginx --set replicaCount=2 bitnami/nginx 使用\u0026ndash;set设置配置说明:\n如果同时指定了values.yaml文件和使用\u0026ndash;set设置配置,那么\u0026ndash;set设置的配置优先级更高\n多组配置:\u0026ndash;set a=1,b=2\n层级:\u0026ndash;set user.name=jaychou\n数组:\u0026ndash;set users={jay,jack,john} \u0026ndash;set users[0].name=jaychou\n由于文件更易管理和迁移,更推荐修改values.yaml的方式定制Chart:\n查看release的资源创建情况\n$ kubectl get pod NAME READY STATUS RESTARTS AGE nginx-78956d9896-5qvdl 1/1 Running 0 32s nginx-78956d9896-fhw5w 1/1 Running 0 32s 确实,如我们所愿,创建了2个副本实例。\n更新Release:\n已经安装了的release,修改定制文件后升级发布\n$ helm upgrade nginx -f ./values.yaml bitnami/nginx Release \u0026#34;nginx\u0026#34; has been upgraded. Happy Helming! NAME: nginx LAST DEPLOYED: Sun Feb 7 03:05:26 2021 NAMESPACE: default STATUS: deployed REVISION: 2 TEST SUITE: None ... 常用命令 # 帮助命令\nhelm help helm get -h 更新charts仓库\nhelm repo update 更多helm install的方式\n从chart仓库安装,我们上面使用的方式\n本地编写chart模板和values.yaml等文件,然后使用以下命令安装\nhelm install nginx /path/to/nginx-chart 得到chart压缩文件,使用以下命令安装\nhelm install nginx nginx-chart.tgz chart压缩文件的http url,使用以下命令安装\nhelm install nginx https://xxx.com/charts/ngin-chart.tgz 如果你有兴趣,你可以创建自己的charts,执行以下命令会在目录下生成charts文件\n$ helm create my-nginx Creating my-nginx # 定制后 $ helm package my-nginx Successfully packaged chart and saved it to: /path/to/my-nginx-0.1.0.tgz $ helm install my-nginx ./my-nginx-0.1.0.tgz NAME: nginx2 LAST DEPLOYED: Sun Feb 28 14:20:53 2021 NAMESPACE: default STATUS: deployed REVISION: 1 ... 更多了解请前往 官方文档\n« Gateway API 实践\n» Kubernetes 0-1 实现Pod自动扩缩HPA\n"},{"id":123,"href":"/kubernetes/hpa-usage/","title":"Hpa Usage","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 实现Pod自动扩缩HPA\nKubernetes 0-1 实现Pod自动扩缩HPA # https://blog.51cto.com/14143894/2458468?source=dra\n前言 # 在K8s集群中,我们可以通过部署Metrics Server来持续收集Pod的资源利用指标数据,我们可以根据收集到的指标数据来评估是否需要调整Pod的数量以贴合它的使用需求。例如,当我们观察到Pod的CPU利用率过高时,我们可以适当上调Deployment的Replicas字段值,来手动实现Pod的横向扩容。\nMetrics Server的指标数据可以通过Dashboard查看到; 安装Metrics Server # HPA介绍 # HPA(Horizontal Pod Autoscaler,Pod水平自动扩缩),根据Pod的资源利用率自动调整Pod管理器中副本数:Pod资源利用率低,降低Pod副本数,降低资源的使用,节约成本;Pod资源利用率高,增加Pod副本数,提高应用的负载能力。\n示例 # 以部署redis为例,现使用redis\n« Kubernetes 0-1 Helm Kubernetes 的包管理工具\n» HTTP 客户端调用 Kubernetes APIServer\n"},{"id":124,"href":"/kubernetes/http-call-k8s-apiserver/","title":"HTTP Call K8s Apiserver","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / HTTP 客户端调用 Kubernetes APIServer\nHTTP 客户端调用 Kubernetes APIServer # 本篇介绍几种如何通过 HTTP 客户端调用 Kubernetes APIServer 的姿势。\n如何获取 Kubernetes api-server 地址 # 查看 api-server 的几种方式:\n# 1. 直接查看 kubeconfig 文件 $ cat ~/.kube/config apiVersion: v1 clusters: - cluster: server: https://192.168.58.2:8443 ... # 2. kubectl 查看集群信息 $ kubectl cluster-info Kubernetes control plane is running at https://192.168.58.2:8443 ... # 3. kubectl 查看集群配置 $ kubectl config view clusters: - cluster: ... server: https://192.168.58.2:8443 ... 配置环境变量\nKUBE_API=$(kubectl config view -o jsonpath=\u0026#39;{.clusters[0].cluster.server}\u0026#39;) echo $KUBE_API api-server 如何给客户端授权 # 使用证书授权 # 直接调用:\n$ curl $KUBE_API/version curl: (60) SSL certificate problem: unable to get local issuer certificate More details here: https://curl.haxx.se/docs/sslcerts.html curl failed to verify the legitimacy of the server and therefore could not establish a secure connection to it. To learn more about this situation and how to fix it, please visit the web page mentioned above. # 忽略证书验证 $ curl $KUBE_API/version --insecure { \u0026#34;kind\u0026#34;: \u0026#34;Status\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;metadata\u0026#34;: { }, \u0026#34;status\u0026#34;: \u0026#34;Failure\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;Unauthorized\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;Unauthorized\u0026#34;, \u0026#34;code\u0026#34;: 401 } 结果是我们无法顺利调用接口。\n获取证书:\n# 服务端证书 $ kubectl config view --raw \\ -o jsonpath=\u0026#39;{.clusters[0].cluster.certificate-authority-data}\u0026#39; \\ | base64 --decode \u0026gt; ./server-ca.crt # 服务端证书 $ kubectl config view --raw \\ -o jsonpath=\u0026#39;{.users[0].user.client-certificate-data}\u0026#39; \\ | base64 --decode \u0026gt; ./client-ca.crt # 服务端证书密钥 $ kubectl config view --raw \\ -o jsonpath=\u0026#39;{.users[0].user.client-key-data}\u0026#39; \\ | base64 --decode \u0026gt; ./client-ca.key 我们这次尝试使用证书调用 API:\n$ curl $KUBE_API/version --cacert ./server-ca.crt \\ --cert ./client-ca.crt \\ --key ./client-ca.key { \u0026#34;major\u0026#34;: \u0026#34;1\u0026#34;, \u0026#34;minor\u0026#34;: \u0026#34;22\u0026#34;, \u0026#34;gitVersion\u0026#34;: \u0026#34;v1.22.6+k3s1\u0026#34;, \u0026#34;gitCommit\u0026#34;: \u0026#34;3228d9cb9a4727d48f60de4f1ab472f7c50df904\u0026#34;, \u0026#34;gitTreeState\u0026#34;: \u0026#34;clean\u0026#34;, \u0026#34;buildDate\u0026#34;: \u0026#34;2022-01-25T01:27:44Z\u0026#34;, \u0026#34;goVersion\u0026#34;: \u0026#34;go1.16.10\u0026#34;, \u0026#34;compiler\u0026#34;: \u0026#34;gc\u0026#34;, \u0026#34;platform\u0026#34;: \u0026#34;linux/amd64\u0026#34; } 此时,可以正常调用 API 了,我们继续调用其他接口:\n$ curl $KUBE_API/apis/apps/v1/deployments --cacert ./server-ca.crt \\ --cert ./client-ca.crt \\ --key ./client-ca.key { \u0026#34;kind\u0026#34;: \u0026#34;DeploymentList\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;apps/v1\u0026#34;, \u0026#34;metadata\u0026#34;: { \u0026#34;resourceVersion\u0026#34;: \u0026#34;654514\u0026#34; }, \u0026#34;items\u0026#34;: [...] } 也 OK 了。\n使用 token 授权 # token 指的是 ServiceAccount 的 Token,每个命名空间内都会存在一个默认的 sa:default。\n我们尝试来生成 sa token,我们首先生成一个 default 命名空间下 default sa 的 token:\n# 获取 sa 采用的 secret $ kubectl get sa default -o jsonpath=\u0026#39;{.secrets[0].name}\u0026#39; # 获取 token DEFAULT_DEFAULT_TOKEN=$(kubectl get secrets \\ $(kubectl get sa default -o jsonpath=\u0026#39;{.secrets[0].name}\u0026#39;) \\ -o jsonpath=\u0026#39;{.data.token}\u0026#39; | base64 --decode) $ echo $DEFAULT_DEFAULT_TOKEN eyJhbGciOiJSUzI1NiIsImtpZCI6IjFJdzhRZlV3TkpRRm5aSzU4b2hWTWI3YmdPUTlyLTBWSU9OQmNlN3cwdGsifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZmF1bHQtdG9rZW4tcW50bGsiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZGVmYXVsdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjE0OGJmM2U0LTkzY2EtNGJiMS04MDczLWMzZmIzM2NiYmJmNiIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OmRlZmF1bHQifQ.ivtk9eRH-35wIaPk4CtPSBoBkuiMxQyta17qMxNjhgedyV5i1QGYty36k0ufbOpBMXr2DsdRy8yQTx2qnH-AcduyxaoCxX-SQ4yGUsKSHCTipktcWqFi-CFzNo6EMCZiX8zAmeXjYOMmF8kh2T6wkHmjERDYsqWPaftasTUrKEYpcawFCMnv0QTpDe-okr6vQx6t7pJ5fx_PCw-GEEZUKQZn1tHIStd77eZd546--rrS6nPczKc3GnVFsDTcPM5HI7T_hXnId1TEnOYM8H5ornJ6uDP2oN_niwV41qOXMM52Bep0cvnikG-kUklLpmZxkwAtQCHDDh36A5JX_oaK5w 💡 如果你的 kubectl 版本大于 1.24,那么你可以直接使用以下命令获取 token:\n$ DEFAULT_DEFAULT_TOKEN=$(kubectl create token default -n default) $ echo $DEFAULT_DEFAULT_TOKEN eyJhbGciOiJSUzI1NiIsImtpZCI6IjFJdzhRZlV3TkpRRm5aSzU4b2hWTWI3YmdPUTlyLTBWSU9OQmNlN3cwdGsifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJrM3MiXSwiZXhwIjoxNjc2NDUwODg2LCJpYXQiOjE2NzY0NDcyODYsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0Iiwic2VydmljZWFjY291bnQiOnsibmFtZSI6ImRlZmF1bHQiLCJ1aWQiOiIxNDhiZjNlNC05M2NhLTRiYjEtODA3My1jM2ZiMzNjYmJiZjYifX0sIm5iZiI6MTY3NjQ0NzI4Niwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6ZGVmYXVsdCJ9.FC6SZ3fKhKML76AYnfaq4Y74mlRMBUxgfsEocrczzoN_NvDbqzZ_0sCAvA_ZdcVXv74hXTTeO1_DLoXZE_aLmGIxH1ImfbCDbxZH1xvNbE-7oozKmWBjYM7VRnNVvNC8EiRmcSEMttnQxgnBqUDZCyU8VA_pujld_RsB4SiD8tpXN5PaSaEx6vz6AWYWtW8wqwcAlIWTGk4hae090a0sLplyB4xx-7SiYjmkM9tVXFz5WWdUYSfyQeM-EfDpH4fNsvefWtW_KeJ5Wg28RuhiLbUv9_UV1RGt11Wh7lf0nNmxobqB8j-PEnphiECMKDv29x5KtQDU1wSgbSMI-_eTlQ 使用 token 调用 API:\n$ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments --cacert ./server-ca.crt \\ -H \u0026#34;Authorization: Bearer $DEFAULT_DEFAULT_TOKEN\u0026#34; { \u0026#34;kind\u0026#34;: \u0026#34;Status\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;metadata\u0026#34;: { } \u0026#34;status\u0026#34;: \u0026#34;Failure\u0026#34;, \u0026#34;message\u0026#34;: \u0026#34;deployments.apps is forbidden: User \\\u0026#34;system:serviceaccount:default:default\\\u0026#34; cannot list resource \\\u0026#34;deployments\\\u0026#34; in API group \\\u0026#34;apps\\\u0026#34; at the cluster scope\u0026#34;, \u0026#34;reason\u0026#34;: \u0026#34;Forbidden\u0026#34;, \u0026#34;details\u0026#34;: { \u0026#34;group\u0026#34;: \u0026#34;apps\u0026#34;, \u0026#34;kind\u0026#34;: \u0026#34;deployments\u0026#34; }, \u0026#34;code\u0026#34;: 403 } 可以看到用户 system:serviceaccount:default:default 并没有权限获取自身命名空间内的 Kubernetes 对象列表。\n接下来让我们尝试给这个 sa 绑定一个 cluster-admin 的 ClusterRole,然后我们再次生成 token,并调用接口:\n$ kubectl create clusterrolebinding default-sa-with-cluster-admin-role \\ --clusterrole cluster-admin --serviceaccount default:default $ DEFAULT_DEFAULT_TOKEN=$(kubectl create token default -n default) $ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments --cacert ./server-ca.crt \\ -H \u0026#34;Authorization: Bearer $DEFAULT_DEFAULT_TOKEN\u0026#34; { \u0026#34;kind\u0026#34;: \u0026#34;DeploymentList\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;apps/v1\u0026#34;, \u0026#34;metadata\u0026#34;: { \u0026#34;resourceVersion\u0026#34;: \u0026#34;2824281\u0026#34; }, \u0026#34;items\u0026#34;: [] } 这完美生效,因为 defualt sa 有了一个超级大的 admin 角色。(另外,你可以单独创建一个特定资源权限的 role,在绑定到一个 sa,来满足最小权限原则)\n在 Pod 内访问 api-server # 在 pod 内部,默认会生成 Kubernetes 服务地址相关的环境变量,并且会在特殊目录下保存证书以及 token,当然这个token 是根据 pod 所使用的 sa 生成的。\n证书文件地址:/var/run/secrets/kubernetes.io/serviceaccount/ca.crt\ntoken 文件地址:/var/run/secrets/kubernetes.io/serviceaccount/ca.crt\n$ kubectl run -it --image curlimages/curl --restart=Never mypod -- sh $ env | grep KUBERNETES KUBERNETES_SERVICE_PORT=443 KUBERNETES_PORT=tcp://10.43.0.1:443 KUBERNETES_PORT_443_TCP_ADDR=10.43.0.1 KUBERNETES_PORT_443_TCP_PORT=443 KUBERNETES_PORT_443_TCP_PROTO=tcp KUBERNETES_SERVICE_PORT_HTTPS=443 KUBERNETES_PORT_443_TCP=tcp://10.43.0.1:443 KUBERNETES_SERVICE_HOST=10.43.0.1 $ TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) $ curl https://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT}/apis/apps/v1 \\ --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \\ --header \u0026#34;Authorization: Bearer $TOKEN\u0026#34; 使用 curl 执行基本的 CRUD 操作 # 创建资源:\n$ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments \\ --cacert ./server-ca.crt \\ --cert ./client-ca.crt \\ --key ./client-ca.key \\ -X POST \\ -H \u0026#39;Content-Type: application/yaml\u0026#39; \\ -d \u0026#39;--- apiVersion: apps/v1 kind: Deployment metadata: name: sleep spec: replicas: 1 selector: matchLabels: app: sleep template: metadata: labels: app: sleep spec: containers: - name: sleep image: curlimages/curl command: [\u0026#34;/bin/sleep\u0026#34;, \u0026#34;365d\u0026#34;] \u0026#39; $ kubectl get deploy NAME READY UP-TO-DATE AVAILABLE AGE sleep 1/1 1 1 8s 获取资源:\n$ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments \\ --cacert ./server-ca.crt \\ --cert ./client-ca.crt \\ --key ./client-ca.key # 给定特定的资源名 $ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments/sleep \\ --cacert ./server-ca.crt \\ --cert ./client-ca.crt \\ --key ./client-ca.key # watch $ curl $KUBE_API\u0026#39;/apis/apps/v1/namespaces/default/deployments?watch=true\u0026#39; \\ --cacert ./server-ca.crt \\ --cert ./client-ca.crt \\ --key ./client-ca.key 更新资源:\ncurl $KUBE_API/apis/apps/v1/namespaces/default/deployments/sleep \\ --cacert ./server-ca.crt \\ --cert ./client-ca.crt \\ --key ./client-ca.key \\ -X PUT \\ -H \u0026#39;Content-Type: application/yaml\u0026#39; \\ -d \u0026#39;--- apiVersion: apps/v1 kind: Deployment metadata: name: sleep spec: replicas: 1 selector: matchLabels: app: sleep template: metadata: labels: app: sleep spec: containers: - name: sleep image: curlimages/curl command: [\u0026#34;/bin/sleep\u0026#34;, \u0026#34;730d\u0026#34;] # \u0026lt;-- Making it sleep twice longer \u0026#39; # patch 更新 curl $KUBE_API/apis/apps/v1/namespaces/default/deployments/sleep \\ --cacert ./server-ca.crt \\ --cert ./client-ca.crt \\ --key ./client-ca.key \\ -X PATCH \\ -H \u0026#39;Content-Type: application/merge-patch+json\u0026#39; \\ -d \u0026#39;{ \u0026#34;spec\u0026#34;: { \u0026#34;template\u0026#34;: { \u0026#34;spec\u0026#34;: { \u0026#34;containers\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;sleep\u0026#34;, \u0026#34;image\u0026#34;: \u0026#34;curlimages/curl\u0026#34;, \u0026#34;command\u0026#34;: [\u0026#34;/bin/sleep\u0026#34;, \u0026#34;1d\u0026#34;] } ] } } } }\u0026#39; 删除资源:\n$ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments \\ --cacert ./server-ca.crt \\ --cert ./client-ca.crt \\ --key ./client-ca.key \\ -X DELETE # 删除单个资源 $ curl $KUBE_API/apis/apps/v1/namespaces/default/deployments/sleep \\ --cacert ./server-ca.crt \\ --cert ./client-ca.crt \\ --key ./client-ca.key \\ -X DELETE 如何使用 kubectl 调用 API # 使用 kubectl 代理 Kubernetes api-server # $ kubectl proxy --port 8080 # 启动了代理服务后,调用 Kubernetes api-server 变得更简单 $ curl localhost:8080/apis/apps/v1/deployments { \u0026#34;kind\u0026#34;: \u0026#34;DeploymentList\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;apps/v1\u0026#34;, \u0026#34;metadata\u0026#34;: { \u0026#34;resourceVersion\u0026#34;: \u0026#34;660883\u0026#34; }, \u0026#34;items\u0026#34;: [...] } kubectl 使用原始模式调用 API # # 获取 $ kubectl get --raw /api/v1/namespaces/default/pods # 创建 $ kubectl create --raw /api/v1/namespaces/default/pods -f file.yaml # 更新 $ kubectl replace --raw /api/v1/namespaces/default/pods/mypod -f file.json # 删除 $ kubectl delete --raw /api/v1/namespaces/default/pods 如何查看 kubectl 命令(如 apply)发送的 API 请求 # $ kubectl create deployment nginx --image nginx -v 6 I0215 17:07:35.188480 43870 loader.go:372] Config loaded from file: /Users/dp/.kube/config I0215 17:07:35.362580 43870 round_trippers.go:553] POST https://192.168.58.2:8443/apis/apps/v1/namespaces/default/deployments?fieldManager=kubectl-create\u0026amp;fieldValidation=Strict 201 Created in 167 milliseconds deployment.apps/nginx created 参考 # How To Call Kubernetes API using Simple HTTP Client 使用 Kubernetes API 访问集群 | Kubernetes 从 Pod 中访问 Kubernetes API | Kuberentes « Kubernetes 0-1 实现Pod自动扩缩HPA\n» Informer\n"},{"id":125,"href":"/kubernetes/informer/","title":"Informer","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Informer\nInformer # Informer是client-go中实现的一个工具包,目前已经被kubernetes中各个组件所使用,例如controller-manager。Informer本质是一个api资源的缓存。\n主要功能:\n将etcd数据同步至本地缓存,客户端通过本地缓存读取和监听资源\n注册资源Add,Update,Delete的触发事件\n目的:\n本地缓存,避免组件直接与api-server交互,减缓对api-server及etcd的访问压力。\n组件 # Reflector\nDelta FIFO Queue\nIndexer(local strorage)\n下面结合流程示意图简单介绍这些组件的角色。\n流程示意图 # 这张示意图展示了client-go类库中各个组件的工作机制,以及它们与咱们将要编写的自定义控制器的交互点(黄颜色标注的块是需要自行开发的部分)。\nReflector:\n负责监听(Watch)特定Kubernetes资源对象,监听的资源对象可以是内置的资源例如Pod,Ingress等,也可以是定制的CR对象。\nReflettor与ApiServer建立连接,第一次使用List\u0026amp;Watch机制从ApiServer中List特定资源的所有实例,这些实例附带的ResourceVersion字段可以用来区分实例是否更新。后续在使用List\u0026amp;Watch机制从ApiServer中Watch特定资源的新增,更新,删除等变化(增量Delta)。\n将监听到的资源的新增,更新,删除顺序写入到DeltaFIFO队列中。\nDeltaFIFO:\n一个增量的先进先出的队列,存储监听到的资源,以及资源事件类型,例如Added,Updated,Deleted,Replaced,Sync。\nInformer:\nIndexer:\n一个自带索引功能的本地存储,用于存储资源对象。Informer从DeltaFIFO中Pop出资源,存储到Indexer。Indexer中资源与k8s etcd数据保持一致。本地读取时直接查询本地存储,从而减少k8s apiserver和etcd的压力。\n使用示例 # 自定义控制器\nclientset, err := kubernetes.NewForConfig(config) stopCh := make(chan struct{}) defer close(stopch) sharedInformers := informers.NewSharedInformerFactory(clientset, time.Minute) informer := sharedInformer.Core().V1().Pods().Informer() informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{} { // ... }, UpdateFunc: func(obj interface{} { // ... }, DeleteFunc : func(obj interface{} { // ... }) informer.Run(stopCh) }) Informer 需要通过 ClientSet 与 Kubernetes API Server 交互; 创建 stopCh 是用于在程序进程退出前通知 Informer 提前退出,Informer 是一个持久运行的 goroutine; NewSharedInformerFactory 实例化了一个 SharedInformer 对象,用于进行本地资源存储; sharedInformer.Core().V1().Pods().Informer() 得到了具体 Pod 资源的 informer 对象; AddEventHandler 即图中的第6步,这是一个资源事件回调方法,上例中即为当创建/更新/删除 Pod 时触发事件回调方法; 一般而言,其他组件使用 Informer 机制触发资源回调方法会将资源对象推送到 WorkQueue 或其他队列中,具体推送的位置要去回调方法里自行实现。 上面这个示例,当触发了 Add,Update 或者 Delete 事件,就通知 Client-go,告知 Kubernetes 资源事件发生变更并且需要进行相应的处理。\nInfromer机制 # 资源Informer # 每个内置的k8s资源对实现了对应的Informer机制,均包含Lister和Informer方法,例如:\ntype PodInformer interface { Lister() cache.SharedIndexInformer Informer() v1.PodLister } SharedInformer共享机制 # 每实例化一个Informer对象,都需要维护一个对应的Reflector。当同一对象Informer实例被实例化多次时,运行过多的ListAndWatch,这其中包括的\n« HTTP 客户端调用 Kubernetes APIServer\n» 通过 Ingress 进行灰度发布\n"},{"id":126,"href":"/kubernetes/ingress-gray-deploy/","title":"Ingress Gray Deploy","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 通过 Ingress 进行灰度发布\n通过 Ingress 进行灰度发布 # https://start.aliyun.com/handson/Tn0HcdCZ/grap_publish_by_ingress\nStep 1 :实验介绍 # 本实验,你将运行运行一个简单的应用,部署一个新的应用用于新的发布,并通过 Ingress 能力实现灰度发布。\n灰度及蓝绿发布是为新版本创建一个与老版本完全一致的生产环境,在不影响老版本的前提下,按照一定的规则把部分流量切换到新版本,当新版本试运行一段时间没有问题后,将用户的全量流量从老版本迁移至新版本。\n通过本实验,你将学习:\n通过 Ingress 按权重进行灰度发布 通过 Ingress 按 Header 进行灰度发布 容器服务 Kubernetes 版(简称 ACK) 本节课使用的 Kubernetes(k8s) 集群就是由 ACK 提供的,本实验涵盖的都是一些基本操作。更多高级用法,可以去 ACK 的产品页面了解哦。\nStep 2 :部署 Deployment V1 应用 # 创建如下 YAML 文件(app-v1.yaml)\napiVersion: v1 kind: Service metadata: name: my-app-v1 labels: app: my-app spec: ports: - name: http port: 80 targetPort: http selector: app: my-app version: v1.0.0 --- apiVersion: apps/v1 kind: Deployment metadata: name: my-app-v1 labels: app: my-app spec: replicas: 1 selector: matchLabels: app: my-app version: v1.0.0 template: metadata: labels: app: my-app version: v1.0.0 annotations: prometheus.io/scrape: \u0026#34;true\u0026#34; prometheus.io/port: \u0026#34;9101\u0026#34; spec: containers: - name: my-app image: registry.cn-hangzhou.aliyuncs.com/containerdemo/containersol-k8s-deployment-strategies ports: - name: http containerPort: 8080 - name: probe containerPort: 8086 env: - name: VERSION value: v1.0.0 livenessProbe: httpGet: path: /live port: probe initialDelaySeconds: 5 periodSeconds: 5 readinessProbe: httpGet: path: /ready port: probe periodSeconds: 5 执行如下命令部署 Deployement V1 应用:\nkubectl apply -f app-v1.yaml 创建如下 Ingress YAML文件(ingress-v1.yaml)\napiVersion: extensions/v1beta1 kind: Ingress metadata: name: my-app labels: app: my-app spec: rules: - host: my-app-898e6352eda2ac9c5cfc5d51c155455b.ca2ef983cc3a64042a32c1a423d32021e.cn-shanghai.alicontainer.com http: paths: - backend: serviceName: my-app-v1 servicePort: 80 path: / 执行如下命令部署 Ingress 资源\nkubectl apply -f ingress-v1.yaml 部署完成后通过 curl 命令进行测试:\ncurl my-app-898e6352eda2ac9c5cfc5d51c155455b.ca2ef983cc3a64042a32c1a423d32021e.cn-shanghai.alicontainer.com 会看到如下返回:\nHost: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 Step 3 :部署 Deployment V2 应用 # 创建如下 YAML 文件(app-v2.yaml)\napiVersion: v1 kind: Service metadata: name: my-app-v2 labels: app: my-app spec: ports: - name: http port: 80 targetPort: http selector: app: my-app version: v2.0.0 --- apiVersion: apps/v1 kind: Deployment metadata: name: my-app-v2 labels: app: my-app spec: replicas: 1 selector: matchLabels: app: my-app version: v2.0.0 template: metadata: labels: app: my-app version: v2.0.0 annotations: prometheus.io/scrape: \u0026#34;true\u0026#34; prometheus.io/port: \u0026#34;9101\u0026#34; spec: containers: - name: my-app image: registry.cn-hangzhou.aliyuncs.com/containerdemo/containersol-k8s-deployment-strategies ports: - name: http containerPort: 8080 - name: probe containerPort: 8086 env: - name: VERSION value: v2.0.0 livenessProbe: httpGet: path: /live port: probe initialDelaySeconds: 5 periodSeconds: 5 readinessProbe: httpGet: path: /ready port: probe periodSeconds: 5 执行如下命令部署 Deployement V2 应用:\nkubectl apply -f app-v2.yaml Step 4 :按照权重策略灰度到 Deployment V2 应用 # 创建如下 Ingress YAML文件(ingress-v2-canary-weigth.yaml)\napiVersion: extensions/v1beta1 kind: Ingress metadata: name: my-app-canary labels: app: my-app annotations: # Enable canary and send 10% of traffic to version 2 nginx.ingress.kubernetes.io/canary: \u0026#34;true\u0026#34; nginx.ingress.kubernetes.io/canary-weight: \u0026#34;10\u0026#34; spec: rules: - host: my-app-898e6352eda2ac9c5cfc5d51c155455b.ca2ef983cc3a64042a32c1a423d32021e.cn-shanghai.alicontainer.com http: paths: - backend: serviceName: my-app-v2 servicePort: 80 path: / 执行如下命令部署 Ingress 资源\nkubectl apply -f ingress-v2-canary-weigth.yaml 执行如下命令进行测试:\nwhile sleep 0.1;do curl my-app-898e6352eda2ac9c5cfc5d51c155455b.ca2ef983cc3a64042a32c1a423d32021e.cn-shanghai.alicontainer.com; done 测试结果如下:\nHost: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 Host: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 Host: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 Host: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 Host: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 Host: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 Host: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 Host: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 Host: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 Host: my-app-v2-67c69b8857-g82gr, Version: v2.0.0 Host: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 Step 5 :按照 Header 策略灰度到 Deployment V2 应用 # 创建如下 Ingress YAML文件(ingress-v2-canary-header.yaml)\napiVersion: extensions/v1beta1 kind: Ingress metadata: name: my-app-canary labels: app: my-app annotations: # Enable canary and send traffic with headder x-app-canary to version 2 nginx.ingress.kubernetes.io/canary: \u0026#34;true\u0026#34; nginx.ingress.kubernetes.io/canary-by-header: \u0026#34;x-app-canary\u0026#34; nginx.ingress.kubernetes.io/canary-by-header-value: \u0026#34;true\u0026#34; spec: rules: - host: my-app-898e6352eda2ac9c5cfc5d51c155455b.ca2ef983cc3a64042a32c1a423d32021e.cn-shanghai.alicontainer.com http: paths: - backend: serviceName: my-app-v2 servicePort: 80 执行如下命令部署 Ingress 资源\nkubectl apply -f ingress-v2-canary-header.yaml 通过 curl 命令对应用进行测试:\ncurl my-app-898e6352eda2ac9c5cfc5d51c155455b.ca2ef983cc3a64042a32c1a423d32021e.cn-shanghai.alicontainer.com 结果如下:\nHost: my-app-v1-68bfcbb7cf-w4rpg, Version: v1.0.0 curl my-app-898e6352eda2ac9c5cfc5d51c155455b.ca2ef983cc3a64042a32c1a423d32021e.cn-shanghai.alicontainer.com -H \u0026#34;x-app-canary: true\u0026#34; 结果如下\nHost: my-app-v2-67c69b8857-g82gr, Version: v2.0.0 « Informer\n» 安装 Kubernetes\n"},{"id":127,"href":"/kubernetes/installation/","title":"Installation","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 安装 Kubernetes\n安装 Kubernetes # « 通过 Ingress 进行灰度发布\n» K3s\n"},{"id":128,"href":"/kubernetes/k3s/","title":"K3s","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / K3s\nK3s # k3s 是一款轻量级的 Kubernetes 发行版,专为物联网及边缘计算设计。\n要求 # K3s 有望在大多数现代 Linux 系统上运行。\n规格要求:\nCPU: 1 核 Memory:512M 端口要求:\nK3s Server 节点的入站规则如下:\n协议 端口 源 描述 TCP 6443 K3s agent 节点 Kubernetes API Server UDP 8472 K3s server 和 agent 节点 仅对 Flannel VXLAN 需要 UDP 51820 K3s server 和 agent 节点 只有 Flannel Wireguard 后端需要 UDP 51821 K3s server 和 agent 节点 只有使用 IPv6 的 Flannel Wireguard 后端才需要 TCP 10250 K3s server 和 agent 节点 Kubelet metrics TCP 2379-2380 K3s server 节点 只有嵌入式 etcd 高可用才需要 启动 # curl -sfL https://rancher-mirror.oss-cn-beijing.aliyuncs.com/k3s/k3s-install.sh | INSTALL_K3S_MIRROR=cn sh - # Check for Ready node, takes maybe 30 seconds k3s kubectl get node 梧桐 Region 安装 k3s # 安装前提:\ndocker\nhelm 3.0+\n80,443,6060,6443,7070,8443 端口可访问\nnfs sudo apt install nfs-common -y\ncurl -sfL https://rancher-mirror.oss-cn-beijing.aliyuncs.com/k3s/k3s-install.sh | INSTALL_K3S_VERSION=\u0026#34;v1.22.6+k3s1\u0026#34; INSTALL_K3S_MIRROR=cn sh -s - server --docker --disable traefik 使用 \u0026ndash;docker 参数指定 docker 作为容器引擎:k3s 默认使用 docker 作为容器引擎,但是目前梧桐 PaaS 还不支持;\n使用 \u0026ndash;disable traefik 关闭 k3s 默认的 ingress controller,梧桐 PaaS 默认使用 wt-gateway,避免端口冲突。\n默认生成的k3s 配置文件位置:/etc/rancher/k3s/k3s.yaml,可以将其拷贝至 ~/.kube/config,以便 helm 等工具可以默认连接到 k3s。\n自动部署清单:/var/lib/rancher/k3s/server/manifests/\n卸载 # /usr/local/bin/k3s-uninstall.sh 汇总 # k3s 默认使用的 local-path 存储不支持 RWX 模式,所以还是得搭建存储服务 nfs 或者 longhorn; « 安装 Kubernetes\n» Kubernetes 0-1 K8s部署coredns\n"},{"id":129,"href":"/kubernetes/k8s-deploy-coredns/","title":"K8s Deploy Coredns","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 K8s部署coredns\nKubernetes 0-1 K8s部署coredns # 在K8s集群未部署DNS之前,K8s中运行的Pod是无法访问外部网络的,因为无法完成域名解析。\n比如我们运行一个busybox的Pod,然后在Pod里面是无法ping通外部网络的:\n[root@k8s-master01 ~]# kubectl run -it --rm busybox --image=busybox sh If you don\u0026#39;t see a command prompt, try pressing enter. / # ping www.baidu.com ping: bad address \u0026#39;www.baidu.com\u0026#39; 我们可以通过在K8s中部署coredns解决这一问题。\n准备coredns.yaml文件,写入文件内容:\napiVersion: v1 kind: ServiceAccount metadata: name: coredns namespace: kube-system labels: kubernetes.io/cluster-service: \u0026#34;true\u0026#34; addonmanager.kubernetes.io/mode: Reconcile --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: labels: kubernetes.io/bootstrapping: rbac-defaults addonmanager.kubernetes.io/mode: Reconcile name: system:coredns rules: - apiGroups: - \u0026#34;\u0026#34; resources: - endpoints - services - pods - namespaces verbs: - list - watch - apiGroups: - \u0026#34;\u0026#34; resources: - nodes verbs: - get --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: annotations: rbac.authorization.kubernetes.io/autoupdate: \u0026#34;true\u0026#34; labels: kubernetes.io/bootstrapping: rbac-defaults addonmanager.kubernetes.io/mode: EnsureExists name: system:coredns roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: system:coredns subjects: - kind: ServiceAccount name: coredns namespace: kube-system --- apiVersion: v1 kind: ConfigMap metadata: name: coredns namespace: kube-system labels: addonmanager.kubernetes.io/mode: EnsureExists data: Corefile: | .:53 { errors health { lameduck 5s } ready kubernetes cluster.local in-addr.arpa ip6.arpa { pods insecure fallthrough in-addr.arpa ip6.arpa ttl 30 } prometheus :9153 forward . /etc/resolv.conf cache 30 loop reload loadbalance } --- apiVersion: apps/v1 kind: Deployment metadata: name: coredns namespace: kube-system labels: k8s-app: kube-dns kubernetes.io/cluster-service: \u0026#34;true\u0026#34; addonmanager.kubernetes.io/mode: Reconcile kubernetes.io/name: \u0026#34;CoreDNS\u0026#34; spec: # replicas: not specified here: # 1. In order to make Addon Manager do not reconcile this replicas parameter. # 2. Default is 1. # 3. Will be tuned in real time if DNS horizontal auto-scaling is turned on. strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 1 selector: matchLabels: k8s-app: kube-dns template: metadata: labels: k8s-app: kube-dns annotations: seccomp.security.alpha.kubernetes.io/pod: \u0026#39;runtime/default\u0026#39; spec: priorityClassName: system-cluster-critical serviceAccountName: coredns tolerations: - key: \u0026#34;CriticalAddonsOnly\u0026#34; operator: \u0026#34;Exists\u0026#34; nodeSelector: kubernetes.io/os: linux containers: - name: coredns image: k8s.gcr.io/coredns:1.6.7 imagePullPolicy: IfNotPresent resources: limits: memory: 160Mi requests: cpu: 100m memory: 70Mi args: [ \u0026#34;-conf\u0026#34;, \u0026#34;/etc/coredns/Corefile\u0026#34; ] volumeMounts: - name: config-volume mountPath: /etc/coredns readOnly: true ports: - containerPort: 53 name: dns protocol: UDP - containerPort: 53 name: dns-tcp protocol: TCP - containerPort: 9153 name: metrics protocol: TCP livenessProbe: httpGet: path: /health port: 8080 scheme: HTTP initialDelaySeconds: 60 timeoutSeconds: 5 successThreshold: 1 failureThreshold: 5 readinessProbe: httpGet: path: /ready port: 8181 scheme: HTTP securityContext: allowPrivilegeEscalation: false capabilities: add: - NET_BIND_SERVICE drop: - all readOnlyRootFilesystem: true dnsPolicy: Default volumes: - name: config-volume configMap: name: coredns items: - key: Corefile path: Corefile --- apiVersion: v1 kind: Service metadata: name: kube-dns namespace: kube-system annotations: prometheus.io/port: \u0026#34;9153\u0026#34; prometheus.io/scrape: \u0026#34;true\u0026#34; labels: k8s-app: kube-dns kubernetes.io/cluster-service: \u0026#34;true\u0026#34; addonmanager.kubernetes.io/mode: Reconcile kubernetes.io/name: \u0026#34;CoreDNS\u0026#34; spec: selector: k8s-app: kube-dns clusterIP: 10.0.0.2 ports: - name: dns port: 53 protocol: UDP - name: dns-tcp port: 53 protocol: TCP - name: metrics port: 9153 protocol: TCP 运行命令:\nkubectl apply -f coredns.yaml 查看coredns部署情况:\n[root@k8s-master01 ~]# kubectl get pod -A NAMESPACE NAME READY STATUS RESTARTS AGE kube-system coredns-6879cc89dc-ngsld 1/1 Running 0 3m kube-system kube-flannel-ds-amd64-bgjt4 1/1 Running 1 6d14h kube-system kube-flannel-ds-amd64-dqlkc 1/1 Running 1 6d14h 可以看到coredns的Pod已经启动,这是我们再次测试Pod内部的网络访问情况:\n[root@k8s-master01 ~]# kubectl run -it --rm busybox --image=busybox sh If you don\u0026#39;t see a command prompt, try pressing enter. / # ping www.baidu.com PING www.baidu.com (14.215.177.39): 56 data bytes 64 bytes from 14.215.177.39: seq=0 ttl=127 time=14.441 ms 64 bytes from 14.215.177.39: seq=1 ttl=127 time=26.355 ms ... 可以正常解析。\n« K3s\n» Kubernetes 0-1 K8s部署Dashboard\n"},{"id":130,"href":"/kubernetes/k8s-deploy-dashboard/","title":"K8s Deploy Dashboard","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 K8s部署Dashboard\nKubernetes 0-1 K8s部署Dashboard # 首先下载部署的必要文件:\nwget https://raw.githubusercontent.com/kubernetes/dashboard/master/aio/deploy/recommended.yaml -O kube-dash.yaml --no-check-certificate 默认Dashboard的Service类型是ClusterIP,我们集群外面不方便访问,我们最好是将Service类型修改为NodePoart或LoadBalancer(前提是你的集群支持LoadBalancer),以LoadBalancer为例。\n修改文件kube-dash.yaml文件,将kubernetes-dashboard Service部分修改成如下:\nkind: Service apiVersion: v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard spec: type: LoadBalancer ports: - port: 443 targetPort: 8443 selector: k8s-app: kubernetes-dashboard 创建kube-dash-admin-user.yaml文件:\nvim kube-dash-admin-user.yaml 写入如下内容:\napiVersion: v1 kind: ServiceAccount metadata: name: admin-user namespace: kubernetes-dashboard --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: admin-user roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: admin-user namespace: kubernetes-dashboard 执行命令:\nkubectl apply -f kube-dash.yaml # 创建dashboard服务 kubectl apply -f kube-dash-admin-user.yaml # 创建kubernetes集群的管理员角色和账号 执行完之后,我们查看Dashboard的Service:\nkubectl get svc -n kubernetes-dashboard 输出以下内容,可以看到,kubernetes-dashboard的svc的EXTERNAL-IP为192.168.115.141,这就是LoadBalancer为我们自动分配的一个IP。\nNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE dashboard-metrics-scraper ClusterIP 10.0.0.210 \u0026lt;none\u0026gt; 8000/TCP 52s kubernetes-dashboard LoadBalancer 10.0.0.51 192.168.115.141 443:31385/TCP 52s 这时我们以https://192.168.115.141访问部署的dashboard,第一次访问可能需要点击 Advanced =\u0026gt; **Proceed to 192.168.115.141 (unsafe)**进入。\n注意:必须以https方式访问,因为dashboard是默认开启更为安全的https通信。\n需要使用Token登录,使用如下命令获取token:\nkubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep admin-user | awk \u0026#39;{print $1}\u0026#39;) 输出内容,获取到token。\nName: admin-user-token-l56hp Namespace: kubernetes-dashboard Labels: \u0026lt;none\u0026gt; Annotations: kubernetes.io/service-account.name: admin-user kubernetes.io/service-account.uid: 95db28c5-4951-4aae-bf59-b0c26c8b35c7 Type: kubernetes.io/service-account-token Data ==== ca.crt: 1375 bytes namespace: 20 bytes token: eyJhbGciOiJSUzI1NiIsImtpZCI6IjYtQW51Z2xPMi1WTmpEZEtIX3BBYXd1YWpGLVU2Y0J0S1dmZE9lR3hoYU0ifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi11c2VyLXRva2VuLWw1NmhwIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImFkbWluLXVzZXIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI5NWRiMjhjNS00OTUxLTRhYWUtYmY1OS1iMGMyNmM4YjM1YzciLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZXJuZXRlcy1kYXNoYm9hcmQ6YWRtaW4tdXNlciJ9.Dqnk21CcBWU7SHgKRUu8uebL1djF1BJChNT5qTvk1uGLTF3AN9RMmacXlzO2xC5fP3zasmBFjcmS_JY76k7CS6DHdHKgxgB8vlEfIbh-i4YTA7cKK3_Ko5hAy7e6GhoPsfcYnV5QVec2mlvfMoozJT62UT62YkNrfUZXwFz02V4EfNgCgWVPKgiKzciMVOMNJ6-FKiiXyfhl4zprb8hSPzpc0F2Jd62Ykoltuir74UoByOazAnr7bA9ZTXSf1k8fjUaOUsBh37ap_eHg3Yh2gIcYMBxsp1tV0VVNKJDnVCN-lRBhfUciK93kvxU3I8xjWRv6JUHifCvHUiiWXjGZ8A 注意:默认token的过期时间为900秒(15分钟),为了避免频繁的因为token过期登录问题,可以修改kubernetes-dashboard的Deployment的配置,添加token-ttl参数:\n... args: - --auto-generate-certificates - --token-ttl=43200 ... 拿到token,拷贝到dashboard进行登录。登入后,可以看到K8s的资源信息。\nKubernetes-Dashboard部署完成。\n« Kubernetes 0-1 K8s部署coredns\n» Kubernetes 0-1 K8s部署EFK\n"},{"id":131,"href":"/kubernetes/k8s-deploy-efk/","title":"K8s Deploy Efk","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 K8s部署EFK\nKubernetes 0-1 K8s部署EFK # 写在前面 # 本篇目标是在K8s集群中搭建EFK。\nEFK是由ElasticSearch,Fluentd,Kibane组成的一套目前比较主流的日志监控系统,使用EFK监控应用日志,可以让开发人员在一个统一的入口查看日志然后分析应用运行情况。\nEFK简单的工作原理可以参考下图。通过fluentd的agent收集日志数据,写入es,kibana从es中读取日志数据展示到ui。\n部署ElasticSearch # 最好选择部署一个ES集群,这样你的ES可用性更高一点。\n采用StatefulSet部署ES。\n编写es-statefulSet.yaml文件如下:\napiVersion: apps/v1 kind: StatefulSet metadata: name: es-cluster namespace: dev spec: serviceName: elasticsearch replicas: 3 selector: matchLabels: app: elasticsearch template: metadata: labels: app: elasticsearch spec: containers: - name: elasticsearch image: docker.elastic.co/elasticsearch/elasticsearch:7.7.0 resources: limits: cpu: 1000m requests: cpu: 100m ports: - containerPort: 9200 name: rest protocol: TCP - containerPort: 9300 name: inter-node protocol: TCP volumeMounts: - name: data mountPath: /usr/share/elasticsearch/data env: - name: cluster.name value: k8s-logs - name: node.name valueFrom: fieldRef: fieldPath: metadata.name - name: discovery.seed_hosts value: \u0026#34;es-cluster-0.elasticsearch,es-cluster-1.elasticsearch,es-cluster-2.elasticsearch\u0026#34; - name: cluster.initial_master_nodes value: \u0026#34;es-cluster-0,es-cluster-1,es-cluster-2\u0026#34; - name: ES_JAVA_OPTS value: \u0026#34;-Xms512m -Xmx512m\u0026#34; initContainers: - name: fix-permissions image: busybox command: [\u0026#34;sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;chown -R 1000:1000 /usr/share/elasticsearch/data\u0026#34;] securityContext: privileged: true volumeMounts: - name: data mountPath: /usr/share/elasticsearch/data - name: increase-vm-max-map image: busybox command: [\u0026#34;sysctl\u0026#34;, \u0026#34;-w\u0026#34;, \u0026#34;vm.max_map_count=262144\u0026#34;] securityContext: privileged: true - name: increase-fd-ulimit image: busybox command: [\u0026#34;sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;ulimit -n 65536\u0026#34;] securityContext: privileged: true volumeClaimTemplates: - metadata: name: data labels: app: elasticsearch spec: accessModes: [\u0026#34;ReadWriteOnce\u0026#34;] storageClassName: gp2 resources: requests: storage: 40Gi 编写es.service.yaml文件如下:\nkind: Service apiVersion: v1 metadata: name: elasticsearch namespace: dev spec: selector: app: elasticsearch type: NodePort ports: - name: elasticsearch-http port: 9200 targetPort: 9200 部署Kibana # 编写kibana-deployment.yaml文件如下:\napiVersion: extensions/v1beta1 kind: Deployment metadata: name: kibana namespace: dev labels: name: kibana spec: strategy: rollingUpdate: maxSurge: 1 maxUnavailable: 1 type: RollingUpdate template: metadata: labels: name: kibana spec: containers: - image: docker.elastic.co/kibana/kibana:7.7.0 imagePullPolicy: Always name: kibana resources: requests: cpu: \u0026#34;1000m\u0026#34; memory: \u0026#34;256M\u0026#34; limits: cpu: \u0026#34;1000m\u0026#34; memory: \u0026#34;1024M\u0026#34; # livenessProbe: # httpGet: # path: /_status/healthz # port: 5000 # initialDelaySeconds: 90 # timeoutSeconds: 10 # readinessProbe: # httpGet: # path: /_status/healthz # port: 5000 # initialDelaySeconds: 30 # timeoutSeconds: 10 env: - name: ELASTICSEARCH_URL value: http://elasticsearch:9200 - name: SERVER_BASEPATH value: /kibana - name: SERVER_REWRITEBASEPATH value: \u0026#34;true\u0026#34; # args: # - server.rewriteBasePath=true # - server.basePath=/kibana ports: - containerPort: 5601 name: kibana-port # volumeMounts: # - mountPath: /etc/kibana/config # name: grafana-data # volumes: # - name: grafana-data # configMap: # name: grafana-config restartPolicy: Always 编写kibana-service.yaml文件如下:\nkind: Service apiVersion: v1 metadata: name: kibana namespace: dev spec: selector: name: kibana type: NodePort ports: - name: kibana-http port: 5601 targetPort: 5601 部署Fluentd # Fluentd是一个开源的数据收集器,可以做数据的集中收集,便于做数据使用和分析,常用于日志收集。\n我们部署Fluentd来收集部署在k8s Pod中的程序的话,首先需要集群赋予它访问Pod的权限,因为我们需要为fluentd分配一个带有Pod相关权限的serviceAccount。\n编写fluentd-serviceAccount.yaml文件如下:\napiVersion: v1 kind: ServiceAccount metadata: name: fluentd namespace: dev labels: app: fluentd --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: fluentd namespace: dev labels: app: fluentd rules: - apiGroups: - \u0026#34;\u0026#34; resources: - pods - namespaces verbs: - get - list - watch --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: fluentd namespace: dev roleRef: kind: ClusterRole name: fluentd apiGroup: rbac.authorization.k8s.io subjects: - kind: ServiceAccount name: fluentd namespace: dev 我们需要在每台节点上都部署Fluent,这相当于是一个日志收集的Agent,因此我们采用DaemonSet的方式部署Fluentd。\n编写fluentd-daemonSet.yaml文件如下:\napiVersion: apps/v1 kind: DaemonSet metadata: name: fluentd namespace: dev labels: app: fluentd spec: selector: matchLabels: app: fluentd template: metadata: labels: app: fluentd spec: serviceAccount: fluentd serviceAccountName: fluentd tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule containers: - name: fluentd image: fluent/fluentd-kubernetes-daemonset:v1.10.4-debian-elasticsearch7-1.0 env: - name: FLUENT_ELASTICSEARCH_HOST value: \u0026#34;elasticsearch.dev.svc.cluster.local\u0026#34; - name: FLUENT_ELASTICSEARCH_PORT value: \u0026#34;9200\u0026#34; - name: FLUENT_ELASTICSEARCH_SCHEME value: \u0026#34;http\u0026#34; - name: FLUENTD_SYSTEMD_CONF value: disable resources: limits: memory: 512Mi requests: cpu: 100m memory: 256Mi volumeMounts: - name: varlog mountPath: /var/log - name: varlibdockercontainers mountPath: /var/lib/docker/containers readOnly: true terminationGracePeriodSeconds: 30 volumes: - name: varlog hostPath: path: /var/log - name: varlibdockercontainers hostPath: path: /var/lib/docker/containers 文件准备好之后,执行\nkubectl apply -f ./fluentd-serviceAccount.yaml kubectl apply -f ./fluentd-daemonSet.yaml 部署之后,查看fluentd daemonset的部署情况\nkubectl get ds -n kube-system 输出信息大致如下:\nNAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE fluentd 3 3 3 3 3 \u0026lt;none\u0026gt; 33s 如果DESIRED和READY列值不一致的话,说明是某个Node上的Pod启用失败了。那么可以查看Pod的启用情况:\nkubectl get pod -n kube-system # 假设Pod fluentd-8hmbd一直未成功启用,使用kubectl describe 或kubectl logs命令检查 kubectl describe pod fluentd-8hmbd -n kube-system kubectl logs fluentd-8hmbd -n kube-system 参考资料 # https://www.digitalocean.com/community/tutorials/how-to-set-up-an-elasticsearch-fluentd-and-kibana-efk-logging-stack-on-kubernetes « Kubernetes 0-1 K8s部署Dashboard\n» 可能需要运行多次以下命令,确保k8s资源都创建\n"},{"id":132,"href":"/kubernetes/k8s-deploy-prometheus-grafana/","title":"K8s Deploy Prometheus Grafana","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 可能需要运行多次以下命令,确保k8s资源都创建\nStep # 下载相关k8s资源文件 git clone https://github.com/coreos/kube-prometheus.git 修改文件kube-prometheus/manifests/prometheus-prometheus.yaml,做这一步的目的是为prometheus的访问分配子路径,访问方式为http(s)://xxx/prometheus\n在prometheus.spec下添加\nexternalUrl: prometheus routePrefix: prometheus 修改文件kube-prometheus/manifests/grafana-deployment.yaml,做这一步的目的是为grafana的访问分配子路径,访问方式为:http(s)://xxx/grafana\n在deployment.spec.template.spec.container[0]下添加\nenv: - name: GF_SERVER_ROOT_URL value: \u0026#34;http://localhost:3000/grafana\u0026#34; - name: GF_SERVER_SERVE_FROM_SUB_PATH value: \u0026#34;true\u0026#34; Apply k8s资源 # 可能需要运行多次以下命令,确保k8s资源都创建 kubectl create -f manifests/setup -f manifests # !如果要删除以上创建的k8s资源,运行以下命令 kubectl delete --ignore-not-found=true -f manifests/ -f manifests/setup Ingress转发 apiVersion: extensions/v1beta1 kind: Ingress metadata: name: prometheus namespace: monitoring spec: rules: - host: dp.example.tech http: paths: - path: /prometheus backend: serviceName: prometheus-k8s servicePort: 9090 - path: /grafana backend: serviceName: grafana servicePort: 3000 - path: /alertmanager backend: serviceName: alertmanager-main servicePort: 9093 « Kubernetes 0-1 K8s部署EFK\n» Kubernetes 0-1 K8s部署Zookeeper和Kafka\n"},{"id":133,"href":"/kubernetes/k8s-deploy-zookeeper-kafka/","title":"K8s Deploy Zookeeper Kafka","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 K8s部署Zookeeper和Kafka\nKubernetes 0-1 K8s部署Zookeeper和Kafka # 按照官方定义,Kafka是一个分布式的流处理平台。更多了解官方文档: http://kafka.apachecn.org/intro.html\n那么直接开始在K8s中部署kafka吧。\n部署kafka,首先要有一个可用的Zookeeper集群,所以我们还需要先部署一个Zookeeper集群。\n首先说明,我的K8s集群是使用的AWS的EKS服务,与自建的K8s集群的配置方面可能会有所差别。\n部署Zookeeper # 编写zookeeper-statefulSet.yaml文件:\nvim zookeeper-statefulSet.yaml 写入内容:\nkind: StatefulSet apiVersion: apps/v1beta1 metadata: name: zookeeper-1 namespace: dev spec: serviceName: zookeeper-1 replicas: 1 selector: matchLabels: app: zookeeper-1 template: metadata: labels: app: zookeeper-1 spec: containers: - name: zookeeper image: digitalwonderland/zookeeper ports: - containerPort: 2181 env: - name: ZOOKEEPER_ID value: \u0026#34;1\u0026#34; - name: ZOOKEEPER_SERVER_1 value: zookeeper-1 - name: ZOOKEEPER_SERVER_2 value: zookeeper-2 - name: ZOOKEEPER_SERVER_3 value: zookeeper-3 volumeMounts: - name: zookeeper-data mountPath: \u0026#34;/var/lib/zookeeper/data\u0026#34; subPath: zookeeper volumeClaimTemplates: - metadata: name: zookeeper-data labels: app: zookeeper-1 spec: accessModes: [\u0026#34;ReadWriteOnce\u0026#34;] storageClassName: gp2 resources: requests: storage: 30Gi --- kind: StatefulSet apiVersion: apps/v1beta1 metadata: name: zookeeper-2 namespace: dev spec: serviceName: zookeeper-2 replicas: 1 selector: matchLabels: app: zookeeper-2 template: metadata: labels: app: zookeeper-2 spec: containers: - name: zookeeper image: digitalwonderland/zookeeper ports: - containerPort: 2181 env: - name: ZOOKEEPER_ID value: \u0026#34;2\u0026#34; - name: ZOOKEEPER_SERVER_1 value: zookeeper-1 - name: ZOOKEEPER_SERVER_2 value: zookeeper-2 - name: ZOOKEEPER_SERVER_3 value: zookeeper-3 volumeMounts: - name: zookeeper-data mountPath: \u0026#34;/var/lib/zookeeper/data\u0026#34; subPath: zookeeper-data volumeClaimTemplates: - metadata: name: zookeeper-data labels: app: zookeeper-2 spec: accessModes: [\u0026#34;ReadWriteOnce\u0026#34;] storageClassName: gp2 resources: requests: storage: 30Gi --- kind: StatefulSet apiVersion: apps/v1beta1 metadata: name: zookeeper-3 namespace: dev spec: serviceName: zookeeper-3 replicas: 1 selector: matchLabels: app: zookeeper-3 template: metadata: labels: app: zookeeper-3 spec: containers: - name: zookeeper image: digitalwonderland/zookeeper ports: - containerPort: 2181 env: - name: ZOOKEEPER_ID value: \u0026#34;3\u0026#34; - name: ZOOKEEPER_SERVER_1 value: zookeeper-1 - name: ZOOKEEPER_SERVER_2 value: zookeeper-2 - name: ZOOKEEPER_SERVER_3 value: zookeeper-3 volumeMounts: - name: zookeeper-data mountPath: \u0026#34;/var/lib/zookeeper/data\u0026#34; subPath: zookeeper volumeClaimTemplates: - metadata: name: zookeeper-data labels: app: zookeeper-3 spec: accessModes: [\u0026#34;ReadWriteOnce\u0026#34;] storageClassName: gp2 resources: requests: storage: 30Gi 编写zookeeper-service.yaml文件:\nvim zookeeper-service.yaml 写入内容:\napiVersion: v1 kind: Service metadata: name: zookeeper-1 namespace: dev labels: app: zookeeper-1 spec: ports: - name: client port: 2181 protocol: TCP - name: follower port: 2888 protocol: TCP - name: leader port: 3888 protocol: TCP selector: app: zookeeper-1 --- apiVersion: v1 kind: Service metadata: name: zookeeper-2 namespace: dev labels: app: zookeeper-2 spec: ports: - name: client port: 2181 protocol: TCP - name: follower port: 2888 protocol: TCP - name: leader port: 3888 protocol: TCP selector: app: zookeeper-2 --- apiVersion: v1 kind: Service metadata: name: zookeeper-3 namespace: dev labels: app: zookeeper-3 spec: ports: - name: client port: 2181 protocol: TCP - name: follower port: 2888 protocol: TCP - name: leader port: 3888 protocol: TCP selector: app: zookeeper-3 完成以上两个文件后,执行kubectl命令即可:\nkubectl apply -f zookeeper-statefulSet.yaml kubectl apply -f zookeeper-service.yaml 部署Kafka # 部署Kafka Connect # wget https://raw.githubusercontent.com/strimzi/strimzi-kafka-operator/0.16.1/examples/kafka/kafka-persistent-single.yaml 安装集群\nwget https://github.com/strimzi/strimzi-kafka-operator/releases/download/0.18.0/strimzi-cluster-operator-0.18.0.yaml 可能该文件下载下来你需要修改其中的namespace。\n« 可能需要运行多次以下命令,确保k8s资源都创建\n» Kubernetes 定制开发 01:K8s API 概念\n"},{"id":134,"href":"/kubernetes/k8s-dev-01-api-concept/","title":"K8s Dev 01 API Concept","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 定制开发 01:K8s API 概念\nKubernetes 定制开发 01:K8s API 概念 # 在 K8s 集群中,API 是一切的基础,K8s 的所有资源对象都是通过 API 来管理的,所以我们在定制开发的时候,首先要了解 K8s 的 API 概念。\n基本概念 # Group(G):\nAPI 组,例如:apps、networking.k8s.io 等\nVersion(V):\nAPI 版本,例如:v1alpha1、v1、v2 等\nResource(R):\nAPI 资源,例如:pods,configmaps 等\nKind(K):\nAPI 类型,例如:Deployment,Service 等 通过 kubectl api-versions 获取集群中所有 API 的版本列表:\n$ kubectl api-versions acme.cert-manager.io/v1 admissionregistration.k8s.io/v1 apiextensions.k8s.io/v1 apiregistration.k8s.io/v1 apps/v1 authentication.k8s.io/v1 通过 kubectl api-resources 命令获取集群所有 API 的资源列表,并且可以看到资源的简写名称,版本以及类型:\n$ kubectl api-resources NAME SHORTNAMES APIVERSION NAMESPACED KIND bindings v1 true Binding componentstatuses cs v1 false ComponentStatus configmaps cm v1 true ConfigMap endpoints ep v1 true Endpoints events ev v1 true Event limitranges limits v1 true LimitRange namespaces ns v1 false Namespace nodes no v1 false Node API 资源端点 # GVR 端点:\n/api,只会获取到 v1 的 APIVersion(组名为空) /apis,获取到所有非核心 API 的 APIGroupList /api/v1,获取到 APIResourceList /apis/{g}/,获取到 APIGroup /apis/{g}/v1,获取到 APIResourceList 命名空间资源端点:\n核心 api:/api/{v}/namespaces/{ns}/{r},例如:/api/v1/namespaces/default/configmaps 其他 api:/apis/{g}/{v}/namespaces/{ns}/{r},例如:apis/apps/v1/namespaces/default/deployments 集群资源端点:\n核心 api:/api/{v}/{r},例如:/api/v1/nodes 其他资源:/apis/{g}/{v}/{r},例如:/apis/rbac.authorization.k8s.io/v1/clusterroles 在使用 kubectl get 命令获取集群资源实例时,可以通过添加 -v 6 查看执行命令的请求端点:\n$ k get nodes -v 6 I0915 11:32:43.715795 18197 loader.go:373] Config loaded from file: /Users/dp/.kube/config I0915 11:32:43.855325 18197 round_trippers.go:553] GET https://127.0.01:6443/api/v1/nodes?limit=500 200 OK in 135 milliseconds NAME STATUS ROLES AGE VERSION local Ready control-plane 115d v1.27.2 APIService # 每个 API 版本都对应一个 APIService:\n$ k api-versions | wc -l 30 $ k get apiservices.apiregistration.k8s.io| wc -l 30 创建一个 CRD # foo-crd.yaml:\napiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: # 固定格式 {kind_plural}.{group},其中 foos 对应 spec.names.plural,play.ketches.io 对应 spec.group name: foos.play.ketches.io spec: group: play.ketches.io # 资源组,用在 URL 标识资源所属 Group,如 /apis/play.ketches.io/v1/foos 之 foos.play.ketches.io names: kind: Foo listKind: FooList plural: foos # 资源名复数,用在 URL 标识资源类型,如 /apis/play.ketches.io/v1/foos 之 foos singular: foo # 资源名单数,可用于 kubectl 匹配资源 shortNames: # 资源简称,可用于 kubectl 匹配资源 - fo scope: Namespaced # Namespaced/Cluster versions: - name: v1 served: true # 是否启用该版本,可使用该标识启动/禁用该版本 API storage: true # 唯一落存储版本,如果 CRD 含有多个版本,只能有一个版本被标识为 true schema: openAPIV3Schema: type: object properties: spec: type: object required: [\u0026#34;msg\u0026#34;] # 必须赋值 properties: msg: type: string maxLength: 6 additionalPrinterColumns: # 声明 kubectl get 输出列,默认在 name 列之外额外输出 age 列,改为额外输出 age 列,message 列 - name: age jsonPath: .metadata.creationTimestamp type: date - name: message jsonPath: .spec.msg type: string 获取 CRD 对应的 APIService:\n~ k get apiservices.apiregistration.k8s.io v1.play.ketches.io NAME SERVICE AVAILABLE AGE v1.play.ketches.io Local True 3h26m 一般这时候 SERVICE 为 Local,表示 对该资源的 APIService 请求会在本地处理,也就是 kube-apiserver 处理,我们可以将其改为一个在集群中运行的 Service 名,例如:default/play-v1-svc。你需要修改 APIService 的声明:\napiVersion: apiregistration.k8s.io/v1 kind: APIService metadata: labels: kube-aggregator.kubernetes.io/automanaged: \u0026#34;true\u0026#34; name: v1.play.ketches.io spec: group: play.ketches.io groupPriorityMinimum: 1000 version: v1 versionPriority: 100 service: name: play-v1-svc namespace: default insecureSkipTLSVerify: true 这时候对 play.ketches.io/v1 组下的资源的请求处理会交给该服务。 即:/apis/play.ketches.io/v1/namespaces/{ns}/foos/* =\u0026gt; default/play-v1-svc,其实这就是一个 自定义的 apiserver 了。 但是一个自定义的 apiserver 需要实现以下这些处理(Handler):\nfor API Discovery /apis /apis/play.ketches.io /apis/play.ketches.io/v1 for OpenAPI Schema /openapi/v2 /openapi/v3 for Foo CRUD /apis/play.ketches.io/v1/foos /apis/play.ketches.io/v1/namespaces/{namespace}/foos /apis/play.ketches.io/v1/namespaces/{namespace}/foos/{name} OpenAPI # API 的 Schema 会 kubectl explain 命令输出内容来源于 OpenAPI Spec。\n参考 # K8s CustomResourceDefinitions (CRD) 原理 实现一个极简 K8s apiserver « Kubernetes 0-1 K8s部署Zookeeper和Kafka\n» Kubernetes 定制开发 02:CRD\n"},{"id":135,"href":"/kubernetes/k8s-dev-02-crd/","title":"K8s Dev 02 Crd","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 定制开发 02:CRD\nKubernetes 定制开发 02:CRD # « Kubernetes 定制开发 01:K8s API 概念\n» Kubernetes 定制开发 50:扩展调度器\n"},{"id":136,"href":"/kubernetes/k8s-dev-50-extend-kube-scheduler/","title":"K8s Dev 50 Extend Kube Scheduler","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 定制开发 50:扩展调度器\nKubernetes 定制开发 50:扩展调度器 # 简介 # Kubernetes Scheduler(调度器)是一个控制面进程,负责将 Pods 指派到节点上。调度器基于约束和可用资源为调度队列中每个 Pod 确定其可合法放置的节点。调度器之后对所有合法的节点进行排序,将 Pod 绑定到一个合适的节点。\nkube-scheduler 是 Kubernetes 自带的一个默认调度器,它会根据 Pod 的资源需求和节点的资源容量,将 Pod 调度到合适的节点上。\n如果默认调度器不符合你的需求,你可以实现自己的调度器,并且你的调度器可以和默认调度器或其他调度器一起运行在集群中。你可以通过声明 Pod 的 spec.schedulerName 字段来指定要使用的调度器。\n扩展调度器 # 有三种方式可以实现自定义调度器:\n修改 kube-scheduler 源码调度逻辑,然后编译成定制的调度器镜像,然后使用这个镜像部署调度进程 自定义 Pod 控制器,监听 Pod 的 spec.schedulerName 字段,在 Pod 被创建时,为其绑定节点 使用 Scheduler Extender 的方式,这种方式不需要修改默认调度器的配置文件 编译定制调度器镜像 # 克隆 kubernetes 源码,然后修改 kube-scheduler 源码,然后编译成定制的调度器镜像。\ngit clone https://github.com/kubernetes/kubernetes.git cd kubernetes # 修改源码 make 编写 Dockerfile:\nFROM alpine ADD ./_output/local/bin/linux/amd64/kube-scheduler /usr/local/bin/kube-scheduler 编译并推送镜像:\ndocker build -t poneding/my-kube-scheduler:v1.0 . docker push poneding/my-kube-scheduler:v1.0 编写部署清单文件:\ndeploy-maniest.yaml:\napiVersion: v1 kind: ServiceAccount metadata: name: my-scheduler namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: my-scheduler-as-kube-scheduler subjects: - kind: ServiceAccount name: my-scheduler namespace: kube-system roleRef: kind: ClusterRole name: system:kube-scheduler apiGroup: rbac.authorization.k8s.io --- apiVersion: v1 kind: ConfigMap metadata: name: my-scheduler-config namespace: kube-system data: my-scheduler-config.yaml: | apiVersion: kubescheduler.config.k8s.io/v1 kind: KubeSchedulerConfiguration profiles: - schedulerName: my-scheduler leaderElection: leaderElect: false --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: my-scheduler-as-volume-scheduler subjects: - kind: ServiceAccount name: my-scheduler namespace: kube-system roleRef: kind: ClusterRole name: system:volume-scheduler apiGroup: rbac.authorization.k8s.io --- apiVersion: apps/v1 kind: Deployment metadata: labels: component: scheduler tier: control-plane name: my-scheduler namespace: kube-system spec: selector: matchLabels: component: scheduler tier: control-plane replicas: 1 template: metadata: labels: component: scheduler tier: control-plane spec: serviceAccountName: my-scheduler containers: - command: - /usr/local/bin/kube-scheduler - --config=/etc/kubernetes/my-scheduler/my-scheduler-config.yaml image: poneding/my-kube-scheduler:v1.0 livenessProbe: httpGet: path: /healthz port: 10259 scheme: HTTPS initialDelaySeconds: 15 name: my-scheduler readinessProbe: httpGet: path: /healthz port: 10259 scheme: HTTPS resources: requests: cpu: \u0026#39;0.1\u0026#39; securityContext: privileged: false volumeMounts: - name: config-volume mountPath: /etc/kubernetes/my-scheduler hostNetwork: false hostPID: false volumes: - name: config-volume configMap: name: my-scheduler-config 部署:\nkubectl apply -f deploy-maniest.yaml 测试:\nkubectl run nginx-by-my-scheduler --image=nginx --overrides=\u0026#39;{\u0026#34;spec\u0026#34;:{\u0026#34;schedulerName\u0026#34;:\u0026#34;my-scheduler\u0026#34;}}\u0026#39; kubectl get pod -o wide -w 如果一切正常,将观察到 Pod 将会被正常调度到节点上。\n使用这种方式来扩展调度器,对开发者来说,需要了解调度器的源码然后修改逻辑,有一定的难度。\n自定义调度控制器 # 基于 controller-runtime 包编写一个调度控制器,原理是通过协调 Pod ,选择一个适合的节点,创建 Binding 对象,将 Pod 绑定到指定的节点上。\n创建项目:\nmkdir my-scheduler \u0026amp;\u0026amp; cd my-scheduler go mod init my-scheduler touch main.go 编写 main.go 调度器逻辑(本质是一个 Pod 的协调控制器):\npackage main import ( \u0026#34;context\u0026#34; \u0026#34;log\u0026#34; \u0026#34;math/rand\u0026#34; corev1 \u0026#34;k8s.io/api/core/v1\u0026#34; \u0026#34;k8s.io/apimachinery/pkg/runtime\u0026#34; ctrl \u0026#34;sigs.k8s.io/controller-runtime\u0026#34; \u0026#34;sigs.k8s.io/controller-runtime/pkg/client\u0026#34; \u0026#34;sigs.k8s.io/controller-runtime/pkg/event\u0026#34; \u0026#34;sigs.k8s.io/controller-runtime/pkg/manager\u0026#34; \u0026#34;sigs.k8s.io/controller-runtime/pkg/predicate\u0026#34; ) func main() { mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), manager.Options{}) if err != nil { log.Fatalf(\u0026#34;new manager err: %s\u0026#34;, err.Error()) } err = (\u0026amp;MyScheduler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), }).SetupWithManager(mgr) if err != nil { log.Fatalf(\u0026#34;setup scheduler err: %s\u0026#34;, err.Error()) } err = mgr.Start(context.Background()) if err != nil { log.Fatalf(\u0026#34;start manager err: %s\u0026#34;, err.Error()) } } const mySchedulerName = \u0026#34;my-scheduler\u0026#34; type MyScheduler struct { Client client.Client Scheme *runtime.Scheme } func (s *MyScheduler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { nodes := new(corev1.NodeList) err := s.Client.List(ctx, nodes) if err != nil { return ctrl.Result{Requeue: true}, err } // 随机选择一个节点 targetNode := nodes.Items[rand.Intn(len(nodes.Items))].Name // 创建绑定关系 binding := new(corev1.Binding) binding.Name = req.Name binding.Namespace = req.Namespace binding.Target = corev1.ObjectReference{ Kind: \u0026#34;Node\u0026#34;, APIVersion: \u0026#34;v1\u0026#34;, Name: targetNode, } err = s.Client.Create(ctx, binding) if err != nil { return ctrl.Result{Requeue: true}, err } return ctrl.Result{}, nil } // SetupWithManager sets up the controller with the Manager. func (s *MyScheduler) SetupWithManager(mgr ctrl.Manager) error { // 过滤目标 Pod filter := predicate.Funcs{ CreateFunc: func(e event.CreateEvent) bool { pod, ok := e.Object.(*corev1.Pod) if ok { return pod.Spec.SchedulerName == mySchedulerName \u0026amp;\u0026amp; pod.Spec.NodeName == \u0026#34;\u0026#34; } return false }, UpdateFunc: func(e event.UpdateEvent) bool { return false }, DeleteFunc: func(e event.DeleteEvent) bool { return false }, } return ctrl.NewControllerManagedBy(mgr). For(\u0026amp;corev1.Pod{}). WithEventFilter(filter). Complete(s) } 运行自定义调度器:\ngo run main.go 也可以参考前面的部署方式,先制作一个镜像,然后部署到集群中。\n运行一个 Pod,指定调度器为 my-scheduler:\nkubectl run nginx-by-my-scheduler --image=nginx --overrides=\u0026#39;{\u0026#34;spec\u0026#34;:{\u0026#34;schedulerName\u0026#34;:\u0026#34;my-scheduler\u0026#34;}}\u0026#39; 一切正常的话,将会观察到 Pod 被正常调度到节点上。\nScheduler Extender # 通过 Scheduler Extender 来扩展 Kubernetes 调度器,它将以 Webhook 的形式运行,并且在调度器框架阶段中进行干扰。\n阶段 描述 Filter 调度框架将调用过滤函数,过滤掉不适合被调度的节点。 Priority 调度框架将调用优先级函数,为每个节点计算一个优先级,优先级越高,节点越适合被调度。 Bind 调度框架将调用绑定函数,将 Pod 绑定到一个节点上。 Scheduler Extender 通过 HTTP 请求的方式,将调度框架阶段中的调度决策委托给外部的调度器,然后将调度结果返回给调度框架。我们只需要实现一个 HTTP 服务,然后将其注册到调度器中,就可以实现自定义调度器。在这个 HTTP 服务中,我们可以实现上述阶段中的任意一个或多个阶段的接口,来定制我们的调度需求。\n接口列表:\nFilter 接口 # 接口方法:POST\n接口请求参数:\ntype ExtenderArgs struct { Pod *v1.Pod Nodes *v1.NodeList NodeNames *[]string } 接口请求结果:\ntype ExtenderFilterResult struct { Nodes *v1.NodeList NodeNames *[]string FailedNodes FailedNodesMap FailedAndUnresolvableNodes FailedNodesMap Error string } Priority 接口 # 接口方法:POST\n接口请求参数:和 Filter 接口请求参数一致。\n接口请求结果:\ntype HostPriorityList []HostPriority type HostPriority struct { Host string Score int64 } Bind 接口 # 接口方法:POST\n接口请求参数:\ntype ExtenderBindingArgs struct { PodName string PodNamespace string PodUID types.UID Node string } 接口请求结果:\ntype ExtenderBindingResult struct { Error string } 实现 # 我们使用 Scheduler Extender 的方式来实现自定义调度器,供参考。\nmain.go:\npackage main import ( \u0026#34;encoding/json\u0026#34; \u0026#34;log\u0026#34; \u0026#34;net/http\u0026#34; extenderv1 \u0026#34;k8s.io/kube-scheduler/extender/v1\u0026#34; ) func Filter(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { w.WriteHeader(http.StatusMethodNotAllowed) return } var args extenderv1.ExtenderArgs var result *extenderv1.ExtenderFilterResult err := json.NewDecoder(r.Body).Decode(\u0026amp;args) if err != nil { result = \u0026amp;extenderv1.ExtenderFilterResult{ Error: err.Error(), } } else { result = filter(args) } w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(result); err != nil { log.Printf(\u0026#34;failed to encode result: %v\u0026#34;, err) } } func Prioritize(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { w.WriteHeader(http.StatusMethodNotAllowed) return } var args extenderv1.ExtenderArgs var result *extenderv1.HostPriorityList err := json.NewDecoder(r.Body).Decode(\u0026amp;args) if err != nil { result = \u0026amp;extenderv1.HostPriorityList{} } else { result = prioritize(args) } w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(result); err != nil { log.Printf(\u0026#34;failed to encode result: %v\u0026#34;, err) } } func Bind(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { w.WriteHeader(http.StatusMethodNotAllowed) return } var args extenderv1.ExtenderBindingArgs var result *extenderv1.ExtenderBindingResult err := json.NewDecoder(r.Body).Decode(\u0026amp;args) if err != nil { result = \u0026amp;extenderv1.ExtenderBindingResult{ Error: err.Error(), } } else { result = bind(args) } w.Header().Set(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;) w.WriteHeader(http.StatusOK) if err := json.NewEncoder(w).Encode(result); err != nil { log.Printf(\u0026#34;failed to encode result: %v\u0026#34;, err) } } func main() { http.HandleFunc(\u0026#34;/filter\u0026#34;, Filter) http.HandleFunc(\u0026#34;/priority\u0026#34;, Prioritize) http.HandleFunc(\u0026#34;/bind\u0026#34;, Bind) http.ListenAndServe(\u0026#34;:8080\u0026#34;, nil) } filter.go:没有具体实现节点过滤逻辑,直接返回所有节点。\npackage main import ( \u0026#34;log\u0026#34; extenderv1 \u0026#34;k8s.io/kube-scheduler/extender/v1\u0026#34; ) func filter(args extenderv1.ExtenderArgs) *extenderv1.ExtenderFilterResult { log.Println(\u0026#34;my-scheduler-extender filter called.\u0026#34;) return \u0026amp;extenderv1.ExtenderFilterResult{ Nodes: args.Nodes, NodeNames: args.NodeNames, } } prioritize.go:模拟打分,按照节点顺序给节点累加一个分数。\npackage main import ( \u0026#34;log\u0026#34; extenderv1 \u0026#34;k8s.io/kube-scheduler/extender/v1\u0026#34; ) func prioritize(args extenderv1.ExtenderArgs) *extenderv1.HostPriorityList { log.Println(\u0026#34;my-scheduler-extender prioritize called.\u0026#34;) var result extenderv1.HostPriorityList for i, node := range args.Nodes.Items { result = append(result, extenderv1.HostPriority{ Host: node.Name, Score: int64(i), }) } return \u0026amp;result } bind.go:没有具体实现绑定逻辑,直接返回成功。\npackage main import ( \u0026#34;context\u0026#34; \u0026#34;log\u0026#34; corev1 \u0026#34;k8s.io/api/core/v1\u0026#34; \u0026#34;k8s.io/client-go/kubernetes/scheme\u0026#34; \u0026#34;k8s.io/client-go/rest\u0026#34; extenderv1 \u0026#34;k8s.io/kube-scheduler/extender/v1\u0026#34; ctrl \u0026#34;sigs.k8s.io/controller-runtime\u0026#34; \u0026#34;sigs.k8s.io/controller-runtime/pkg/client\u0026#34; ) var kconfig *rest.Config var kruntimeclient client.Client func init() { kconfig = ctrl.GetConfigOrDie() var err error kruntimeclient, err = client.New(kconfig, client.Options{ Scheme: scheme.Scheme, }) if err != nil { log.Fatalf(\u0026#34;failed to create k8s runtime client: %v\u0026#34;, err) } } func bind(args extenderv1.ExtenderBindingArgs) *extenderv1.ExtenderBindingResult { log.Println(\u0026#34;my-scheduler-extender bind called.\u0026#34;) log.Printf(\u0026#34;pod %s/%s is bind to %s\u0026#34;, args.PodNamespace, args.PodName, args.Node) // 创建绑定关系 binding := new(corev1.Binding) binding.Name = args.PodName binding.Namespace = args.PodNamespace binding.Target = corev1.ObjectReference{ Kind: \u0026#34;Node\u0026#34;, APIVersion: \u0026#34;v1\u0026#34;, Name: args.Node, } result := new(extenderv1.ExtenderBindingResult) err := kruntimeclient.Create(context.Background(), binding) if err != nil { result.Error = err.Error() } return result } 编译成二进制文件:\nGOOS=linux GOARCH=amd64 go build -o my-scheduler-extender 编写 Dockerfile:\nFROM alpine ARG TARGETOS TARGETARCH ADD ./bin/$TARGETOS/$TARGETARCH/my-scheduler-extender /my-scheduler-extender ENTRYPOINT [\u0026#34;/my-scheduler-extender\u0026#34;] 编译并推送镜像:\nGOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o bin/linux/amd64/my-scheduler-extender GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -o bin/linux/arm64/my-scheduler-extender docker buildx build --push --platform linux/amd64,linux/arm64 -t poneding/my-kube-scheduler-extender:v1.0 . 编写部署清单文件,部署清单中包括额外的调度器(参考上述编译定制调度器镜像的方式)和我们开发的 Scheduler Extender:\n注意:为了简化部署清单,给了 my-scheduler-extender 和 my-scheduler-with-extender 容器 cluster-admin 权限,实际上不需要这么高的权限。\ndeploy-manifests.yaml:\napiVersion: v1 kind: ServiceAccount metadata: name: my-scheduler-with-extender namespace: kube-system --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: my-scheduler-with-extender subjects: - kind: ServiceAccount name: my-scheduler-with-extender namespace: kube-system roleRef: kind: ClusterRole name: cluster-admin apiGroup: rbac.authorization.k8s.io --- apiVersion: v1 kind: ConfigMap metadata: name: my-scheduler-with-extender-config namespace: kube-system data: my-scheduler-with-extender-config.yaml: | apiVersion: kubescheduler.config.k8s.io/v1 kind: KubeSchedulerConfiguration profiles: - schedulerName: my-scheduler-with-extender leaderElection: leaderElect: false extenders: - urlPrefix: \u0026#34;http://my-scheduler-extender.kube-system.svc:8080\u0026#34; enableHTTPS: false filterVerb: \u0026#34;filter\u0026#34; prioritizeVerb: \u0026#34;prioritize\u0026#34; bindVerb: \u0026#34;bind\u0026#34; weight: 1 nodeCacheCapable: false --- apiVersion: apps/v1 kind: Deployment metadata: labels: component: my-scheduler-with-extender tier: control-plane name: my-scheduler-with-extender namespace: kube-system spec: selector: matchLabels: component: my-scheduler-with-extender tier: control-plane replicas: 1 template: metadata: labels: component: my-scheduler-with-extender tier: control-plane spec: serviceAccountName: my-scheduler-with-extender containers: - command: - kube-scheduler - --config=/etc/kubernetes/my-scheduler-with-extender/my-scheduler-with-extender-config.yaml image: registry.k8s.io/kube-scheduler:v1.29.0 livenessProbe: httpGet: path: /healthz port: 10259 scheme: HTTPS initialDelaySeconds: 15 name: my-scheduler-with-extender readinessProbe: httpGet: path: /healthz port: 10259 scheme: HTTPS resources: requests: cpu: \u0026#39;0.1\u0026#39; securityContext: privileged: false volumeMounts: - name: config-volume mountPath: /etc/kubernetes/my-scheduler-with-extender hostNetwork: false hostPID: false volumes: - name: config-volume configMap: name: my-scheduler-with-extender-config --- apiVersion: apps/v1 kind: Deployment metadata: labels: component: my-scheduler-extender tier: control-plane name: my-scheduler-extender namespace: kube-system spec: selector: matchLabels: component: my-scheduler-extender tier: control-plane replicas: 1 template: metadata: labels: component: my-scheduler-extender tier: control-plane spec: serviceAccountName: my-scheduler-with-extender containers: - image: poneding/my-kube-scheduler-extender:v1.0 name: my-scheduler-extender imagePullPolicy: Always --- apiVersion: v1 kind: Service metadata: name: my-scheduler-extender namespace: kube-system spec: selector: component: my-scheduler-extender tier: control-plane ports: - port: 8080 targetPort: 8080 部署:\nkubectl apply -f deploy-manifests.yaml 运行一个测试 Pod,查看 my-scheduler-extender 容器的日志:\nkubectl run nginx-by-my-scheduler-extender --image=nginx --overrides=\u0026#39;{\u0026#34;spec\u0026#34;:{\u0026#34;schedulerName\u0026#34;:\u0026#34;my-scheduler-with-extender\u0026#34;}}\u0026#39; # 查看 my-scheduler-extender 日志 kubectl logs deploy/my-scheduler-extender -n kube-system -f 代码传送门: my-scheduler-extender\n参考 # 调度器配置 Kubernetes Extender Create a custom Kubernetes scheduler « Kubernetes 定制开发 02:CRD\n» 简单介绍 K8s\n"},{"id":137,"href":"/kubernetes/k8s-get-started/","title":"K8s Get Started","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 简单介绍 K8s\n简单介绍 K8s # 简介 # 我们的应用部署趋势由大型单体应用向微服务演变,微服务应用之间解耦,形成可被独立开发、部署、升级、伸缩的软件单元。\n另一方面容器技术由于它的轻量级,资源隔离,可移植、部署高效等特性得到了迅速的发展和普及。越来越多的应用选择使用容器来部署,微服务更不例外。\n这时,便有了管理微服务+容器的需求,Kubernetes 开始大放异彩。\nKubernetes 是基于容器技术的服务器集群管理系统,通过它,可以托管数量庞大的应用集,并且内置完备的集群管理能力,它有能力帮你做到这些:\n应用的容器化部署、健康检查、自我修复、自动伸缩、滚动更新、资源分配等 服务的注册、发现、均衡负载等 Kubernetes 对于运维团队来说,是一个强大的帮手,更自动化的部署和管理应用,更高效的利用硬件资源。\n基本概念 # Kubernetes 集群,后面简称为K8s,主要是由控制节点(Master)和工作节点(Node)组成。\n在 Master 节点中运行着三大组件:kube-api-server、kube-controller-manager、kube-scheduler,通常也会将 etcd 数据库部署在 Master 节点。\n在 Node 节点中,也是需要部署三个主要组件,容器引擎(基本默认 docker 了)、kubelet、kube-proxy。\nK8s 的组成结构大致如下图:\nMaster 节点 # 负责 K8s 资源的调度管理,由 Master 向 Node 下达控制命令,并且一般运维人员使用 Master 操作和执行命令。\nMaster 节点扮演的角色相当于 K8s 的大脑,其重要性可想而知,因此建议部署 3 台 Master 节点保证 K8s 的高可用性。\nkube-api-server:http rest 接口服务,与 K8s 其他组件通信,负责 K8s 资源的 CURD 的操作入口;\nkube-controller-manager:K8s 资源的自动化控制管理中心,如跟踪资源状态,资源修复等;\nkube-scheduler:应用调度,为应用自动分配节点等;\netcd:分布式的数据库系统,存储 K8s 中的各种资源信息。\nNode 节点 # 负责 K8s 应用资源的容器化部署和运行,Node 节点配置要求一般高于 Master,当其中一个 Node 不可用时,部署在其上的 K8s 资源会自动被迁移到可用的 Node。\ndocker:部署应用的容器引擎; kubelet:与 kube-api-server 通信,向 Master 注册自己,定时汇报自身 Node 信息,Master 下达调度命令,同时负责容器生命周期的管理; kube-proxy:实现 service 代理 endpoint 和负载均衡,分配 Node 端口等。 其实了解了 K8s 的整个组成和相关组件服务的功能,我们就能知道如何来管理我们的 K8s 节点了。例如,我们要新增 Node 节点,该如何操作?\n原理上新扩 Node 只需要在一台新的机器上安装以上三个服务,然后配置 kubectl 和 kube-proxy 的启动参数,通过 kubelet 向 Master的kube-api-server 提交 Node 注册信息,就可以将 Node 纳入 Master 的调度中心了。\n工具 # kubeadm:\n使用 kubeadm 快速创建 K8s 集群,一般仅用于学习测试使用。\nkubectl:\n使用 kubectl 命令工具对 K8s 下达操作指令,一般将这个工具安装在 Master 节点。\n« Kubernetes 定制开发 50:扩展调度器\n» kubeadm 安装 Kubernetes (Docker)\n"},{"id":138,"href":"/kubernetes/kubeadm-install-k8s-docker/","title":"Kubeadm Install K8s Docker","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / kubeadm 安装 Kubernetes (Docker)\nkubeadm 安装 Kubernetes (Docker) # 使用 kubeadm 安装 k8s 集群,是社区推荐的安装方式,本文档将介绍使用 kubeadm 安装 k8s 集群(使用 Docker 作为容器运行时)的详细过程。\nNotes:\n随着 kubeadm \u0026amp; k8s 版本的更新,安装过程可能会有所不同,截至目前,本文档使用的是 kubeadm v1.28.3 \u0026amp; k8s v1.28.3 版本; 本文档使用的操作系统是 Ubuntu 22.04,其他操作系统可能会有所不同。 要求 # 至少一台物理机或虚拟机(例如:Ubuntu 22.04)作为集群节点,最少 2 核 2G 内存; 多节点之前网络互通,且节点主机名不冲突; Master 节点需要开放以下端口:6443、2379-2380、10250、10251、10252; 准备工作 # 禁用交换分区:\n# 临时禁用交换分区 sudo swapoff -a vim /etc/fstab # 注释掉 swap 分区的配置 配置系统:\ncat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter # sysctl params required by setup, params persist across reboots cat \u0026lt;\u0026lt;EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 EOF # Apply sysctl params without reboot sudo sysctl --system 安装 Docker # 设置 Docker 的 APT 源:\n# Add Docker\u0026#39;s official GPG key: sudo apt-get update sudo apt-get install ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg # Add the repository to Apt sources: echo \\ \u0026#34;deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \\ $(. /etc/os-release \u0026amp;\u0026amp; echo \u0026#34;$VERSION_CODENAME\u0026#34;) stable\u0026#34; | \\ sudo tee /etc/apt/sources.list.d/docker.list \u0026gt; /dev/null sudo apt-get update 安装 Docker:\nsudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin 官方文档: Docker 安装。\n安装 cri-dockerd # wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.8/cri-dockerd-0.3.8.amd64.tgz tar -xzvf cri-dockerd-0.3.8.amd64.tgz sudo install -m 0755 -o root -g root -t /usr/local/bin cri-dockerd/cri-dockerd wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.service wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.socket sudo install cri-docker.service /etc/systemd/system sudo install cri-docker.socket /etc/systemd/system sudo sed -i -e \u0026#39;s,/usr/bin/cri-dockerd,/usr/local/bin/cri-dockerd,\u0026#39; /etc/systemd/system/cri-docker.service sudo systemctl daemon-reload sudo systemctl enable --now cri-docker.socket sudo systemctl start cri-docker.service 社区文档: cri-dockerd 安装\n安装 kubeadm、kubelet 和 kubectl # sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg # 国内网络使用下面命令替换 # curl -fsSL https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add - echo \u0026#39;deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /\u0026#39; | sudo tee /etc/apt/sources.list.d/kubernetes.list # 国内网络使用下面命令替换 # cat \u0026lt;\u0026lt; EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list # deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main # EOF sudo apt-get update sudo apt-get install -y kubelet kubeadm kubectl sudo apt-mark hold kubelet kubeadm kubectl 早于 Debian 12 和 Ubuntu 22.04 的版本,/etc/apt/keyrings 目录默认不存在,需要手动创建:sudo mkdir -m 755 /etc/apt/keyrings\n配置 cgroup-driver # # 查看 cgroup-driver docker info | grep \u0026#34;Cgroup Driver\u0026#34; 配置使用 systemd 作为 cgroup-driver:\nvim /etc/docker/daemon.json 添加或修改配置项:\n{ ... \u0026#34;exec-opts\u0026#34;: [ \u0026#34;native.cgroupdriver=systemd\u0026#34; ] } 配置 kubelet 使用 systemd 作为 cgroup-driver(首次创建集群时,该文件并未生成,如果首次集群创建失败,那么可能已经生成该文件了,此时可以修改这个文件的配置):\nvim /etc/systemd/system/kubelet.service.d/10-kubeadm.conf 添加或修改配置项:\n... Environment=\u0026#34;KUBELET_EXTRA_ARGS=--cgroup-driver=systemd\u0026#34; ... 安装集群 # 国内网络提前拉取 registry.k8s.io/pause:3.9 镜像:\ndocker pull registry.aliyuncs.com/google_containers/pause:3.9 docker tag registry.aliyuncs.com/google_containers/pause:3.9 registry.k8s.io/pause:3.9 使用 kubeadm init 初始化集群:\nsudo kubeadm init \\ --pod-network-cidr 10.244.0.0/16 \\ --kubernetes-version 1.28.3 \\ --control-plane-endpoint=\u0026lt;EXTERNAL_IP\u0026gt;:6443 \\ --ignore-preflight-errors=Swap \\ --cri-socket=unix:///var/run/cri-dockerd.sock Notes:\n需要确保 --control-plane-endpoint 端点在执行环境是可以访问的,如果参数值为服务器的公网 IP,那么你可能需要对安全组开通 6443 端口; 国内网络拉取镜像使用代理添加命令参数:--image-repository registry.aliyuncs.com/google_containers。 初始化完成后,执行以下命令,配置集群访问环境:\nmkdir -p ~/.kube sudo cp -i /etc/kubernetes/admin.conf ~/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config 查看集群节点状态:\nkubectl get nodes 如果允许 Pod 调度到 Master 节点,那么需要去除 Master 节点的污点:\nkubectl taint nodes --all node-role.kubernetes.io/control-plane- 安装网络插件 # 安装 flanenel 网络插件:\nkubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml flannel 默认使用 CIDR 为 10.244.0.0/16,需要与 kubeadm init 时指定的 --pod-network-cidr 参数一致。\n安装 metrics-server # kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml 在多数情况下,metrics-server 由于证书问题无法正常启动,需要修改 metrics-server 的 Deployment 配置,添加 --kubelet-insecure-tls 参数:\nkubectl edit deployment metrics-server -n kube-system ... spec: ... template: ... spec: containers: - args: - --cert-dir=/tmp - --secure-port=4443 - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname - --kubelet-use-node-status-port - --metric-resolution=15s - --kubelet-insecure-tls 部署应用 # kubectl create deployment nginx --image=nginx kubectl expose deployment nginx --name=nginx --port=80 --target-port=80 --type=NodePort 参考 # Bootstrapping clusters with kubeadm Container Runtimes « 简单介绍 K8s\n» kubeadm 安装 k8s (containerd)\n"},{"id":139,"href":"/kubernetes/kubeadm-install-k8s/","title":"Kubeadm Install K8s","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / kubeadm 安装 k8s (containerd)\nkubeadm 安装 k8s (containerd) # 使用 kubeadm 安装 k8s 集群,是社区推荐的安装方式,本文档将介绍使用 kubeadm 安装 k8s 集群的详细过程。\nNotes:\n随着 kubeadm \u0026amp; k8s 版本的更新,安装过程可能会有所不同,截至目前,本文档使用的是 kubeadm v1.28.3 \u0026amp; k8s v1.28.3 版本; 本文档使用的操作系统是 Ubuntu 22.04,其他操作系统可能会有所不同。 要求 # 至少一台物理机或虚拟机(例如:Ubuntu 22.04)作为集群节点,最少 2 核 2G 内存; 多节点之前网络互通,且节点主机名不冲突; Master 节点需要开放以下端口:6443、2379-2380、10250、10251、10252; 准备工作 # 禁用交换分区:\n# 临时禁用交换分区 sudo swapoff -a vim /etc/fstab # 注释掉 swap 分区的配置 配置系统:\ncat \u0026lt;\u0026lt;EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter # sysctl params required by setup, params persist across reboots cat \u0026lt;\u0026lt;EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 EOF # Apply sysctl params without reboot sudo sysctl --system 安装 containerd # 参照 containerd 安装。\nexport LATEST=$(curl -s https://api.github.com/repos/containerd/containerd/releases/latest | jq -r .tag_name) LATEST=${LATEST#v} wget https://github.com/containerd/containerd/releases/download/v$LATEST/containerd-$LATEST-linux-amd64.tar.gz sudo tar Cxzvf /usr/local containerd-$LATEST-linux-amd64.tar.gz # systemd 配置 sudo wget https://raw.githubusercontent.com/containerd/containerd/main/containerd.service -O /lib/systemd/system/containerd.service sudo systemctl daemon-reload sudo systemctl enable --now containerd.service sudo systemctl restart containerd.service 安装 kubeadm、kubelet 和 kubectl # sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg # 国内网络使用下面命令替换 # curl -fsSL https://mirrors.aliyun.com/kubernetes/apt/doc/apt-key.gpg | apt-key add - echo \u0026#39;deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /\u0026#39; | sudo tee /etc/apt/sources.list.d/kubernetes.list # 国内网络使用下面命令替换 # cat \u0026lt;\u0026lt; EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list # deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main # EOF sudo apt-get update sudo apt-get install -y kubelet kubeadm kubectl sudo apt-mark hold kubelet kubeadm kubectl 早于 Debian 12 和 Ubuntu 22.04 的版本,/etc/apt/keyrings 目录默认不存在,需要手动创建:sudo mkdir -m 755 /etc/apt/keyrings\n安装集群 # 国内网络提前拉取 registry.k8s.io/pause:3.9 镜像:\ndocker pull registry.aliyuncs.com/google_containers/pause:3.9 docker tag registry.aliyuncs.com/google_containers/pause:3.9 registry.k8s.io/pause:3.9 使用 kubeadm init 初始化集群:\nsudo kubeadm init \\ --pod-network-cidr 10.244.0.0/16 \\ --kubernetes-version 1.29.0 \\ --control-plane-endpoint=\u0026lt;EXTERNAL_IP\u0026gt;:6443 \\ --ignore-preflight-errors=Swap Notes:\n需要确保 --control-plane-endpoint 端点在执行环境是可以访问的,如果参数值为服务器的公网 IP,那么你可能需要对安全组开通 6443 端口; 国内网络拉取镜像使用代理添加命令参数:--image-repository registry.aliyuncs.com/google_containers。 初始化完成后,执行以下命令,配置集群访问环境:\nmkdir -p ~/.kube sudo cp -i /etc/kubernetes/admin.conf ~/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config 查看集群节点状态:\nkubectl get nodes 如果允许 Pod 调度到 Master 节点,那么需要去除 Master 节点的污点:\nkubectl taint nodes --all node-role.kubernetes.io/control-plane- 安装网络插件 # 安装 flannel 网络插件:\nkubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml flannel 默认使用 CIDR 为 10.244.0.0/16,需要与 kubeadm init 时指定的 --pod-network-cidr 参数一致。\n安装 metrics-server # kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml 在多数情况下,metrics-server 由于证书问题无法正常启动,需要修改 metrics-server 的 Deployment 配置,添加 --kubelet-insecure-tls 参数:\nkubectl edit deployment metrics-server -n kube-system ... spec: ... template: ... spec: containers: - args: - --cert-dir=/tmp - --secure-port=4443 - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname - --kubelet-use-node-status-port - --metric-resolution=15s - --kubelet-insecure-tls 部署应用 # kubectl create deployment nginx --image=nginx kubectl expose deployment nginx --name=nginx --port=80 --target-port=80 --type=NodePort 参考 # Bootstrapping clusters with kubeadm Container Runtimes « kubeadm 安装 Kubernetes (Docker)\n» Kubeadm 升级 K8s\n"},{"id":140,"href":"/kubernetes/kubeadm-upgrade/","title":"Kubeadm Upgrade","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubeadm 升级 K8s\nKubeadm 升级 K8s # 本篇以升级 1.29.0(旧版本:1.28.x ) 版本为例,介绍如何通过 kubeadm 工具来升级 K8s 集群。\n注意:\n不支持跨主版本升级,如 1.27.x 升级到 1.29.x,中间必须先升级到 1.28.x 主版本更新必须先升级到最新的次版本,如 1.28.3 升级到 1.28.4,然后再升级到 1.29.x 升级步骤 # 控制节点(control plane node)升级 工作节点(worker node)升级 升级过程 # 1、升级至当前主版本的最新次版本 # sudo apt update sudo apt-cache madison kubeadm 以上命令后,将可以得到类似如下输出:\n$ sudo apt-cache madison kubeadm kubeadm | 1.28.4-1.1 | https://pkgs.k8s.io/core:/stable:/v1.28/deb Packages kubeadm | 1.28.3-1.1 | https://pkgs.k8s.io/core:/stable:/v1.28/deb Packages kubeadm | 1.28.2-1.1 | https://pkgs.k8s.io/core:/stable:/v1.28/deb Packages kubeadm | 1.28.1-1.1 | https://pkgs.k8s.io/core:/stable:/v1.28/deb Packages kubeadm | 1.28.0-1.1 | https://pkgs.k8s.io/core:/stable:/v1.28/deb Packages 确定当前主版本的最新次版本为 1.28.4-1.1,然后执行以下命令(控制节点上执行):\nexport VERSION=1.28.4-1.1 sudo apt-mark unhold kubeadm \u0026amp;\u0026amp; \\ sudo apt-get update \u0026amp;\u0026amp; sudo apt-get install -y kubeadm=$VERSION \u0026amp;\u0026amp; \\ sudo apt-mark hold kubeadm sudo kubeadm version sudo kubeadm upgrade plan sudo kubeadm upgrade apply v$(echo $VERSION | cut -d\u0026#39;-\u0026#39; -f1) 其他节点升级,最后一步执行 sudo kubeadm upgrade node 命令。\n升级 kubectl、kubelet:\n如果集群包括多个节点,那么在升级单个节点上的 kubelet 服务前,最好先将当前节点置为不可调度状态,并且将其上的 Pod 驱逐到其他节点上。\nkubectl drain [current-node] --ignore-daemonsets 等待当前节点上的 Pod 驱逐完成后,再升级 kubelet 服务。升级完成后,再将当前节点置为可调度状态。\nkubectl uncordon [current-node] 执行一下命令,升级 kubectl、kubelet 组件:\nsudo apt-mark unhold kubelet kubectl \u0026amp;\u0026amp; \\ sudo apt-get update \u0026amp;\u0026amp; sudo apt-get install -y kubelet=$VERSION kubectl=$VERSION \u0026amp;\u0026amp; \\ sudo apt-mark hold kubelet kubectl # 重启 kubelet 服务 sudo systemctl daemon-reload sudo systemctl restart kubelet.service 2、升级至下一个主版本 # 修改 /etc/apt/sources.list.d/kubernetes.list 文件,将 1.28 改为 1.29:\nsudo sed -i \u0026#39;s/1.28/1.29/g\u0026#39; /etc/apt/sources.list.d/kubernetes.list 然后参照上面的步骤,升级集群到下一个主版本 v1.29.x。\n参考 # Upgrading kubeadm clusters « kubeadm 安装 k8s (containerd)\n» kubebuilder 实战\n"},{"id":141,"href":"/kubernetes/kubebuilder-inaction/","title":"Kubebuilder Inaction","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / kubebuilder 实战\nkubebuilder 实战 # 简介 # kubebuilder 是一个构建 Operator(CRD + Controller)的框架的工具,它可以帮助我们快速的构建一个 Operator 项目,并提供了一些常用的命令,例如:创建 API、创建 Controller、Webhook 等。\n安装 # 条件 # kustomize curl -s \u0026#34;https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh\u0026#34; | bash controller-gen go install sigs.k8s.io/controller-tools/cmd/controller-gen@latest ⚠️ 注意:以上命令都是直接下载了对应命令工具最新的版本,在使用 kubebuilder 创建项目之后,在 Makefile 文件中会指定 kustomize 和 controller-gen 的版本,为了避免不兼容,推荐下载对应指定的版本。\n使用以下命令安装 kubebuilder:\n# download kubebuilder and install locally. GOOS=$(go env GOOS) GOARCH=$(go env GOARCH) curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/$GOOS/$GOARCH chmod +x kubebuilder \u0026amp;\u0026amp; mv kubebuilder /usr/local/bin/ 代码自动补全:\nsource \u0026lt;(kubebuilder completion bash) source \u0026lt;(kubebuilder completion zsh) # zsh echo \u0026#34;source \u0026lt;(kubebuilder completion zsh)\u0026#34; \u0026gt;\u0026gt; ~/.zshrc # bash echo \u0026#34;source \u0026lt;(kubebuilder completion bash)\u0026#34; \u0026gt;\u0026gt; ~/.bashrc 创建项目 # mkdir ./kube-acme \u0026amp;\u0026amp; cd ./kube-acme kubebuilder init --domain ketches.cn --repo github.com/ketches/kube-acme # 如果不开启多 Group 模式,生成的 API 文件路径为:api/{version} # 开启多 Group 模式,生成 API 的文件路径为 apis/{grouop}/{version} kubebuilder edit --multigroup 创建项目后,会在目录中生成 hack/boilerplate.go.txt 文件,用于kubebuilder 生成 go 文件的版权信息,建议在开始编写代码之前修改该文件,例如作者信息。\n创建 API # kubebuilder create api --group acme --version v1alpha1 --kind DNSProvider # 连续输入 y 确认生成 API 以及 Controller 文件 在集群中部署 # 将控制器编译成镜像并推送到镜像仓库:\nmake docker-build docker-push IMG=ketches/kube-acme:v0.0.1 将控制器部署到集群中:\nmake deploy IMG=ketches/kube-acme:v0.0.1 卸载 CRD # 从集群中删除CRD:\nmake uninstall 卸载控制器 # 从集群中卸载控制器:\nmake undeploy « Kubeadm 升级 K8s\n» kubectl\n"},{"id":142,"href":"/kubernetes/kubectl/","title":"Kubectl","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / kubectl\nkubectl # 安装 # 参考文档: kubectl 安装文档\n常用命令 # 自动补全 # source \u0026lt;(kubectl completion bash) 可以将上面的命令写入 ~/.bashrc 或 /etc/bash.bashrc 中,这样每次登录都会自动补全。\n$ vim ~/.bashrc ... source \u0026lt;(kubectl completion bash) 命令别名 # alias k=kubectl complete -F __start_kubectl k Troubleshooting # Q1. _get_comp_words_by_ref: command not found # 解决方法:\napt install bash-completion -y source /usr/share/bash-completion/bash_completion source \u0026lt;(kubectl completion bash) « kubebuilder 实战\n» Kubernetes 0-1 Kubernetes最佳实践\n"},{"id":143,"href":"/kubernetes/kubernetes-best-practice/","title":"Kubernetes Best Practice","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 Kubernetes最佳实践\nKubernetes 0-1 Kubernetes最佳实践 # https://github.com/learnk8s/kubernetes-production-best-practices\n« kubectl\n» Kubernetes Dashboard\n"},{"id":144,"href":"/kubernetes/kubernetes-dashboard/","title":"Kubernetes Dashboard","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes Dashboard\nKubernetes Dashboard # Installation # Steps # 登入Kubernetes Master机器。\nCopy最新的recommended.yaml文件内容,写入本地kubernetes-dashboard.yaml文件。recommended.yaml文件地址: kubernetes dashboard github\n![image-20191223173827866](\nC:\\Users\\dp\\AppData\\Roaming\\Typora\\typora-user-images\\image-20191223173827866.png)\n# Copyright 2017 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the \u0026#34;License\u0026#34;); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an \u0026#34;AS IS\u0026#34; BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. apiVersion: v1 kind: Namespace metadata: name: kubernetes-dashboard --- apiVersion: v1 kind: ServiceAccount metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard --- kind: Service apiVersion: v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard spec: # 新增type: NodePort以及nodePort: 32100 #type: NodePort #ports: #- port: 443 #targetPort: 8443 #nodePort: 32100 #selector: #k8s-app: kubernetes-dashboard # 或者新增type: LoadBalancer type: LoadBalancer ports: - port: 443 targetPort: 8443 selector: k8s-app: kubernetes-dashboard --- # 注释掉这段 #apiVersion: v1 #kind: Secret #metadata: # labels: # k8s-app: kubernetes-dashboard # name: kubernetes-dashboard-certs # namespace: kubernetes-dashboard #type: Opaque --- apiVersion: v1 kind: Secret metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard-csrf namespace: kubernetes-dashboard type: Opaque data: csrf: \u0026#34;\u0026#34; --- apiVersion: v1 kind: Secret metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard-key-holder namespace: kubernetes-dashboard type: Opaque --- kind: ConfigMap apiVersion: v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard-settings namespace: kubernetes-dashboard --- kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard rules: # Allow Dashboard to get, update and delete Dashboard exclusive secrets. - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;secrets\u0026#34;] resourceNames: [\u0026#34;kubernetes-dashboard-key-holder\u0026#34;, \u0026#34;kubernetes-dashboard-certs\u0026#34;, \u0026#34;kubernetes-dashboard-csrf\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;update\u0026#34;, \u0026#34;delete\u0026#34;] # Allow Dashboard to get and update \u0026#39;kubernetes-dashboard-settings\u0026#39; config map. - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;configmaps\u0026#34;] resourceNames: [\u0026#34;kubernetes-dashboard-settings\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;update\u0026#34;] # Allow Dashboard to get metrics. - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;services\u0026#34;] resourceNames: [\u0026#34;heapster\u0026#34;, \u0026#34;dashboard-metrics-scraper\u0026#34;] verbs: [\u0026#34;proxy\u0026#34;] - apiGroups: [\u0026#34;\u0026#34;] resources: [\u0026#34;services/proxy\u0026#34;] resourceNames: [\u0026#34;heapster\u0026#34;, \u0026#34;http:heapster:\u0026#34;, \u0026#34;https:heapster:\u0026#34;, \u0026#34;dashboard-metrics-scraper\u0026#34;, \u0026#34;http:dashboard-metrics-scraper\u0026#34;] verbs: [\u0026#34;get\u0026#34;] --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard rules: # Allow Metrics Scraper to get metrics from the Metrics server - apiGroups: [\u0026#34;metrics.k8s.io\u0026#34;] resources: [\u0026#34;pods\u0026#34;, \u0026#34;nodes\u0026#34;] verbs: [\u0026#34;get\u0026#34;, \u0026#34;list\u0026#34;, \u0026#34;watch\u0026#34;] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: kubernetes-dashboard subjects: - kind: ServiceAccount name: kubernetes-dashboard namespace: kubernetes-dashboard --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: kubernetes-dashboard roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: kubernetes-dashboard subjects: - kind: ServiceAccount name: kubernetes-dashboard namespace: kubernetes-dashboard --- kind: Deployment apiVersion: apps/v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard spec: replicas: 1 revisionHistoryLimit: 10 selector: matchLabels: k8s-app: kubernetes-dashboard template: metadata: labels: k8s-app: kubernetes-dashboard spec: containers: - name: kubernetes-dashboard image: kubernetesui/dashboard:v2.0.0-beta8 imagePullPolicy: Always ports: - containerPort: 8443 protocol: TCP args: - --auto-generate-certificates - --namespace=kubernetes-dashboard # Uncomment the following line to manually specify Kubernetes API server Host # If not specified, Dashboard will attempt to auto discover the API server and connect # to it. Uncomment only if the default does not work. # - --apiserver-host=http://my-address:port volumeMounts: - name: kubernetes-dashboard-certs mountPath: /certs # Create on-disk volume to store exec logs - mountPath: /tmp name: tmp-volume livenessProbe: httpGet: scheme: HTTPS path: / port: 8443 initialDelaySeconds: 30 timeoutSeconds: 30 securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsUser: 1001 runAsGroup: 2001 volumes: - name: kubernetes-dashboard-certs secret: secretName: kubernetes-dashboard-certs - name: tmp-volume emptyDir: {} serviceAccountName: kubernetes-dashboard nodeSelector: \u0026#34;beta.kubernetes.io/os\u0026#34;: linux # Comment the following tolerations if Dashboard must not be deployed on master tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule --- kind: Service apiVersion: v1 metadata: labels: k8s-app: dashboard-metrics-scraper name: dashboard-metrics-scraper namespace: kubernetes-dashboard spec: ports: - port: 8000 targetPort: 8000 selector: k8s-app: dashboard-metrics-scraper --- kind: Deployment apiVersion: apps/v1 metadata: labels: k8s-app: dashboard-metrics-scraper name: dashboard-metrics-scraper namespace: kubernetes-dashboard spec: replicas: 1 revisionHistoryLimit: 10 selector: matchLabels: k8s-app: dashboard-metrics-scraper template: metadata: labels: k8s-app: dashboard-metrics-scraper annotations: seccomp.security.alpha.kubernetes.io/pod: \u0026#39;runtime/default\u0026#39; spec: containers: - name: dashboard-metrics-scraper image: kubernetesui/metrics-scraper:v1.0.1 ports: - containerPort: 8000 protocol: TCP livenessProbe: httpGet: scheme: HTTP path: / port: 8000 initialDelaySeconds: 30 timeoutSeconds: 30 volumeMounts: - mountPath: /tmp name: tmp-volume securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsUser: 1001 runAsGroup: 2001 serviceAccountName: kubernetes-dashboard nodeSelector: \u0026#34;beta.kubernetes.io/os\u0026#34;: linux # Comment the following tolerations if Dashboard must not be deployed on master tolerations: - key: node-role.kubernetes.io/master effect: NoSchedule volumes: - name: tmp-volume emptyDir: {} 注意::\n需要注释掉一段,因为不注释掉的话,存在证书过期问题,chrome、safari主流浏览器将无法访问,好像firefox可以;\nkubernetes dashboard service的type定义为NodePort,并指定一个节点端口32100;\n生成自签证书请求的key: openssl genrsa -out dashboard.key 2048 生成自签证书请求: openssl req -new -out dashboard.csr -key dashboard.key -subj \u0026#39;/CN=\u0026lt;k8s master ip or domain name\u0026gt;\u0026#39; 生成自签证书 # 不设置-days,则默认365天过期 openssl x509 -days 3650 -req -in dashboard.csr -signkey dashboard.key -out dashboard.crt 部署kubernetes dashboard kubectl apply -f kubernetes-dashboard.yaml 由于kubernetes-dashboard.yaml我们注释掉了kubernetes-dashboard-certs,pod跑不起来,我们需要创建certs kubectl create secret generic kubernetes-dashboard-certs --from-file=dashboard.key --from-file=dashboard.crt -n kubernetes-dashboard 创建访问用户,用于访问kubernetes-dashboard,以下文本内容写入本地admin-user.yaml文件 apiVersion: v1 kind: ServiceAccount metadata: name: admin-user namespace: kubernetes-dashboard --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: admin-user roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: admin-user namespace: kubernetes-dashboard kubectl apply -f admin-user.yaml 获取登录密钥 kubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep admin-user | awk \u0026#39;{print $1}\u0026#39;) ​ 上面命令输出,拿到token的值\nIngress转发 apiVersion: extensions/v1beta1 kind: Ingress metadata: name: kubedash namespace: kubernetes-dashboard spec: rules: - host: kubedash.example.tech http: paths: - path: / backend: serviceName: kubernetes-dashboard servicePort: 32100 访问 https://xxxx:32100 ,注意这里一定要用https 选择Token访问,输入token即可。 References # https://www.cnblogs.com/life-of-coding/p/11794993.html https://kubernetes.io/docs/tasks/access-application-cluster/web-ui-dashboard/#deploying-the-dashboard-ui « Kubernetes 0-1 Kubernetes最佳实践\n» Kubernetes 中资源名称规范\n"},{"id":145,"href":"/kubernetes/kubernetes-naming-constraints/","title":"Kubernetes Naming Constraints","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 中资源名称规范\nKubernetes 中资源名称规范 # 在Kubernetes中,同一种资源(GVR)在同一个命名空间下名称是唯一。但是名称也需要遵循命名规则。本篇主要介绍 Kubernetes 中三种资源名称的命名规范。\nDNS1123Subdomain # 不能超过 253 个字符 只能包含小写字母、数字,以及 \u0026lsquo;-\u0026rsquo; 和 \u0026lsquo;.\u0026rsquo; 必须以字母数字开头 必须以字母数字结尾 以此规范约束的资源有:\nIngress Pod ConfigMap NetworkPolicy DNS1123Label # 最多 63 个字符 只能包含小写字母、数字,以及 \u0026lsquo;-\u0026rsquo; 必须以字母数字开头 必须以字母数字结尾 以此规范约束的资源有:\nNamespace Service DNS1035Label # 最多 63 个字符 只能包含小写字母、数字,以及 \u0026lsquo;-\u0026rsquo; 必须以字母开头 必须以字母数字结尾 以此规范约束的资源有:\nDeployment StatefulSet 建议 # 如果资源命名符合 DNS1035Label 规范,那么一定符合 Kubernetes 资源命名规范。假如在容器平台开发过程中,为了命名约束更加统一,建议使用 DNS1035Label 规范来约束资源命名。\n可以使用下面的代码(Go)来检查资源名称是否符合规范:\n引入包:\ngo get k8s.io/apimachinery/pkg/util/validation 示例代码:\npackage main import ( \u0026#34;k8s.io/apimachinery/pkg/util/validation\u0026#34; ) func main() { namespace: = \u0026#34;test-ns\u0026#34; if msgs := validation.IsDNS1123Subdomain(namespace); len(msgs) \u0026gt; 0 { fmt.Println(\u0026#34;namespace name is not valid\u0026#34;) } } « Kubernetes Dashboard\n» Kuberentes\n"},{"id":146,"href":"/kubernetes/kubernetes/","title":"Kubernetes","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kuberentes\nKuberentes # « Kubernetes 中资源名称规范\n» KubeVirt 创建 Windows 虚拟机\n"},{"id":147,"href":"/kubernetes/kubevirt-create-windows-vm/","title":"Kubevirt Create Windows Vm","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / KubeVirt 创建 Windows 虚拟机\nKubeVirt 创建 Windows 虚拟机 # virtctl image-upload --image-path windows-10.iso --pvc-name=windows-10-iso --size 10G --uploadproxy-url https://\u0026lt;cdi-uploadproxy.cdi.svc\u0026gt; --insecure --wait-secs 240 apiVersion: v1 kind: PersistentVolumeClaim metadata: name: windows-10-data spec: accessModes: - ReadWriteOnce resources: requests: storage: 40G storageClassName: longhorn volumeMode: Filesystem --- apiVersion: kubevirt.io/v1 kind: VirtualMachine metadata: name: windows-10 spec: running: true template: metadata: labels: kubevirt.io/domain: windows-10 spec: domain: cpu: cores: 4 devices: networkInterfaceMultiqueue: true #开启网卡多队列模式 blockMultiQueue: true #开启磁盘多队列模式 disks: - cdrom: bus: sata name: virtiocontainerdisk - cdrom: bus: sata name: cdromiso bootOrder: 1 - disk: bus: virtio name: harddrive bootOrder: 2 interfaces: - masquerade: {} model: virtio name: default resources: requests: memory: 8G networks: - name: default pod: {} volumes: - name: cdromiso persistentVolumeClaim: claimName: windows-10-iso - name: harddrive persistentVolumeClaim: claimName: windows-10-data - containerDisk: image: kubevirt/virtio-container-disk name: virtiocontainerdisk « Kuberentes\n» Kubevirt 实践\n"},{"id":148,"href":"/kubernetes/kubevirt-practice/","title":"Kubevirt Practice","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubevirt 实践\nKubevirt 实践 # 简介 # Kubevirt 是 Redhat 开源的以容器方式运行虚拟机的项目,以 k8s add-on 方式,利用 k8s CRD 为增加资源类型 VirtualMachineInstance(VMI), 使用容器的 image registry 去创建虚拟机并提供 VM 生命周期管理。 CRD 的方式使得 kubevirt 对虚拟机的管理不局限于 pod 管理接口,但是也无法使用 pod 的 RS DS Deployment 等管理能力,也意味着 kubevirt 如果想要利用 pod 管理能力,要自主去实现,目前 kubevirt 实现了类似 RS 的功能。 kubevirt 目前支持的 runtime 是 docker 和 runc。\n安装 # 部署 K8s 资源 # 最新版本 export KUBEVIRT_VERSION=$(curl -s https://api.github.com/repos/kubevirt/kubevirt/releases/latest | jq -r .tag_name) echo $KUBEVIRT_VERSION # 安装 CRD kubectl create -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-cr.yaml # 安装控制器 kubectl create -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-operator.yaml 安装 virtctl 工具 wget -O virtctl https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/virtctl-${KUBEVIRT_VERSION}-linux-amd64 chmod +x virtctl mv virtctl /usr/local/bin 检查资源状态 # 需要等待所有 Pod 处于 Running 状态 kubectl get pods -n kubevirt # 需要等待 kubevirt 处于 Deployed 状态 kubectl -n kubevirt get kubevirt 卸载 卸载首先需要删除 CRD,然后卸载控制器。\n# 0、获取当前 Kubevirt 版本 export KUBEVIRT_VERSION=$(kubectl get kubevirts.kubevirt.io -n kubevirt kubevirt -o=jsonpath=\u0026#39;{.status.observedKubeVirtVersion}\u0026#39;) # 1、删除 CRD kubectl delete -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-cr.yaml # 2、卸载控制器 kubectl delete -f kubectl create -f https://github.com/kubevirt/kubevirt/releases/download/${KUBEVIRT_VERSION}/kubevirt-operator.yaml 概念 # 虚拟机(VirtualMachine) 虚拟机实例(VirtualMachineInstance) 创建虚拟机 # 现在你可以像创建其他任何 K8s 资源一样,声明虚拟机资源然后创建了,如下:\napiVersion: kubevirt.io/v1 kind: VirtualMachine metadata: name: testvm spec: running: false template: metadata: labels: kubevirt.io/size: small kubevirt.io/domain: testvm spec: domain: devices: disks: - name: containerdisk disk: bus: virtio - name: cloudinitdisk disk: bus: virtio interfaces: - name: default masquerade: {} resources: requests: memory: 64M networks: - name: default pod: {} volumes: - name: containerdisk containerDisk: image: quay.io/kubevirt/cirros-container-disk-demo - name: cloudinitdisk cloudInitNoCloud: userDataBase64: SGkuXG4= 创建虚拟机:\nkubectl apply -f testvm.yaml 查看虚拟机状态,虚拟机应该处于未运行的状态:\n# kubectl get vms kubectl get vms -o yaml testvm | grep -E \u0026#39;running:.*|$\u0026#39; 运行虚拟机 # 使用 virtctl start 命令运行虚拟机:\nvirtual start testvm 注意:\n如果遇到 virtctl start testvm dial tcp 127.0.0.1:8080: connect: connection refused问题,通过 kubectl proxy --port 8080 在本地代理 K8s apiserver 服务。\n等待片刻,再次查看虚拟机状态,虚拟机应该处于未运行的状态:\nkubectl get vms -o yaml testvm | grep -E \u0026#39;running:.*|$\u0026#39; kubectl get vmis kubectl get vms 访问虚拟机 # 使用 virtual console 命令访问虚拟机:\nvirtual console testvm 注意:\n使用镜像 quay.io/kubevirt/cirros-container-disk-demo 创建的虚拟机,默认账号密码:cirros/gocubsgo; 使用 ctrl+] 退出。 关闭虚拟机 # 使用 virtual stop 命令访问虚拟机:\nvirtual stop testvm 删除虚拟机:\nkubectl delete vms testvm 磁盘访问 # lun # 将卷以 lun 的方式公开给虚拟机。\npvc:\napiVersion: v1 kind: PersistentVolumeClaim metadata: name: lun-pvc namespace: default spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: longhorn volumeMode: Filesystem vm:\napiVersion: kubevirt.io/v1 kind: VirtualMachine metadata: name: testvm spec: running: false template: metadata: labels: kubevirt.io/size: small kubevirt.io/domain: testvm spec: domain: devices: disks: - name: containerdisk disk: bus: virtio - name: cloudinitdisk disk: bus: virtio - name: lundisk lun: {} interfaces: - name: default masquerade: {} resources: requests: memory: 64M networks: - name: default pod: {} volumes: - name: containerdisk containerDisk: image: quay.io/kubevirt/cirros-container-disk-demo - name: cloudinitdisk cloudInitNoCloud: userDataBase64: SGkuXG4= - name: lundisk persistentVolumeClaim: claimName: lun-pvc disk # pvc:\napiVersion: v1 kind: PersistentVolumeClaim metadata: name: disk-pvc namespace: default spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: longhorn volumeMode: Filesystem vm:\napiVersion: kubevirt.io/v1 kind: VirtualMachine metadata: name: testvm spec: running: false template: metadata: labels: kubevirt.io/size: small kubevirt.io/domain: testvm spec: domain: devices: disks: - name: containerdisk disk: bus: virtio - name: cloudinitdisk disk: bus: virtio - name: disk-pvc disk: bus: virtio interfaces: - name: default masquerade: {} resources: requests: memory: 64M networks: - name: default pod: {} volumes: - name: containerdisk containerDisk: image: quay.io/kubevirt/cirros-container-disk-demo - name: cloudinitdisk cloudInitNoCloud: userDataBase64: SGkuXG4= - name: disk-pvc persistentVolumeClaim: claimName: disk-pvc cdrom # 将卷以 cdrom 驱动器的方式公开给虚拟机。默认情况下只读,可以通过 readonly: false 设置为可写。\npvc:\napiVersion: v1 kind: PersistentVolumeClaim metadata: name: cdrom-pvc namespace: default spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: longhorn volumeMode: Filesystem vm:\napiVersion: kubevirt.io/v1 kind: VirtualMachine metadata: name: testvm spec: running: false template: metadata: labels: kubevirt.io/size: small kubevirt.io/domain: testvm spec: domain: devices: disks: - name: containerdisk disk: bus: virtio - name: cloudinitdisk disk: bus: virtio - name: disk-pvc cdrom: readonly: false bus: sata interfaces: - name: default masquerade: {} resources: requests: memory: 64M networks: - name: default pod: {} volumes: - name: containerdisk containerDisk: image: quay.io/kubevirt/cirros-container-disk-demo - name: cloudinitdisk cloudInitNoCloud: userDataBase64: SGkuXG4= - name: cdrom-pvc persistentVolumeClaim: claimName: cdrom-pvc 排错 # 使用 virtctl 工具运行虚拟机时,遇到 virtctl start testvm dial tcp 127.0.0.1:8080: connect: connection refused 错误: 解决方法:本地代理 K8s apiserver 服务。\nkubectl proxy --port 8080 « KubeVirt 创建 Windows 虚拟机\n» Kustomize\n"},{"id":149,"href":"/kubernetes/kustomize/","title":"Kustomize","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kustomize\nKustomize # Kustomize 是一个通过 kustomization 文件 定制 Kubernetes 对象的工具。它提供以下功能特性来管理应用配置文件:\n从其他来源生成资源 为资源设置贯穿性(Cross-Cutting)字段 组织和定制资源集合 从 1.14 版本开始,kubectl 也开始支持使用 kustomization 文件来管理 Kubernetes 对象。 要查看包含 kustomization 文件的目录中的资源,执行下面的命令:\nkubectl kustomize \u0026lt;kustomization_directory\u0026gt; 要应用这些资源,使用 --kustomize 或 -k 参数来执行 kubectl apply:\nkubectl apply -k \u0026lt;kustomization_directory\u0026gt; 生成资源 # ConfigMap 和 Secret 包含其他 Kubernetes 对象(如 Pod)所需要的配置或敏感数据。 ConfigMap 或 Secret 中数据的来源往往是集群外部,例如某个 .properties 文件或者 SSH 密钥文件。 Kustomize 提供 secretGenerator 和 configMapGenerator,可以基于文件或字面值来生成 Secret 和 ConfigMap。\nconfigMapGenerator # 要基于文件来生成 ConfigMap,可以在 configMapGenerator 的 files 列表中添加表项。 下面是一个根据 .properties 文件中的数据条目来生成 ConfigMap 的示例:\n# 生成一个 application.properties 文件 cat \u0026lt;\u0026lt;EOF \u0026gt;application.properties FOO=Bar EOF cat \u0026lt;\u0026lt;EOF \u0026gt;./kustomization.yaml configMapGenerator: - name: example-configmap-1 files: - application.properties EOF 所生成的 ConfigMap 可以使用下面的命令来检查:\nkubectl kustomize ./ 所生成的 ConfigMap 为:\napiVersion: v1 data: application.properties: | FOO=Bar kind: ConfigMap metadata: name: example-configmap-1-8mbdf7882g 要从 env 文件生成 ConfigMap,请在 configMapGenerator 中的 envs 列表中添加一个条目。 下面是一个用来自 .env 文件的数据生成 ConfigMap 的例子:\n# 创建一个 .env 文件 cat \u0026lt;\u0026lt;EOF \u0026gt;.env FOO=Bar EOF cat \u0026lt;\u0026lt;EOF \u0026gt;./kustomization.yaml configMapGenerator: - name: example-configmap-1 envs: - .env EOF 可以使用以下命令检查生成的 ConfigMap:\nkubectl kustomize ./ 生成的 ConfigMap 为:\napiVersion: v1 data: FOO: Bar kind: ConfigMap metadata: name: example-configmap-1-42cfbf598f 说明::\n.env 文件中的每个变量在生成的 ConfigMap 中成为一个单独的键。这与之前的示例不同, 前一个示例将一个名为 application.properties 的文件(及其所有条目)嵌入到同一个键的值中。\nConfigMap 也可基于字面的键值偶对来生成。要基于键值偶对来生成 ConfigMap, 在 configMapGenerator 的 literals 列表中添加表项。下面是一个例子, 展示如何使用键值偶对中的数据条目来生成 ConfigMap 对象:\ncat \u0026lt;\u0026lt;EOF \u0026gt;./kustomization.yaml configMapGenerator: - name: example-configmap-2 literals: - FOO=Bar EOF 可以用下面的命令检查所生成的 ConfigMap:\nkubectl kustomize ./ 所生成的 ConfigMap 为:\napiVersion: v1 data: FOO: Bar kind: ConfigMap metadata: name: example-configmap-2-g2hdhfc6tk 要在 Deployment 中使用生成的 ConfigMap,使用 configMapGenerator 的名称对其进行引用。 Kustomize 将自动使用生成的名称替换该名称。\n这是使用生成的 ConfigMap 的 deployment 示例:\n# 创建一个 application.properties 文件 cat \u0026lt;\u0026lt;EOF \u0026gt;application.properties FOO=Bar EOF cat \u0026lt;\u0026lt;EOF \u0026gt;deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-app labels: app: my-app spec: selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: app image: my-app volumeMounts: - name: config mountPath: /config volumes: - name: config configMap: name: example-configmap-1 EOF cat \u0026lt;\u0026lt;EOF \u0026gt;./kustomization.yaml resources: - deployment.yaml configMapGenerator: - name: example-configmap-1 files: - application.properties EOF 生成 ConfigMap 和 Deployment:\nkubectl kustomize ./ 生成的 Deployment 将通过名称引用生成的 ConfigMap:\napiVersion: v1 data: application.properties: | FOO=Bar kind: ConfigMap metadata: name: example-configmap-1-g4hk9g2ff8 --- apiVersion: apps/v1 kind: Deployment metadata: labels: app: my-app name: my-app spec: selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - image: my-app name: app volumeMounts: - mountPath: /config name: config volumes: - configMap: name: example-configmap-1-g4hk9g2ff8 name: config secretGenerator # 你可以基于文件或者键值偶对来生成 Secret。要使用文件内容来生成 Secret, 在 secretGenerator 下面的 files 列表中添加表项。使用和 configMapGenerator 基本一致。\n设置贯穿性字段 # 在项目中为所有 Kubernetes 对象设置贯穿性字段是一种常见操作。 贯穿性字段的一些使用场景如下:\n为所有资源设置相同的名字空间 为所有对象添加相同的前缀或后缀 为对象添加相同的标签集合 为对象添加相同的注解集合 下面是一个例子:\n# 创建一个 deployment.yaml cat \u0026lt;\u0026lt;EOF \u0026gt;./deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx EOF cat \u0026lt;\u0026lt;EOF \u0026gt;./kustomization.yaml namespace: my-namespace namePrefix: dev- nameSuffix: \u0026#34;-001\u0026#34; commonLabels: app: bingo commonAnnotations: oncallPager: 800-555-1212 resources: - deployment.yaml EOF 执行 kubectl kustomize ./ 查看这些字段都被设置到 Deployment 资源上:\napiVersion: apps/v1 kind: Deployment metadata: annotations: oncallPager: 800-555-1212 labels: app: bingo name: dev-nginx-deployment-001 namespace: my-namespace spec: selector: matchLabels: app: bingo template: metadata: annotations: oncallPager: 800-555-1212 labels: app: bingo spec: containers: - image: nginx name: nginx 组织和定制资源 # 一种常见的做法是在项目中构造资源集合并将其放到同一个文件或目录中管理。 Kustomize 提供基于不同文件来组织资源并向其应用补丁或者其他定制的能力。\n组织 # Kustomize 支持组合不同的资源。kustomization.yaml 文件的 resources 字段定义配置中要包含的资源列表。 你可以将 resources 列表中的路径设置为资源配置文件的路径。 下面是由 Deployment 和 Service 构成的 NGINX 应用的示例:\n# 创建 deployment.yaml 文件 cat \u0026lt;\u0026lt;EOF \u0026gt; deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: selector: matchLabels: run: my-nginx replicas: 2 template: metadata: labels: run: my-nginx spec: containers: - name: my-nginx image: nginx ports: - containerPort: 80 EOF # 创建 service.yaml 文件 cat \u0026lt;\u0026lt;EOF \u0026gt; service.yaml apiVersion: v1 kind: Service metadata: name: my-nginx labels: run: my-nginx spec: ports: - port: 80 protocol: TCP selector: run: my-nginx EOF # 创建 kustomization.yaml 来组织以上两个资源 cat \u0026lt;\u0026lt;EOF \u0026gt;./kustomization.yaml resources: - deployment.yaml - service.yaml EOF kubectl kustomize ./ 所得到的资源中既包含 Deployment 也包含 Service 对象。\n定制 # 补丁文件(Patches)可以用来对资源执行不同的定制。 Kustomize 通过 patchesStrategicMerge 和 patchesJson6902 支持不同的打补丁机制。 patchesStrategicMerge 的内容是一个文件路径的列表,其中每个文件都应可解析为 策略性合并补丁(Strategic Merge Patch)。 补丁文件中的名称必须与已经加载的资源的名称匹配。 建议构造规模较小的、仅做一件事情的补丁。 例如,构造一个补丁来增加 Deployment 的副本个数;构造另外一个补丁来设置内存限制。\n# 创建 deployment.yaml 文件 cat \u0026lt;\u0026lt;EOF \u0026gt; deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: selector: matchLabels: run: my-nginx replicas: 2 template: metadata: labels: run: my-nginx spec: containers: - name: my-nginx image: nginx ports: - containerPort: 80 EOF # 生成一个补丁 increase_replicas.yaml cat \u0026lt;\u0026lt;EOF \u0026gt; increase_replicas.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: replicas: 3 EOF # 生成另一个补丁 set_memory.yaml cat \u0026lt;\u0026lt;EOF \u0026gt; set_memory.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: template: spec: containers: - name: my-nginx resources: limits: memory: 512Mi EOF cat \u0026lt;\u0026lt;EOF \u0026gt;./kustomization.yaml resources: - deployment.yaml patchesStrategicMerge: - increase_replicas.yaml - set_memory.yaml EOF 执行 kubectl kustomize ./ 来查看 Deployment:\napiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: replicas: 3 selector: matchLabels: run: my-nginx template: metadata: labels: run: my-nginx spec: containers: - image: nginx name: my-nginx ports: - containerPort: 80 resources: limits: memory: 512Mi 并非所有资源或者字段都支持策略性合并补丁。为了支持对任何资源的任何字段进行修改, Kustomize 提供通过 patchesJson6902 来应用 JSON 补丁的能力。 为了给 JSON 补丁找到正确的资源,需要在 kustomization.yaml 文件中指定资源的组(group)、 版本(version)、类别(kind)和名称(name)。 例如,为某 Deployment 对象增加副本个数的操作也可以通过 patchesJson6902 来完成:\n# 创建一个 deployment.yaml 文件 cat \u0026lt;\u0026lt;EOF \u0026gt; deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: selector: matchLabels: run: my-nginx replicas: 2 template: metadata: labels: run: my-nginx spec: containers: - name: my-nginx image: nginx ports: - containerPort: 80 EOF # 创建一个 JSON 补丁文件 cat \u0026lt;\u0026lt;EOF \u0026gt; patch.yaml - op: replace path: /spec/replicas value: 3 EOF # 创建一个 kustomization.yaml cat \u0026lt;\u0026lt;EOF \u0026gt;./kustomization.yaml resources: - deployment.yaml patchesJson6902: - target: group: apps version: v1 kind: Deployment name: my-nginx path: patch.yaml EOF 执行 kubectl kustomize ./ 以查看 replicas 字段被更新:\napiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: replicas: 3 selector: matchLabels: run: my-nginx template: metadata: labels: run: my-nginx spec: containers: - image: nginx name: my-nginx ports: - containerPort: 80 除了补丁之外,Kustomize 还提供定制容器镜像或者将其他对象的字段值注入到容器中的能力,并且不需要创建补丁。 例如,你可以通过在 kustomization.yaml 文件的 images 字段设置新的镜像来更改容器中使用的镜像。\ncat \u0026lt;\u0026lt;EOF \u0026gt; deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: selector: matchLabels: run: my-nginx replicas: 2 template: metadata: labels: run: my-nginx spec: containers: - name: my-nginx image: nginx ports: - containerPort: 80 EOF cat \u0026lt;\u0026lt;EOF \u0026gt;./kustomization.yaml resources: - deployment.yaml images: - name: nginx newName: my.image.registry/nginx newTag: 1.4.0 EOF 执行 kubectl kustomize ./ 以查看所使用的镜像已被更新:\napiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: replicas: 2 selector: matchLabels: run: my-nginx template: metadata: labels: run: my-nginx spec: containers: - image: my.image.registry/nginx:1.4.0 name: my-nginx ports: - containerPort: 80 有些时候,Pod 中运行的应用可能需要使用来自其他对象的配置值。 例如,某 Deployment 对象的 Pod 需要从环境变量或命令行参数中读取读取 Service 的名称。 由于在 kustomization.yaml 文件中添加 namePrefix 或 nameSuffix 时 Service 名称可能发生变化,建议不要在命令参数中硬编码 Service 名称。 对于这种使用场景,Kustomize 可以通过 vars 将 Service 名称注入到容器中。\n# 创建一个 deployment.yaml 文件(引用此处的文档分隔符) cat \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; \u0026gt; deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: selector: matchLabels: run: my-nginx replicas: 2 template: metadata: labels: run: my-nginx spec: containers: - name: my-nginx image: nginx command: [\u0026#34;start\u0026#34;, \u0026#34;--host\u0026#34;, \u0026#34;$(MY_SERVICE_NAME)\u0026#34;] EOF # 创建一个 service.yaml 文件 cat \u0026lt;\u0026lt;EOF \u0026gt; service.yaml apiVersion: v1 kind: Service metadata: name: my-nginx labels: run: my-nginx spec: ports: - port: 80 protocol: TCP selector: run: my-nginx EOF cat \u0026lt;\u0026lt;EOF \u0026gt;./kustomization.yaml namePrefix: dev- nameSuffix: \u0026#34;-001\u0026#34; resources: - deployment.yaml - service.yaml vars: - name: MY_SERVICE_NAME objref: kind: Service name: my-nginx apiVersion: v1 EOF 执行 kubectl kustomize ./ 以查看注入到容器中的 Service 名称是 dev-my-nginx-001:\napiVersion: apps/v1 kind: Deployment metadata: name: dev-my-nginx-001 spec: replicas: 2 selector: matchLabels: run: my-nginx template: metadata: labels: run: my-nginx spec: containers: - command: - start - --host - dev-my-nginx-001 image: nginx name: my-nginx 基准(Bases)与覆盖(Overlays) # Kustomize 中有 基准(bases) 和 覆盖(overlays) 的概念区分。 基准 是包含 kustomization.yaml 文件的一个目录,其中包含一组资源及其相关的定制。 基准可以是本地目录或者来自远程仓库的目录,只要其中存在 kustomization.yaml 文件即可。 覆盖 也是一个目录,其中包含将其他 kustomization 目录当做 bases 来引用的 kustomization.yaml 文件。 基准不了解覆盖的存在,且可被多个覆盖所使用。 覆盖则可以有多个基准,且可针对所有基准中的资源执行组织操作,还可以在其上执行定制。\n# 创建一个包含基准的目录 mkdir base # 创建 base/deployment.yaml cat \u0026lt;\u0026lt;EOF \u0026gt; base/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: selector: matchLabels: run: my-nginx replicas: 2 template: metadata: labels: run: my-nginx spec: containers: - name: my-nginx image: nginx EOF # 创建 base/service.yaml 文件 cat \u0026lt;\u0026lt;EOF \u0026gt; base/service.yaml apiVersion: v1 kind: Service metadata: name: my-nginx labels: run: my-nginx spec: ports: - port: 80 protocol: TCP selector: run: my-nginx EOF # 创建 base/kustomization.yaml cat \u0026lt;\u0026lt;EOF \u0026gt; base/kustomization.yaml resources: - deployment.yaml - service.yaml EOF 此基准可在多个覆盖中使用。你可以在不同的覆盖中添加不同的 namePrefix 或其他贯穿性字段。 下面是两个使用同一基准的覆盖:\nmkdir dev cat \u0026lt;\u0026lt;EOF \u0026gt; dev/kustomization.yaml resources: - ../base namePrefix: dev- EOF mkdir prod cat \u0026lt;\u0026lt;EOF \u0026gt; prod/kustomization.yaml resources: - ../base namePrefix: prod- EOF 如何使用 Kustomize 来应用、查看和删除对象 # 在 kubectl 命令中使用 --kustomize 或 -k 参数来识别被 kustomization.yaml 所管理的资源。 注意 -k 要指向一个 kustomization 目录。例如:\nkubectl apply -k \u0026lt;kustomization 目录\u0026gt;/ 假定使用下面的 kustomization.yaml:\n# 创建 deployment.yaml 文件 cat \u0026lt;\u0026lt;EOF \u0026gt; deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-nginx spec: selector: matchLabels: run: my-nginx replicas: 2 template: metadata: labels: run: my-nginx spec: containers: - name: my-nginx image: nginx ports: - containerPort: 80 EOF # 创建 kustomization.yaml cat \u0026lt;\u0026lt;EOF \u0026gt;./kustomization.yaml namePrefix: dev- commonLabels: app: my-nginx resources: - deployment.yaml EOF 执行下面的命令来应用 Deployment 对象 dev-my-nginx:\n\u0026gt; kubectl apply -k ./ deployment.apps/dev-my-nginx created 运行下面的命令之一来查看 Deployment 对象 dev-my-nginx:\nkubectl get -k ./ kubectl describe -k ./ 执行下面的命令来比较 Deployment 对象 dev-my-nginx 与清单被应用之后集群将处于的状态:\nkubectl diff -k ./ 执行下面的命令删除 Deployment 对象 dev-my-nginx:\n\u0026gt; kubectl delete -k ./ deployment.apps \u0026#34;dev-my-nginx\u0026#34; deleted Kustomize 功能特性列表 # 字段 类型 解释 namespace string 为所有资源添加名字空间 namePrefix string 此字段的值将被添加到所有资源名称前面 nameSuffix string 此字段的值将被添加到所有资源名称后面 commonLabels map[string]string 要添加到所有资源和选择算符的标签 commonAnnotations map[string]string 要添加到所有资源的注解 resources []string 列表中的每个条目都必须能够解析为现有的资源配置文件 configMapGenerator [] ConfigMapArgs 列表中的每个条目都会生成一个 ConfigMap secretGenerator []SecretArgs[] SecretArgs 列表中的每个条目都会生成一个 Secret generatorOptions GeneratorOptions 更改所有 ConfigMap 和 Secret 生成器的行为 bases []string 列表中每个条目都应能解析为一个包含 kustomization.yaml 文件的目录 patchesStrategicMerge []string 列表中每个条目都能解析为某 Kubernetes 对象的策略性合并补丁 patchesJson6902 []Patch[] Patch 列表中每个条目都能解析为一个 Kubernetes 对象和一个 JSON 补丁 vars [] Var 每个条目用来从某资源的字段来析取文字 images []Image[] images 每个条目都用来更改镜像的名称、标记与/或摘要,不必生成补丁 configurations []string 列表中每个条目都应能解析为一个包含 Kustomize 转换器配置 的文件 crds []string 列表中每个条目都应能够解析为 Kubernetes 类别的 OpenAPI 定义文件 « Kubevirt 实践\n» Kubernetes 0-1 Pod中的livenessProbe和readinessProbe解读\n"},{"id":150,"href":"/kubernetes/liveness-readiness-probe/","title":"Liveness Readiness Probe","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 Pod中的livenessProbe和readinessProbe解读\nKubernetes 0-1 Pod中的livenessProbe和readinessProbe解读 # 写在前面 # K8s对Pod的健康状态可以通过两类探针来检查:livenessProbe和readinessProbe,kubelet通过定期执行这两类探针来诊断容器的健康状况。\nlivenessProbe简介 # 存活指针,判断Pod(中的应用容器)是否健康,可以理解为健康检查。我们使用livenessProbe来定期的去探测,如果探测成功,则Pod状态可以判定为Running;如果探测失败,可kubectl会根据Pod的重启策略来重启容器。\n如果未给Pod设置livenessProbe,则默认探针永远返回Success。\n当我们执行kubectl get pods命令,输出信息中STATUS一列我们可以看到Pod是否处于Running状态。\nreadinessProbe简介 # 就绪指针,就绪的意思是已经准备好了,Pod的就绪我们可以理解为这个Pod可以接受请求和访问。我们使用readinessProbe来定期的去探测,如果探测成功,则Pod 的Ready状态判定为True;如果探测失败,Pod的Ready状态判定为False。\n与livenessProbe不同的是,kubelet不会对readinessProbe的探测情况有重启操作。\n当我们执行kubectl get pods命令,输出信息中READY一列我们可以看到Pod的READY状态是否为True。\n定义参数 # livenessProbe和readinessProbe的定义参数是一致的,可以通过kubectl explain pods.spec.containers.readinessProbe或kubectl explain pods.spec.containers.livenessProbe命令了解:\n就绪探针的几种类型:\nhttpGet:\n向容器发送Http Get请求,调用成功(通过Http状态码判断)则确定Pod就绪;\n使用方式:\nlivenessProbe: httpGet: path: /app/healthz port: 80 exec:\n在容器内执行某命令,命令执行成功(通过命令退出状态码为0判断)则确定Pod就绪;\n使用方式:\nlivenessProbe: exec: command: - cat - /app/healthz tcpSocket:\n打开一个TCP连接到容器的指定端口,连接成功建立则确定Pod就绪。\n使用方式:\nlivenessProbe: tcpSocket: port: 80 一般就绪探针会在启动容器一段时间后才开始第一次的就绪探测,之后做周期性探测。所以在定义就绪指针时,会给以下几个参数:\ninitialDelaySeconds:在初始化容器多少秒后开始第一次就绪探测; timeoutSeconds:如果该次就绪探测超过多少秒后还未成功,判定为超时,该次探测失败,Pod不就绪。默认值1,最小值1; periodSeconds:如果Pod未就绪,则每隔多少秒周期性的做就绪探测。默认值10,最小值1; failureThreshold:如果容器之前探测成功,后续连续几次探测失败,则确定容器未就绪。默认值3,最小值1; successThreshold:如果容器之前探测失败,后续连续几次探测成功,则确定容器就绪。默认值1,最小值1。 使用示例 # 目前我在docker hub有一个测试镜像:poneding/helloweb:v1,容器启动后,有一个健康检查路由/healthz/return200,访问该路由状态码返回200;有一个检查路由/health/return404,访问该路由状态码返回404。\nreadinessProbe示例 # 在实验之前先了解一下Pod和Service的负载均衡关系:在K8s中,Service作为Pod的负载均衡器,是通过Label Selector匹配Pod的。但是这句话没有说完整,因为还有一个必要条件:Pod当前已经就绪。也就是说,Service通过Label Selector匹配当前就绪的Pod,还未就绪的Pod就算labelSelector匹配上了,也不会出现在Service的endpoints中,请求是不会被转发过去的,如下图示例。\n示例说明:我们使用poneding/helloweb:v1镜像启动三个Pod,三个Pod的Label都设置成一样,为了使Service匹配到;三个Pod其中两个readinessProbe使用httpGet探测/health/return200,模拟探测成功,一个readinessProbe使用httpGet探测/health/return404,模拟探测失败。\n编写我们的helloweb-readinessProbe.yaml文件如下:\napiVersion: v1 kind: Pod metadata: name: helloweb1 labels: app: helloweb spec: containers: - name: helloweb image: poneding/helloweb:v1 readinessProbe: httpGet: path: /healthz/return200 port: 80 initialDelaySeconds: 30 timeoutSeconds: 10 ports: - containerPort: 80 --- apiVersion: v1 kind: Pod metadata: name: helloweb2 labels: app: helloweb spec: containers: - name: helloweb image: poneding/helloweb:v1 readinessProbe: httpGet: path: /healthz/return200 port: 80 initialDelaySeconds: 30 timeoutSeconds: 10 ports: - containerPort: 80 --- apiVersion: v1 kind: Pod metadata: name: helloweb3 labels: app: helloweb spec: containers: - name: helloweb image: poneding/helloweb:v1 readinessProbe: httpGet: path: /healthz/return404 port: 80 initialDelaySeconds: 30 timeoutSeconds: 10 ports: - containerPort: 80 --- apiVersion: v1 kind: Service metadata: name: helloweb spec: selector: app: helloweb type: ClusterIP ports: - name: http port: 80 targetPort: 80 运行命令部署Pod和Service:\nkubectl apply -f helloweb-readinessProbe.yaml 之后,我们查看Pod的就绪情况:\n可以看到Pod只有helloweb1和helloweb2当前使处于READY(就绪)的状态,helloweb3尚未Ready,我们接着查看helloweb service的endpoints:\n可以看到Service的EndPoints只将helloweb1、helloweb2 pod的IP负载上了。\n查看日志访问情况:\n可以看到每隔10秒(readniessProbe.periodSeconds默认10s)就会做一次就绪探测。\nlivenessProbe示例 # 编写我们的helloweb-livenessProbe.yaml文件如下:\napiVersion: v1 kind: Pod metadata: name: helloweb4 labels: app: helloweb spec: containers: - name: helloweb image: poneding/helloweb:v1 livenessProbe: httpGet: path: /healthz/return200 port: 80 initialDelaySeconds: 30 timeoutSeconds: 10 ports: - containerPort: 80 --- apiVersion: v1 kind: Pod metadata: name: helloweb5 labels: app: helloweb spec: containers: - name: helloweb image: poneding/helloweb:v1 livenessProbe: httpGet: path: /healthz/return404 port: 80 initialDelaySeconds: 30 timeoutSeconds: 10 ports: - containerPort: 80 运行命令部署Pod和Service:\nkubectl apply -f helloweb-livenessProbe.yaml 之后,我们查看Pod的就绪情况:\n可以看到helloweb4的STATUS状态为Running,而helloweb5的STATUS状态最终变为CrashLoopBackOff,并且一直在重启。\n相信到这里,你已经对readniessProbe和livenessProbe有一个清晰的了解了。\n« Kustomize\n» local 存储卷实践\n"},{"id":151,"href":"/kubernetes/local-storageclass/","title":"Local Storageclass","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / local 存储卷实践\nlocal 存储卷实践 # 在 Kubernetes 中有一种存储卷类型为 local。\nlocal 卷所代表的是某个被挂载的本地存储设备,例如磁盘、分区或者目录。\nlocal 卷只能用作静态创建的持久卷。不支持动态配置。\n与 hostPath 卷相比,local 卷能够以持久和可移植的方式使用,而无需手动将 Pod 调度到节点。系统通过查看 PersistentVolume 的节点亲和性配置,就能了解卷的节点约束。\n然而,local 卷仍然取决于底层节点的可用性,并不适合所有应用程序。 如果节点变得不健康,那么 local 卷也将变得不可被 Pod 访问。使用它的 Pod 将不能运行。 使用 local 卷的应用程序必须能够容忍这种可用性的降低,以及因底层磁盘的耐用性特征而带来的潜在的数据丢失风险。\n创建 local-storage 存储类 # apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: local-storage provisioner: kubernetes.io/no-provisioner volumeBindingMode: WaitForFirstConsumer 手动创建 PV/PVC # 1、使用 local 卷时,你需要设置 PersistentVolume 对象的 nodeAffinity 字段。 Kubernetes 调度器使用 PersistentVolume 的 nodeAffinity 信息来将使用 local 卷的 Pod 调度到正确的节点;\n2、PersistentVolume 对象的 volumeMode 字段可被设置为 \u0026ldquo;Block\u0026rdquo; (而不是默认值 \u0026ldquo;Filesystem\u0026rdquo;),以将 local 卷作为原始块设备暴露出来。\napiVersion: v1 kind: PersistentVolumeClaim metadata: name: mysql-local-pvc namespace: default spec: accessModes: - ReadWriteOnce resources: requests: storage: 128Mi storageClassName: local-storage volumeMode: Filesystem volumeName: mysql-local-pv --- apiVersion: v1 kind: PersistentVolume metadata: name: mysql-local-pv spec: capacity: storage: 128Mi volumeMode: Filesystem accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Delete storageClassName: local-storage local: path: /mysql-data nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - worker-01 ⚠ 注意:\n1、指定亲和节点名称,示例中为 worker-01;\n2、指定的节点上需要确保本地目录是存在的,示例中为 /mysql-data。\n创建 Pod # apiVersion: apps/v1 kind: StatefulSet metadata: name: mysql labels: app: mysql spec: selector: matchLabels: app: mysql serviceName: mysql replicas: 1 template: metadata: labels: app: mysql spec: containers: - name: mysql image: mysql:8.0 env: - name: MYSQL_ROOT_PASSWORD value: \u0026#34;123456\u0026#34; volumeMounts: - name: data mountPath: /var/lib/mysql restartPolicy: Always volumes: - name: data persistentVolumeClaim: claimName: mysql-local-pvc 参考 # https://kubernetes.io/zh-cn/docs/concepts/storage/volumes/#local lovo 项目 # 基于 local 存储卷实现一个控制器动态管理 PV。\n« Kubernetes 0-1 Pod中的livenessProbe和readinessProbe解读\n» Kubernetes 0-1 K8s自建LoadBalancer\n"},{"id":152,"href":"/kubernetes/metallb/","title":"Metallb","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 K8s自建LoadBalancer\nKubernetes 0-1 K8s自建LoadBalancer # Metallb介绍 # 一般只有云平台支持LoadBalancer,如果脱离云平台,自己搭建的K8s集群,Service的类型使用LoadBalancer是没有任何效果的。为了让私有网络中的K8s集群也能体验到LoadBalabcer,Metallb成为了解决方案。\nMetallb运行在K8s集群中,监视集群内LoadBalancer类型的服务,然后从配置的IP池中为其分配一个可用IP,以ARP/NDP或BGP的方式将其广播出去,这个可用IP成为了LoadBalancer的Url,可供集群外访问。\nMetallb搭建过程 # 创建命名空间 metallb-system:\nvim metallb-namespace.yaml 写入文件内容:\napiVersion: v1 kind: Namespace metadata: name: metallb-system 下载metallb.yaml文件\nwget https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/metallb.yaml -O metallb.yaml --no-check-certificate 定义LoadBalancer的IP池,先创建configmap\nvim metallb-configMap.yaml 写入文件内容:\napiVersion: v1 kind: ConfigMap metadata: namespace: metallb-system name: config data: config: | address-pools: - name: default protocol: layer2 addresses: - 192.168.115.140-192.168.115.199 注意:IP池的网络需要和K8s集群的IP处于同一网段,我的K8s集群网络是192.168.115.13x,这里IP池则是给到192.168.115.140-192.168.115.199的范围。\n执行命令:\nkubectl apply -f metallb-namespace.yaml kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey=\u0026#34;$(openssl rand -base64 128)\u0026#34; kubectl apply -f metallb.yaml kubectl apply -f metallb-configMap.yaml LoadBalancer测试 # 我们使用类型为LoadBalancer的Service进行测试,以nginx服务为例。\nkubectl create deployment nginx --image=nginx kubectl expose deployment nginx --port=80 --type=LoadBalancer 查看nginx服务:\n[root@k8s-master01 test]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.0.0.1 \u0026lt;none\u0026gt; 443/TCP 11h nginx LoadBalancer 10.0.0.82 192.168.115.140 80:31610/TCP 8s 可以看到,已经为nginx服务分配了一个192.168.115.140 的IP,直接在浏览器中访问,一切正常。\n« local 存储卷实践\n» 使用 nfs 持久化存储\n"},{"id":153,"href":"/kubernetes/nfs-as-pvc/","title":"Nfs as Pvc","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 使用 nfs 持久化存储\n使用 nfs 持久化存储 # 一般云平台都会提供云存储服务,如 AWS EBS 服务,K8s 可以直接使用云存储服务创建 PV 和 PVC 作为 Volume 的存储后端。假设你没有使用到云存储,那么 NFS 可能会适合你。\nNFS(Network File System),网络文件系统,允许计算机之间共享存储资源,这里也就不具体介绍了。\n部署 nfs # 以下命令需要root权限,示例中机器IP为192.168.115.137。\n安装 nfs: # Ubuntu \u0026amp; Debian apt install nfs-kernel-server -y # CentOS yum install nfs-util -y 创建共享目录: mkdir /nfs/data -p 修改 nfs 的默认配置,在文末添加配置: vim /etc/exports /nfs/data *(rw,sync,no_root_squash) 其中:\n/nfs/data:共享目录 *:对所有开放访问,可以配置成网段,IP,域名等 rw:读写权限 sync:文件同时写入磁盘和内存 no_root_squash:当登录 NFS 主机使用共享目录的使用者是 root 时,其权限将被转换成为匿名使用者,通常它的 UID 与 GID,都会变成 nobody 身份 重启 rpc,nfs 需要向 rpc 注册: systemctl restart rpcbind.service systemctl enable rpcbind.service 重启 nfs 服务: systemctl restart nfs-kernel-server.service systemctl enable nfs-kernel-server.service 挂载共享,在 fstab 文件中添加配置: vim /etc/fstab 192.168.115.137:/nfs/data /nfs/data nfs rw,tcp,soft 0 0 创建 PV # PersistentVolume 作为 K8s 的存储资源,我们需要为它定义 apacity(存储能力),accessModes(访问模式)persistentVolumeReclaimPolicy(回收策略),存储媒介等信息。\n下面定义了一个 pv 资源,文件名为 pv1.yaml\napiVersion: v1 kind: PersistentVolume metadata: name: pv1 spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Recycle nfs: path: /nfs/data server: 192.168.115.137 需要对 AccessModes 和 PersistentVolumeReclaimPolicy 做简单的枚举介绍:\nAccessModes 访问模式 # 设置对 PV 存储资源的访问权限:\nReadWriteOnce(RWO):读写权限,只能被单个实例挂载\nReadOnlyMany(ROX):只读权限,可以被多个实例挂载\nReadWriteMany(RWX):读写权限,可以被多个实例挂载\nPersistentVolumeReclaimPolicy 回收策略 # 当挂载实例被删除时,设置对 PV 存储资源的回收策略:\nRetain:保留 Recycle:清除 Delete:删除,一般用于云存储服务删除对应的资源,如 AWS 的 EBS 现在创建 pv:\nkubectl apply -f pv1.yaml 查看 pv,可以看到当前 pv1 的 STATUS 为 Available:\nkubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv1 1Gi RWO Recycle Available 48s 这里也需要对 PV 的状态作下简单介绍:\nStatus # PV 的生命周期包含了四个阶段:\nAvaliable:当前未绑定 PVC,处于可用状态\nBound:已经被 PVC 绑定,不可用\nReleased:之前被 PVC 绑定,PVC 删除时,PV 会被重新声明,很短暂的一段时间内处于 Released 状态\nFailed:PV 自动回收失败\n创建 PVC # 下面定义了一个 pvc 资源,文件名为 pvc1.yaml\napiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc1 spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi 现在创建 pvc\nkubectl apply -f pvc1.yaml 查看 PVC,可以看到 pvc1 已经处于 Bound 的状态,说明它已经绑定上了一个 PV,而我们目前只创建了一个名为 pv1 的 PV\nNAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE pvc1 Bound pv1 1Gi RWO 3s 再次查看 PV,看看是不是被 pvc1 绑定了。\nNAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv1 1Gi RWO Recycle Bound default/pvc1 2m 确实如我们所想,pv1 被 pvc1 绑定,并且 pv1 的状态也更新成了 Bound。其实这是集群自动为我们的 PVC 寻找到的符合条件的 PV:\n当前状态处于 Avaliable 的 PV PV 容量 \u0026gt;= PVC 申请容量 pvc 和 pv 的 AccessMode 需要一致 当然,我们也可以通过标签选择器为 PVC 指定绑定的 PV。\n定义 PV:\n... kind: PersistentVolume metadata: labels: app: pv1 .... 定义 PVC:\n... kind: PersistentVolumeClaim spec: selector: matchLabels: app: pv1 ... Volume 使用 PVC # ... volumes: - name: www persistentVolumeClaim: claimName: pvc1 ... « Kubernetes 0-1 K8s自建LoadBalancer\n» Kubernetes 0-1 了解 Pod\n"},{"id":154,"href":"/kubernetes/pod-understood/","title":"Pod Understood","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 了解 Pod\nKubernetes 0-1 了解 Pod # Pod 介绍 # Pod,是 K8s 对象模型中的最小单元,Pod 里面包含着一组容器(单个容器或多个紧密耦合的容器),这时候 Pod 可以理解为一个机器,而 Pod 里面的容器则理解为该机器里面的进程。\nPod 的容器运行时由容器引擎提供,默认的容器引擎是 Docker;并且 K8s 管理的是 Pod,而不是容器。\n一个 Pod 内部的容器共享:\n存储:一个 Pod 可以指定一组共享存储卷。 网络:每个 Pod 分配一个唯一 IP(集群内 IP),共享网络命名空间,包括 IP 地址和网络端口。Pod 内的容器可以使用 localhost 互相通信,集群内 Pod 与 Pod通信可以使用 Pod 分配的 IP,但是由于 Pod 的 IP 是随机分配的,这种互通信的方式不太适合使用。 尽管一个 Pod 内可以包含多个 Pod,但我们在部署应用容器时的最佳实践是一个 Pod 里面只包含一个应用容器作为主容器,其他容器为主容器服务,称之为辅助容器。例如主容器崩溃了,会有一个辅助容器去重启主容器。辅助容器可以有也可以没有,因为 Pod 里面容器的生命周期可以被 Pod 的生命周期取代,而 Pod 的生命周期可以通过 Pod 管理器来管理维护。\n将我们应用服务隔离单独部署在 Pod 的好处可以罗列以下:\nPod 可以分别的调度到各个 K8s 节点,充分利用了节点的计算资源; 方便我们单独为某个应用服务做扩缩操作。 Pod 创建 # 在K8s集群中一般不会直接单独创建 Pod,而是通过 Pod 管理器。如果单独创建 Pod,Pod的进程被结束的话,Pod 就永远被删除;使用 Pod 管理器创建出来的Pod,Pod 管理器会负责保证Pod按期调度,即使 Pod 被删除,也会重新被调度起来。简而言之,Pod 的生存由 Pod 管理器全权负责。\nPod 管理器包含很多种,由很早的 ReplicationController 过渡到 ReplicaSet 再过渡到当前普遍使用的 Deployment,其实这三者能做的事情是类似的,都是调度和监视 Pod 列表,保证 Pod 列表与声明的数量和其他期望相符。\n除此之外还有其他的管理器:\nStatefuleSet:带状态的 Pod 管理器,需要持久存储数据,一般用于创建数据库类型的应用实例,如 mysql,redis; DaemonSet:每个符合条件的Node都分配一个 Pod,一般用于创建 agent 服务,如日志收集组件,指标数据收集组件等。 一般通过以下方式创建 Pod\n单行命令创建 Pod kubectl run nginx --image=nginx:latest --replicas=2 以上命令实际上是创建了一个 Deployment 资源和由其管理的 2 个 Pod。\n定义资源清单,创建 Pod 以yaml或者json格式定义 Pod 资源,大都选择 yaml。如果你使用VSCode的话,那么 Kubernetes Support 插件会成为你的利器。\n我们先定义个一个 Pod 的资源文件 pod-sample.yaml:\napiVersion: v1 kind: Pod metadata: name: nginx labels: name: nginx spec: containers: - name: nginx image: nginx imagePullPolicy: Always restartPolicy: Never resources: requests: memory: \u0026#34;128Mi\u0026#34; cpu: \u0026#34;256m\u0026#34; limits: memory: \u0026#34;256Mi\u0026#34; cpu: \u0026#34;512m\u0026#34; ports: - containerPort: 80 以上是一个简单的Pod定义文件,我们可以从这个文件得到这些信息:\n运行nginx镜像,作为Pod的主容器,向外暴露80端口; 每次启动这个Pod都会从网络拉取镜像; 如果主容器不小心挂了,则不会被重启; 容器启动需要的最小资源和运行最大资源。 然后通过 kubectl 命令创建(下面这行命令也适用于更新 Pod):\nkubectl apply -f pod-sample.yaml 查看Pod:\nkubectl get pod -o wide 通过查看 Pod 的描述、Pod 里面容器的运行日志,或直接进入 Pod 容器分析定位问题:\n# 描述 Pod 详情 kubectl describe pod \u0026lt;POD_NAME\u0026gt; # 查看 Pod 容器控制台运行日志 kubectl logs \u0026lt;POD_NAME\u0026gt; # 进入 Pod kubectl exec -it \u0026lt;POD_NAME\u0026gt; -c \u0026lt;CONTAINER_NAME\u0026gt; -- \u0026lt;COMMAND\u0026gt; 删除 Pod:\nkubectl delete -f pod-sample.yaml kubectl delete pod \u0026lt;POD_NAME\u0026gt; Pod 字段 # 通过以下命令查看定义 Pod 资源的字段即作用:\nkubectl explain pod kubectl explain pod.spec 对一些字段简单介绍一下:\nimagePullPolicy # 镜像拉取策略,有三种,Always、IfNotPresent、Never\nAlways:每次都拉取最新镜像,默认策略; IfNotPresent:如果 Pod 被调度的Node上已经存在镜像了则直接使用镜像,不存在在拉取; Never:只使用 Node 上的镜像,即使不存在也不拉取。 restartPolicy # Pod 重启策略,有三种:Always、OnFaliure、Never\nAlways:Pod 只要终止运行,kubelet 就会重启它; OnFaliure:Pod非正常终止,退出码不为零,kubelet 就会重启它,正常退出不会重启; Never:退出了就不重启。 nodeSelector # 定义Lable对,选择调度到拥有该 Label 对的 Node 节点。\nlivenessProbe # 存活指针,可以理解为 Pod 内容器运行的健康检查,如果健康检查没通过,则重启 Pod 内容器,这里面的内容有点多,有机会详细讲。\nreadinessProbe # 就绪指针,也是通过健康检查机制,对外呈现 Pod 的就绪状态,如果健康检查通过,Pod 状态为就绪,可以接受外部流量请求。流量无法转发到非就绪状态的 Pod。\ncommand # 容器启动时的命令列表,和 Dockerfile 中定义的 CMD 作用一样。\nargs # 容器启动命令的参数。\nenv # 容器内的环境变量列表,和 Dockerfile 中定义的 ENV 作用一样。\nresource # 可以定义容器启动的最小字段和运行最大分配资源,对 Pod 的资源使用的控制。\nPod 日志 # kubelet 定义了 pod 的日志路径,宿主机目录:\n/var/log/pods/\u0026lt;pod-namespace\u0026gt;_\u0026lt;pod-name\u0026gt;_\u0026lt;pod-uid\u0026gt;/\u0026lt;contianer name\u0026gt;/\u0026lt;restart count\u0026gt;.log 日志文件名为 0.log,1.log\u0026hellip;,这里数字使用的是容器重启的次数。\n« 使用 nfs 持久化存储\n» Kubernetes 编程\n"},{"id":155,"href":"/kubernetes/prgramming-kubernetes/","title":"Prgramming Kubernetes","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 编程\nKubernetes 编程 # 开始 Kuberntes 编程之旅~\nmkdir programming-kubernetes \u0026amp;\u0026amp; cd programming-kubernetes git mod init programming-kubernetes 常用包:\nkubeconfig 对应的结构 clientcmdapi \u0026#34;k8s.io/client-go/tools/clientcmd/api\u0026#34; clientcmdapi.Config 编写自定义 API # 随机生成字符 \u0026#34;k8s.io/apimachinery/pkg/util/rand\u0026#34; rand.String(5) 参考:\nkubernetes/code-generator: Generators for kube-like API types (github.com)\ncode-generator 使用\nB站 code-generator 介绍\nmkdir hack vim hack/boilerplate.go.txt /* Copyright 2022 programming-kubernetes. Licensed under the Apache License, Version 2.0 (the \u0026#34;License\u0026#34;); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \u0026#34;AS IS\u0026#34; BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ mkdir -p pkg/apis/demo/v1alpha vim pkg/apis/demo/v1alpha/doc.go /* Copyright 2022 programming-kubernetes. Licensed under the Apache License, Version 2.0 (the \u0026#34;License\u0026#34;); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \u0026#34;AS IS\u0026#34; BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ // +k8s:deepcopy-gen=package // +groupName=demo.pk.io package v1alpha1 文件名称一定要是 doc.go,要不然无法成功生成 deepcopy 代码;\n// +groupName=demo.pk.io,将根据该注释在 client 中生成对应的组\nvim pkg/apis/demo/v1alpha/types.go /* Copyright 2022 programming-kubernetes. Licensed under the Apache License, Version 2.0 (the \u0026#34;License\u0026#34;); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \u0026#34;AS IS\u0026#34; BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1alpha1 import ( corev1 \u0026#34;k8s.io/api/core/v1\u0026#34; metav1 \u0026#34;k8s.io/apimachinery/pkg/apis/meta/v1\u0026#34; ) // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // Foo - type Foo struct { metav1.TypeMeta `json:\u0026#34;,inline\u0026#34;` metav1.ObjectMeta `json:\u0026#34;metadata,omitempty\u0026#34; protobuf:\u0026#34;bytes,1,opt,name=metadata\u0026#34;` Spec FooSpec `json:\u0026#34;spec,omitempty\u0026#34; protobuf:\u0026#34;bytes,2,opt,name=spec\u0026#34;` Status FooStatus `json:\u0026#34;status,omitempty\u0026#34; protobuf:\u0026#34;bytes,3,opt,name=status\u0026#34;` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type FooList struct { metav1.TypeMeta `json:\u0026#34;,inline\u0026#34;` metav1.ListMeta `json:\u0026#34;metadata,omitempty\u0026#34; protobuf:\u0026#34;bytes,1,opt,name=metadata\u0026#34;` Items []Foo `json:\u0026#34;items\u0026#34; protobuf:\u0026#34;bytes,2,rep,name=items\u0026#34;` } type FooSpec struct { Name string `json:\u0026#34;name\u0026#34; protobuf:\u0026#34;bytes,1,opt,name=name\u0026#34;` Desc string `json:\u0026#34;desc\u0026#34; protobuf:\u0026#34;bytes,2,opt,name=desc\u0026#34;` } type FooStatus struct { Conditions []FooCondition `json:\u0026#34;conditions,omitempty\u0026#34; protobuf:\u0026#34;bytes,1,rep,name=conditions\u0026#34;` } type FooCondition struct { // Type of team condition. Type FooConditionType `json:\u0026#34;type\u0026#34; protobuf:\u0026#34;bytes,1,opt,name=type,casttype=TeamConditionType\u0026#34;` // Status of the condition, one of True, False, Unknown. Status corev1.ConditionStatus `json:\u0026#34;status\u0026#34; protobuf:\u0026#34;bytes,2,opt,name=status,casttype=k8s.io/api/core/v1.ConditionStatus\u0026#34;` // Last time the condition transitioned from one status to another. // +optional LastTransitionTime metav1.Time `json:\u0026#34;lastTransitionTime,omitempty\u0026#34; protobuf:\u0026#34;bytes,3,opt,name=lastTransitionTime\u0026#34;` // The reason for the condition\u0026#39;s last transition. // +optional Reason string `json:\u0026#34;reason,omitempty\u0026#34; protobuf:\u0026#34;bytes,4,opt,name=reason\u0026#34;` // A human readable message indicating details about the transition. // +optional Message string `json:\u0026#34;message,omitempty\u0026#34; protobuf:\u0026#34;bytes,5,opt,name=message\u0026#34;` } type FooConditionType string vim pkg/apis/demo/v1alpha/register.go /* Copyright 2022 Ketches. Licensed under the Apache License, Version 2.0 (the \u0026#34;License\u0026#34;); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an \u0026#34;AS IS\u0026#34; BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package v1alpha1 import ( metav1 \u0026#34;k8s.io/apimachinery/pkg/apis/meta/v1\u0026#34; \u0026#34;k8s.io/apimachinery/pkg/runtime\u0026#34; \u0026#34;k8s.io/apimachinery/pkg/runtime/schema\u0026#34; ) var SchemeGroupVersion = schema.GroupVersion{Group: \u0026#34;demo.pk.io\u0026#34;, Version: \u0026#34;v1alpha1\u0026#34;} var ( SchemeBuilder runtime.SchemeBuilder localSchemeBuilder = \u0026amp;SchemeBuilder AddToScheme = localSchemeBuilder.AddToScheme ) func init() { // We only register manually written functions here. The registration of the // generated functions takes place in the generated files. The separation // makes the code compile even when the generated files are missing. localSchemeBuilder.Register(addKnownTypes) } // Resource takes an unqualified resource and returns a Group qualified GroupResource func Resource(resource string) schema.GroupResource { return SchemeGroupVersion.WithResource(resource).GroupResource() } // Adds the list of known types to the given scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, \u0026amp;Foo{}, \u0026amp;FooList{}, ) scheme.AddKnownTypes(SchemeGroupVersion, \u0026amp;metav1.Status{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil } 需要在机器上克隆下 k8s.io/code-generator 仓库:\nmkdir -p $GOPATH/src/k8s.io \u0026amp;\u0026amp; cd $GOPATH/src/k8s.io git clone https://github.com/kubernetes/code-generator.git 回到 programming-kubernetes/hack 项目目录:\nmkdir hack \u0026amp;\u0026amp; cd hack vim update-codegen.sh #!/usr/bin/env bash # Copyright 2022 Ketches. # Licensed under the Apache License, Version 2.0 (the \u0026#34;License\u0026#34;); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an \u0026#34;AS IS\u0026#34; BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. set -o errexit set -o nounset set -o pipefail SCRIPT_ROOT=$(dirname \u0026#34;${BASH_SOURCE[0]}\u0026#34;)/.. # generate the code with: # - --output-base because this script should also be able to run inside the vendor dir of # k8s.io/kubernetes. The output-base is needed for the generators to output into the vendor dir # instead of the $GOPATH directly. For normal projects this can be dropped. ~/go/src/k8s.io/code-generator/generate-groups.sh \\ all \\ programming-kubernetes/pkg/client \\ programming-kubernetes/pkg/apis \\ \u0026#34;demo.dev:v1alpha1\u0026#34; \\ --output-base ${SCRIPT_ROOT}/.. \\ --go-header-file ${SCRIPT_ROOT}/hack/boilerplate.go.txt \u0026ldquo;demo:v1alpha1\u0026rdquo; 需要保持 api 的目录一致,而不是 groupname\n如果项目 mod 名称中包含 “/”,那么项目也需要放在完整的目录下,例如 mod 名称为 github.com/poneding/programming-kubernetes,那么项目一定要放在 [..]/github.com/poneding/programming-kubernetes 下,此时 --output-base 也需要调整,需要增加目录查找长度 ${SCRIPT_ROOT}/../../..。\n测试使用自定义 API 的 client,infromer,lister 等:\n# programming-kubernets 项目目录 mkdir -p manifests/crd vim manitests/crd/demo-foo.yaml 安装 controller-gen 工具\ngo install sigs.k8s.io/controller-tools/cmd/controller-gen@latest 生成 crd yaml 文件\ncontroller-gen « Kubernetes 0-1 了解 Pod\n» Prometheus-监控Kong完整操作\n"},{"id":156,"href":"/kubernetes/prometheus-collect-kong-metrics/","title":"Prometheus Collect Kong Metrics","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Prometheus-监控Kong完整操作\nPrometheus-监控Kong完整操作 # 本篇记录使用Prometheus收集Kong暴露的/metrics接口,收集指标数据,从而实现对Kong的监控。\n先决条件 # Prometheus部署完成; Kong(Kong 服务,端口8000)部署完成; Kong 的Admin Api(端口8001)部署完成 Konga(Kong的WebUI,端口1337)部署完成。 Kong添加Prometheus插件 # 登录进入Konga; 点击右边菜单栏”PLUGINS“,进入Plugins管理,点击“Analytics \u0026amp; Monitoring”,选择添加Promethus插件 Kong添加metrics接口 # 我们知道Prometheus主要通过读取 http://host/metrics接口, 来收集相关服务的性能数据,但是Kong的metrics接口服务默认是没有开启的,所以需要先为Kong添加/metrics。\n登录进入Konga; 点击右边菜单栏”SERVICES“,进入Services管理,点击“ADD NEW SERVICE” 添加页面输入“Name”和“Url”参数即可,例如“Name”=“prometheusService”,“Url”=“ http://localhost:8001/metrics” 添加完Prometheus Service之后,Service列表选中并点击进入prometheusService,选择”Routes“菜单,点击“ADD ROUTE” 添加页面输入“Paths”参数即可,例如“Paths”=[“/metrics”](Path必须以“/”为首) 这时候访问“ http://localhost:8000/metrics”,看到页面如下显示,说明已经成功的添加了metrics接口 Prometheus添加Kong指标收集 # 修改Prometheus配置文件,prometheus.yml\nscrape_configs配置项下添加如下配置\n- job_name: \u0026#39;kong\u0026#39; scrape_interval: 5s static_configs: - targets: [\u0026#39;localhost:8000\u0026#39;] 配置完之后重启Prometheus,访问“ http://localhost:9090/graph”\n可以看到一已经生成了很多kong的指标项,如http访问,nginx当前访问量等指标\n« Kubernetes 编程\n» Prometheus\n"},{"id":157,"href":"/kubernetes/prometheus/","title":"Prometheus","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Prometheus\nPrometheus # Intro # 是一个面向云原生应用程序的开源的监控\u0026amp;报警工具。\n开源:继Kubernetes之后的第二个CNCF开源项目,Go语言开发 监控:通过HTTP服务定时从配置的目标收集指标数据,识别数据展示规则,展示监控系统和服务指标数据 报警:监控到的指标数据达到某一设定好的条件时,触发报警机制 时间序列数据库: 时间序列是由唯一的指标名称(Metrics)和一组标签(key=value)的形式组成 PromeQL:基于Prometheus时间序列数据库的一个灵活的查询语言 Prometheus的监控对象 # 资源监控:\n服务器的资源使用情况,在Kubernetes集群中,则可以做到对Kubernetes Node、 Deployment 、Pod的资源利用以及apiserver,controller-manager,etcd等组件的监控。\n应用监控:\n业务应用暴露端口供Prometheus调用实现监测,比如实时的用户在线人数,数据库的当前连接数等。\nPrometheus优势 # 支持机器资源和动态配置的应用监控; 多维数据收集和查询; 服务独立,少依赖。 Prometheus组件 # Prometheus Server:采集监控数据,存储时序指标,提供数据查询; Prometheus Client SDK:对接开发工具包,提供自定义的指标数据等; Push Gateway:推送指标数据的网关组件; Third-part Exporter:外部指标采集系统,暴露接口供Prometheus采集; AlertManager:指标数据的分析告警管理器; Architecture overview # 上图来源于官网:\n处理流程: 配置资源目标或应用抓取; 抓取资源或应用指标数据; 制定报警规则,推送报警; 灵活查询语言,结合Grafana展示 Installation \u0026amp; Start Up # 1. 以服务进程运行Prometheus # ​ 在ubuntu系统上安装Prometheus,一般有两种方式\n第一种,安装命令如下:\nwget https://github.com/prometheus/prometheus/releases/download/v2.13.1/prometheus-2.13.1.linux-amd64.tar.gz tar xvfz prometheus-2.13.1.linux-amd64.tar.gz # 启动Prometheus cd prometheus-2.13.1.linux-amd64 ./prometheus # 停止Prometheus ps -ef | grep prometheus # 定位Prometheus进程的pid kill -s 9 [pid] 第二种,安装命令如下(ubuntu系统):\nwget https://s3-eu-west-1.amazonaws.com/deb.robustperception.io/41EFC99D.gpg | sudo apt-key add - sudo apt-get update -y sudo apt-get install prometheus prometheus-node-exporter prometheus-pushgateway prometheus-alertmanager -y # 启动Prometheus sudo systemctl start prometheus sudo systemctl enable prometheus sudo systemctl status prometheus Service Proxy # 默认Prometheus的访问方式是http://{ip/domain-name}:9090,而服务器一般的是不会暴露9090端口的,而是暴露80端口,其他端口以nginx代理访问。\n下面的步骤便是将http://{ip/domain-name}:9090代理为http://{ip/domain-name}/prometheus\n​ Step 1 配置prometheus:\n配置\u0026ndash;web.external-url\n如果是第一种方式安装的Prometheus,则启动Prometheus的命令行需要附带\u0026ndash;web.external-url=prometheus,完整命令如下:\n./prometheus --web.external-url=prometheus 如果是第二种方式安装的Prometheus,则需要在/etc/default/prometheus文件在ARGS=\u0026quot;\u0026quot;中添加参数--web.external-url=prometheus,添加完之后,文件应该是像下面这样的:\n重启prometheus\nsudo systemctl restart prometheus 重启之后,http://{ip/domain-name}:9090/prometheus可以访问。\n​ Step 2 配置nginx:\n默认服务器已经安装了nginx。\nnginx.conf文件,注释掉以下行:\n#include /etc/nginx/sites-enabled/*;\n执行以下命令:\nsudo vim /etc/nginx/conf.d/prometheus.conf 在文件中写入以下内容:\nserver { location /prometheus { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; proxy_pass http://172.31.23.19:9090/prometheus; break; } } 重启nginx\nsudo systemctl restart nginx.service 重启之后,http://{ip/domain-name}/prometheus可以访问。\n停止Prometheus # sudo systemctl stop prometheus 2.Docker启动Prometheus # docker run --name prometheus -d -p 127.0.0.1:9090:9090 prom/prometheus 3. Kubernetes运行Prometheus # ConfigMap # Prometheus的配置文件\napiVersion: v1 kind: ConfigMap metadata: name: prometheus-config namespace: ns-prometheus data: prometheus.yml: | global: scrape_interval: 15s scrape_timeout: 15s scrape_configs: - job_name: \u0026#39;prometheus\u0026#39; static_configs: - targets: [\u0026#39;localhost:9090\u0026#39;] Deployment # apiVersion: app/v1 kind: Deployment metadata: name: prometheus namespace: ns-prometheus labels: app: prometheus spec: template: metadata: labels: app: prometheus spec: containers: - image: prom/prometheus name: prometheus imagePullPolicy: IfNotPresent args: - \u0026#34;--config.file=/etc/prometheus/prometheus.yml\u0026#34; - \u0026#34;--storage.tsdb.path=/prometheus\u0026#34; - \u0026#34;--storage.tsdb.retention=7d\u0026#34; - \u0026#34;--web.enable-admin-api\u0026#34; - \u0026#34;--web.enable-lifecycle\u0026#34; ports: - containerPort: 9090 name: http volumeMounts: - mountPath: \u0026#34;/prometheus\u0026#34; subPath: prometheus name: data - mountPath: \u0026#34;/etc/prometheus\u0026#34; name: config resources: requests: cpu: 1000m memory: 2Gi limits: cpu: 1000m memory: 2Gi securityContext: runAsUser: 0 volumes: - name: config configMap: name: prometheus-config - name: data persistentVolumeClaim: claimName: prometheus PersistentVolumeClaim # apiVersion: v1 kind: PersistentVolumeClaim metadata: name: prometheus namespace: ns-prometheus spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi storageClassName: \u0026#34;rook-ceph-block\u0026#34; ServiceAccount # apiVersion: v1 kind: ServiceAccount metadata: name: prometheus namespace: ns-prometheus --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: prometheus rules: - apiGroups: - \u0026#34;\u0026#34; resources: - nodes - services - endpoints - pods - nodes/proxy verbs: - get - list - watch - apiGroups: - \u0026#34;\u0026#34; resources: - configmaps - nodes/metrics verbs: - get - nonResourceURLs: - /metrics verbs: - get --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: prometheus roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: prometheus subjects: - kind: ServiceAccount name: prometheus namespace: kube-ops Service # apiVersion: v1 kind: Service metadata: name: prometheus namespace: ns-prometheus labels: app: prometheus spec: selector: app: prometheus type: NodePort ports: - name: web port: 9090 targetPort: http Apply # kubectl apply -f . kubectl get svc -o wide -n ns-prometheus 4.官方kubernets运行Prometheus # 参考: https://github.com/coreos/kube-prometheus\n5.Kubernetes部署Prometheus+Grafana # 下载相关k8s资源文件 git clone https://github.com/coreos/kube-prometheus.git 修改文件kube-prometheus/manifests/prometheus-prometheus.yaml,做这一步的目的是为prometheus的访问分配子路径,访问方式为http(s)://xxx/prometheus\n在prometheus.spec下添加\nexternalUrl: prometheus routePrefix: prometheus 修改文件kube-prometheus/manifests/grafana-deployment.yaml,做这一步的目的是为grafana的访问分配子路径,访问方式为:http(s)://xxx/grafana\n在deployment.spec.template.spec.container[0]下添加\nenv: - name: GF_SERVER_ROOT_URL value: \u0026#34;http://localhost:3000/grafana\u0026#34; - name: GF_SERVER_SERVE_FROM_SUB_PATH value: \u0026#34;true\u0026#34; Apply k8s资源 # 可能需要运行多次以下命令,确保k8s资源都创建 kubectl create -f manifests/setup -f manifests # !如果要删除以上创建的k8s资源,运行以下命令 kubectl delete --ignore-not-found=true -f manifests/ -f manifests/setup Ingress转发 apiVersion: extensions/v1beta1 kind: Ingress metadata: name: prometheus namespace: monitoring spec: rules: - host: dp.example.tech http: paths: - path: /prometheus backend: serviceName: prometheus-k8s servicePort: 9090 - path: /grafana backend: serviceName: grafana servicePort: 3000 - path: /alertmanager backend: serviceName: alertmanager-main servicePort: 9093 Configuring Prometheus # ​ 配置文件为prometheus.yml,文件格式为yaml。\nConcepts # Metrics # 存储 # Prometheus 2.x 默认将时间序列数据库保存在本地磁盘中,同时也可以将数据保存到任意第三方的存储服务中。\n本地存储 # Prometheus 采用自定义的存储格式将样本数据保存在本地磁盘当中。\n存储格式 # Prometheus 按照两个小时为一个时间窗口,将两小时内产生的数据存储在一个块(Block)中。每个块都是一个单独的目录,里面含该时间窗口内的所有样本数据(chunks),元数据文件(meta.json)以及索引文件(index)。其中索引文件会将指标名称和标签索引到样板数据的时间序列中。此期间如果通过 API 删除时间序列,删除记录会保存在单独的逻辑文件 tombstone 当中。\n当前样本数据所在的块会被直接保存在内存中,不会持久化到磁盘中。为了确保 Prometheus 发生崩溃或重启时能够恢复数据,Prometheus 启动时会通过预写日志(write-ahead-log(WAL))重新记录,从而恢复数据。预写日志文件保存在 wal 目录中,每个文件大小为 128MB。wal 文件包括还没有被压缩的原始数据,所以比常规的块文件大得多。一般情况下,Prometheus 会保留三个 wal 文件,但如果有些高负载服务器需要保存两个小时以上的原始数据,wal 文件的数量就会大于 3 个。\nPrometheus保存块数据的目录结构如下所示:\nrometheus 提供了几个参数来修改本地存储的配置,最主要的有: 启动参数 默认值 含义 --storage.tsdb.path /data 数据存储路径 --storage.tsdb.retention.time 15d 样本数据在存储中保存的时间。超过该时间限制的数据就会被删除。 --storage.tsdb.retention.size 0 每个块的最大字节数(不包括 wal 文件)。如果超过限制,最早的样本数据会被优先删除。支持的单位有 KB, MB, GB, PB,例如:“512MB”。该参数只是试验性的,可能会在未来的版本中被移除。 --storage.tsdb.retention 该参数从 2.7 版本开始已经被弃用,使用 --storage.tsdb.retention.time 参数替代 在一般情况下,Prometheus 中存储的每一个样本大概占用1-2字节大小。如果需要对 Prometheus Server 的本地磁盘空间做容量规划时,可以通过以下公式计算:./data |- 01BKGV7JBM69T2G1BGBGM6KB12 # 块 |- meta.json # 元数据 |- wal # 写入日志 |- 000002 |- 000001 |- 01BKGTZQ1SYQJTR4PB43C8PD98 # 块 |- meta.json #元数据 |- index # 索引文件 |- chunks # 样本数据 |- 000001 |- tombstones # 逻辑数据 |- 01BKGTZQ1HHWHV8FBJXW1Y3W0K |- meta.json |- wal |-000001 最初两个小时的块最终会在后台被压缩成更长的块。\n[info] 注意:\n本地存储不可复制,无法构建集群,如果本地磁盘或节点出现故障,存储将无法扩展和迁移。因此我们只能把本地存储视为近期数据的短暂滑动窗口。如果你对数据持久化的要求不是很严格,可以使用本地磁盘存储多达数年的数据。\n关于存储格式的详细信息,请参考 TSDB 格式\n本地存储配置 # rometheus 提供了几个参数来修改本地存储的配置,最主要的有:\n启动参数 默认值 含义 \u0026ndash;storage.tsdb.path /data 数据存储路径 \u0026ndash;storage.tsdb.retention.time 15d 样本数据在存储中保存的时间。超过该时间限制的数据就会被删除。 \u0026ndash;storage.tsdb.retention.size 0 每个块的最大字节数(不包括 wal 文件)。如果超过限制,最早的样本数据会被优先删除。支持的单位有 KB, MB, GB, PB,例如:“512MB”。该参数只是试验性的,可能会在未来的版本中被移除。 \u0026ndash;storage.tsdb.retention 该参数从 2.7 版本开始已经被弃用,使用 \u0026ndash;storage.tsdb.retention.time 参数替代 在一般情况下,Prometheus 中存储的每一个样本大概占用1-2字节大小。如果需要对 Prometheus Server 的本地磁盘空间做容量规划时,可以通过以下公式计算:\nneeded_disk_space = retention_time_seconds * ingested_samples_per_second * bytes_per_sample 从上面公式中可以看出在保留时间(retention_time_seconds)和样本大小(bytes_per_sample)不变的情况下,如果想减少本地磁盘的容量需求,只能通过减少每秒获取样本数(ingested_samples_per_second)的方式。因此有两种手段,一是减少时间序列的数量,二是增加采集样本的时间间隔。考虑到 Prometheus 会对时间序列进行压缩效率,减少时间序列的数量效果更明显。\n如果你的本地存储出现故障,最好的办法是停止运行 Prometheus 并删除整个存储目录。因为 Prometheus 的本地存储不支持非 POSIX 兼容的文件系统,一旦发生损坏,将无法恢复。NFS 只有部分兼容 POSIX,大部分实现都不兼容 POSIX。\n除了删除整个目录之外,你也可以尝试删除个别块目录来解决问题。删除每个块目录将会丢失大约两个小时时间窗口的样本数据。所以,Prometheus 的本地存储并不能实现长期的持久化存储。:\n如果同时指定了样本数据在存储中保存的时间和大小,则哪一个参数的限制先触发,就执行哪个参数的策略。\n远程存储 # Prometheus 的本地存储无法持久化数据,无法灵活扩展。为了保持Prometheus的简单性,Prometheus并没有尝试在自身中解决以上问题,而是通过定义两个标准接口(remote_write/remote_read),让用户可以基于这两个接口对接将数据保存到任意第三方的存储服务中,这种方式在 Promthues 中称为 Remote Storage。\nPrometheus 可以通过两种方式来集成远程存储。\nRemote Write # 用户可以在 Prometheus 配置文件中指定 Remote Write(远程写)的 URL 地址,一旦设置了该配置项,Prometheus 将采集到的样本数据通过 HTTP 的形式发送给适配器(Adaptor)。而用户则可以在适配器中对接外部任意的服务。外部服务可以是真正的存储系统,公有云的存储服务,也可以是消息队列等任意形式。\nRemote Read # 如下图所示,Promthues 的 Remote Read(远程读)也通过了一个适配器实现。在远程读的流程当中,当用户发起查询请求后,Promthues 将向 remote_read 中配置的 URL 发起查询请求(matchers,ranges),Adaptor 根据请求条件从第三方存储服务中获取响应的数据。同时将数据转换为 Promthues 的原始样本数据返回给 Prometheus Server。\n当获取到样本数据后,Promthues 在本地使用 PromQL 对样本数据进行二次处理。\n[info] 注意:\n启用远程读设置后,Prometheus 仅从远程存储读取一组时序样本数据(根据标签选择器和时间范围),对于规则文件的处理,以及 Metadata API 的处理都只基于 Prometheus 本地存储完成。这也就意味着远程读在扩展性上有一定的限制,因为所有的样本数据都要首先加载到 Prometheus Server,然后再进行处理。所以 Prometheus 暂时不支持完全分布式处理。\n远程读和远程写协议都使用了基于 HTTP 的 snappy 压缩协议的缓冲区编码,目前还不稳定,在以后的版本中可能会被替换成基于 HTTP/2 的 gRPC 协议,前提是 Prometheus 和远程存储之间的所有通信都支持 HTTP/2。\n配置文件 # 想知道如何在 Prometheus 中添加远程存储的配置,请参考前面的章节: 配置远程写 和 配置远程读。\n关于请求与响应消息的详细信息,可以查看远程存储相关协议的 proto 文件:\nsyntax = \u0026#34;proto3\u0026#34;; package prometheus; option go_package = \u0026#34;prompb\u0026#34;; import \u0026#34;types.proto\u0026#34;; import \u0026#34;gogoproto/gogo.proto\u0026#34;; message WriteRequest { repeated prometheus.TimeSeries timeseries = 1 [(gogoproto.nullable) = false]; } message ReadRequest { repeated Query queries = 1; } message ReadResponse { // In same order as the request\u0026#39;s queries. repeated QueryResult results = 1; } message Query { int64 start_timestamp_ms = 1; int64 end_timestamp_ms = 2; repeated prometheus.LabelMatcher matchers = 3; prometheus.ReadHints hints = 4; } message QueryResult { // Samples within a time series must be ordered by time. repeated prometheus.TimeSeries timeseries = 1; } 支持的远程存储 # 目前 Prometheus 社区也提供了部分对于第三方数据库的 Remote Storage 支持:\n存储服务 支持模式 AppOptics write Chronix write Cortex read/write CrateDB read/write Elasticsearch write Gnocchi write Graphite write InfluxDB read/write IRONdb read/write Kafka write M3DB read/write OpenTSDB write PostgreSQL/TimescaleDB read/write SignalFx write Splunk write TiKV read/write VictoriaMetrics write Wavefront write 联邦集群 # 联邦使得一个 Prometheus 服务器可以从另一个 Prometheus 服务器提取选定的时序。\n使用场景 # Prometheus 联邦有不同的使用场景。通常,联邦被用来实现可扩展的 Prometheus 监控设置,或者将相关的指标从一个服务的 Prometheus 拉取到另一个 Prometheus 中。\n分层联邦 # 分层联邦允许 Prometheus 能够扩展到十几个数据中心和上百万的节点。在此场景下,联邦拓扑类似一个树形拓扑结构,上层的 Prometheus 服务器从大量的下层 Prometheus 服务器中收集和汇聚的时序数据。\n例如,一个联邦设置可能由多个数据中心中的 Prometheus 服务器和一套全局 Prometheus 服务器组成。每个数据中心中部署的 Prometheus 服务器负责收集本区域内细粒度的数据(实例级别),全局 Prometheus 服务器从这些下层 Prometheus 服务器中收集和汇聚数据(任务级别),并存储聚合后的数据。这样就提供了一个聚合的全局视角和详细的本地视角。\n跨服务联邦 # 在跨服务联邦中,一个服务的 Prometheus 服务器被配置来提取来自其他服务的 Prometheus 服务器的指定的数据,以便在一个 Prometheus 服务器中对两个数据集启用告警和查询。\n例如,一个运行多种服务的集群调度器可以暴露在集群上运行的服务实例的资源使用信息(例如内存和 CPU 使用率)。另一方面,运行在集群上的服务只需要暴露指定应用程序级别的服务指标。通常,这两种指标集分别被不同的 Prometheus 服务器抓取。利用联邦,监控服务级别指标的 Prometheus 服务器也可以从集群中 Prometheus 服务器拉取其特定服务的集群资源使用率指标,以便可以在该 Prometheus 服务器中使用这两组指标集。\n配置联邦 # 在 Prometheus 服务器中,/federate 节点允许获取服务中被选中的时间序列集合的值。至少一个 match[] URL 参数必须被指定为要暴露的序列。每个 match[] 变量需要被指定为一个 不变的维度选择器像 up 或者 {job=\u0026quot;api-server\u0026quot;}。如果有多个 match[] 参数,则所有符合的时序数据的集合都会被选择。\n从一个 Prometheus 服务器联邦指标到另一个 Prometheus 服务器,配置你的目标 Prometheus 服务器从源服务器的 /federate 节点抓取指标数据,同时也使用 honor_lables 抓取选项(不重写源 Prometheus 服务暴露的标签)并且传递需要的 match[] 参数。例如,下面的 scrape_configs 联邦 source-prometheus-{1,2,3}:9090 三台 Prometheus 服务器,上层 Prometheus 抓取并汇总他们暴露的任何带 job=\u0026quot;prometheus\u0026quot; 标签的序列或名称以 job: 开头的指标。\nscrape_configs: - job_name: \u0026#39;federate\u0026#39; scrape_interval: 15s honor_labels: true metrics_path: \u0026#39;/federate\u0026#39; params: \u0026#39;match[]\u0026#39;: - \u0026#39;{job=\u0026#34;prometheus\u0026#34;}\u0026#39; - \u0026#39;{__name__=~\u0026#34;job:.*\u0026#34;}\u0026#39; static_configs: - targets: - \u0026#39;source-prometheus-1:9090\u0026#39; - \u0026#39;source-prometheus-2:9090\u0026#39; - \u0026#39;source-prometheus-3:9090\u0026#39; Monitor App # MSSQL # 参考\n监控Mssql应用\n在一台安装了Docker的服务器上运行mssql-exporter\nexport MSSQL_PASS=\u0026#39;xxx\u0026#39; sudo docker run -e SERVER=db-dev-ms01.example.com -e USERNAME=starlightdba -e PASSWORD=$DEV_MSSQL_PASS -e DEBUG=app --rm -d -p 4000:4000 --name dev-mssql awaragi/prometheus-mssql-exporter 注意:Mssql账号需要有管理员权限。\nprometheus.yaml新增监控 的配置\n监控Mssql服务器 solved # « Prometheus-监控Kong完整操作\n» PVC 扩容\n"},{"id":158,"href":"/kubernetes/pvc-expansion/","title":"Pvc Expansion","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / PVC 扩容\nPVC 扩容 # K8s 部署的 Kafka 程序突然挂了,查看相关日志发现原来是挂日志的磁盘空间不足,那么现在需要对磁盘进行扩容。\n使用以下命令执行 PVC 扩容的操作:\nkubectl edit pvc \u0026lt;pvc-name\u0026gt; -n \u0026lt;namespace\u0026gt; 执行过程中发现,无法对该 PVC 进行动态扩容,需要分配 PVC 存储的 StorageClass 支持动态扩容。\n那么怎么是的 StorageClass 支持动态扩容呢,很简单,更新 StorageClass 即可。\nkubectl edit storageclass \u0026lt;storageclass-name\u0026gt; 添加属性:\nallowVolumeExpansion: true # 允许卷扩充 之后再次执行 PVC 扩容的操作即可。\n« Prometheus\n» 了解 Secret\n"},{"id":159,"href":"/kubernetes/secret-understood/","title":"Secret Understood","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 了解 Secret\n了解 Secret # 通常我们的应用程序的配置都会包含一些敏感信息,例如数据库连接字符串,证书,私钥等,为了保证其安全性,K8s 提供了 Secret 资源对象来保存敏感数据,它和 CongfigMap 类似,也是键值对的映射,并且使用方式也几乎一样。\n介绍 Secret # Secret 中存储着键值对数据,可以\n作为环境变量传递给容器 作为文件挂载到容器的 Volume Secret 会存储在 Pod 所调度的节点的内存中,而不是写入磁盘。\nPod 默认生成的 Secret # 每个 Pod 都会被自动挂载一个 Secret 卷,只需要使用 kubectl desribe pod 命令就能看到一个名称类似 default-token-n4q6m 的 Secret,Secret 也是一种 K8s 资源,所以,可以使用 kubectl get secret 或 kubectl describe secret 获取查看。\n从上面图例可以看出,Pod 默认生成的 Secret 会包含三个配置项:ca.crt、namespace、token。其实这三个配置项是 Pod 内部安全访问Kubernetes API 服务的所有信息,而在 kubectl describe pod 的时候,你可以看到 Secret 所挂载的具体目录在 /var/run/secrets/kubernetes.io/serviceaccount.\n每个 Pod 会默认生成 default-token-xxxxx 的 Secret,可以通过在Pod 中定义 pod.spec.automountServiceAccountToken 为 false 来关闭这种默认行为。\n创建 Secret # 可以直接通过 kubectl create secret 命令创建,也可以先编写 secret 的 yaml 文件再使用 kubectl apply -f \u0026lt;filename\u0026gt; 创建,推荐使用后者。\n单行命令创建 Secret # 创建一个键值对的 secret: kubectl create secret generic first-secret --from-literal=user=admin --from-literal=password=admin123 创建完成之后,使用 kubectl describe secret first-secret 查看,可以看到这个 secret 的键值内容并不会直接打印出来,而是只显示了占用了多少个字节。\n创建一个文件内容的 Secret 假如我当前有一个配置文件 secret.json,文件内容如下:\n{ \u0026#34;User\u0026#34;: \u0026#34;admin\u0026#34;, \u0026#34;Password\u0026#34;: \u0026#34;admin123\u0026#34; } 使用以下命令创建 Secret:\nkubectl create secret generic second-secret --from-file=secret.json 创建完成之后,使用 kubectl describe secret second-secret 查看 secret 的键值内容,同样也不会将文件内容显示出来:\n默认使用文件名称 secret.json 作为键值对的 key,也可以通过 --from-file=second_secret=app.json 指定 key 为 second_secret;\n可以使用多组 --from-file=\u0026lt;key\u0026gt;=\u0026lt;filename\u0026gt; 参数,在 secret 中定义多组文件;\n--from-file= 后面可以直接跟某个文件路径,这样会将目录下的所有文件引入到 Secret;\n--from-literal 和 --from-file 可以共同使用,键值合并。\n删除创建的 first-secret 和 second-secret:\nkubectl delete secret first-secret kubectl delete secret second-secret 基于资源清单文件创建 Secret # 创建一个键值对的 Secret: 首先定义 Secret 的资源文件 first-secret.yaml,定义如下:\n先使用 base64 对 secret 资源文件中要保存的键值编码\necho \u0026#34;admin\u0026#34; | base64 # 得到 YWRtaW4K echo \u0026#34;admin123\u0026#34; | base64 # 得到 YWRtaW4xMjMK vim first-secret.yaml apiVersion: v1 kind: Secret metadata: name: first-secret data: user: \u0026#34;YWRtaW4K\u0026#34; password: \u0026#34;YWRtaW4xMjMK\u0026#34; 使用 kubectl apply 命令创建 Secret 资源:\nkubectl apply -f first-secret.yaml 创建完成之后,使用 kubectl describe secret first-secret 查看。\n可以在 data 下定义多组键值对。\n创建一个文件内容的 Secret 首先定义 Secret 的资源文件 second-secret.yaml,定义如下:\n先使用 base64 对上文中的 secret.json 文件内容编码:\necho $(cat secret.json) | base64 # 得到 eyAiVXNlciI6ICJhZG1pbiIsICJQYXNzd29yZCI6ICJhZG1pbjEyMyIgfQo= vim second-secret.yaml apiVersion: v1 kind: Secret metadata: name: second-secret data: secret.json: eyAiVXNlciI6ICJhZG1pbiIsICJQYXNzd29yZCI6ICJhZG1pbjEyMyIgfQo= 使用 kubectl apply 命令创建 Secret 资源:\nkubectl apply -f second-secret.yaml 创建完成之后,使用 kubectl describe secret second-secret 查看。\n可以在 data 下定义多组文件,也可以和键值对一起定义;\n删除创建的 first-secret 和 second-secret:\nkubectl delete secret first-secret kubectl delete secret second-secret 使用 Secret # Secret 的用途也与 ConfigMap 相差无几:\n使用 Secret 作为容器的环境变量 使用 Secret 作为 Volume 向容器提供文件 使用 Secret 作为容器的环境变量 # 假如有一个名为 first-secret 的 Secret,里面包含了一个键为 user,我想将这个 Secret 中 user 键用到我的环境变量 USER_NAME 中,可以使用如下方式:\n... env: - name: USER_NAME valueFrom: secretKeyRef: name: first-secret key: user ... 如果有一个名为 second-secret Secret 中包含多个键如 USER_NAME,PASSWORD,我想将这个 Secret 中所有的键都用到我的环境变量中,可以使用如下方式:\n... spec: container: - image: \u0026lt;some-image\u0026gt; envFrom: - prefix: MYSQL_ secretRef: name: second-secret ... 容器将会生成 DB_USER_NAME,DB_PASSWORD 环境变量,prefix 也可以不配置,则直接使用 Secret 的键。\n注意:\nsecretRef 与上面 secretKeyRef 的区别; 如果Secret中有一个为 USER-NAME 键,那么将不会生成 MYSQL_USER-NAME 的环境变量,因为MYSQL_USER-NAME 不是一个合法的环境变量名称。 使用 Secret 为容器的 Volume 提供文件 # 上次的文章——《 了解 ConfigMap》中,使用 ConfigMap 向容器提供文件,这次使用 Secret 来实际使用一下。\n我们现在有一个文件 secret.json 要传递到容器中,文件内容如下:\n{ \u0026#34;User\u0026#34;: \u0026#34;admin\u0026#34;, \u0026#34;Password\u0026#34;: \u0026#34;admin123\u0026#34; } 创建 Secret\nkubectl create secret generic mockapi-secret --from-file=secret.json 定义 mockapi-pod.yaml 文件如下:\napiVersion: v1 kind: Pod metadata: name: mockapi spec: containers: - name: mockapi image: poneding/mockapi:v1 ports: - containerPort: 80 volumeMounts: - mountPath: /app/mysettings/secret.json name: mockapi-secret subPath: secret.json volumes: - name: mockapi-secret secret: secretName: mockapi-secret 创建 Pod:\nkubectl apply -f mockapi-pod.yaml 一段时间后,可以验证文件是否挂载到容器:\nYeah!没毛病。\n使用 Secret 拉取私有镜像 # 当我们要访问拉取私有仓库或者私有镜像时,我们需要可能需要使用到 Secret。\n比如我现在将我 docker 仓库中的镜像 mockapi 设置成私有镜像,这是我使用该镜像创建 Pod 是会显示镜像拉取失败的,很明显,我需要登录 docker。\n创建镜像仓库 Secret kubectl create secret docker-registry docker-hub-secret --docker-username=\u0026lt;my_username\u0026gt; --docker-password=\u0026lt;my_password\u0026gt; 私有仓库的话使用 --docker-server 指定;\n更多使用 kubectl create secret docker-registry --help 查看\nPod 中使用 imagePullSecrets: ... kind: Pod spec: imagePullSecrets: - name: docker-hub-secret containers: - name: mockapi image: poneding/mockapi:v1 ... 使用 ServiceAccount 如果很多镜像都要从私有仓库拉取,那最好将 secret 添加到一个固定的 ServiceAccount 中,一个 ServiceAccount 可以包含多个镜像仓库 Secret:\napiVersion: v1 kind: ServiceAccount metadata: name: docker-service-account imagePullSecrets: - name: docker-hub-secret - name: harbor-secret 这时 Pod 使用 ServiceAccount 即可:\n... kind: Pod spec: serviceAccountName: docker-service-account ... « PVC 扩容\n» 了解 Service\n"},{"id":160,"href":"/kubernetes/service-understood/","title":"Service Understood","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 了解 Service\n了解 Service # Service 介绍 # 按照官方文档的说法,在 K8s 中,Service 是将运行在集群中的一组 Pod 的应用公开为网络服务的抽象方法,是 K8s 的核心概念之一,Service 的主要作用是使客户端发现 Pod 并与之通信。\n简单理解起来就是,由 Service 提供统一的入口地址,然后将请求负载分发到后端 Pod 的容器应用。\n为什么有 Service # 集群中部署了 Pod,应用是成功的部署起来了,但是只是至此的话,Pod 提供服务访问存在以下一些问题。\nPod 是短暂的,可能会被销毁或重新调度,这使得 Pod 的 IP 是随时变动和更新的; 部署多个 Pod 的伸缩问题,流量分配问题; 集群外部客户端无法直接访问 Pod。 这时候就需要 Service,Pod 作为 Service 的后端提供服务。所以我们可以想象,Service 需要完成的事情:\n服务发现,通过 Pod 的 lable 查找目标 Pod,将查找的 Pod 的注册到自己的后端列表,Pod 的 IP 信息发生更改,后端列表也同步更新; 负载均衡,请求到达 Service 之后,将请求均衡转发的后端列表; 服务暴露:对外提供统一的请求地址。 创建 Service # 在创建 Sercvice 之前我们首先创建 service 代理的 Pod,nginx-pod.yaml:\napiVersion: v1 kind: Pod metadata: name: nginx labels: app: nginx spec: containers: - name: nginx image: nginx ports: - containerPort: 80 先给到一个简单的 Service 定义实例 nginx-service.yaml:\napiVersion: v1 kind: Service metadata: name: nginx spec: ports: - name: http port: 80 protocol: TCP targetPort: 80 selector: app: nginx type: LoadBalancer spec.ports.port 是 Service 对外提供服务的端口,spec.ports.targetPort 是转发到 Pod 内的访问端口;\n通过 spec.selector 发现同一命名空间下带有 app=nginx 的 label 的 Pod 作为后端。\n创建命令:\nkubectl apply -f nginx-service.yaml Service作为Pod的负载均衡器,使用 service.spec.selector 字段去匹配Pod,上面示例中,nginx Service将通过 app: nginx 的 label 查找 Pod 作为后端。\n创建 Service 之后,查看 Service 的后端:\nkubectl get svc -o wide kubectl describe svc nginx 在创建 Sercvice 之前,我已经创建了一个带有 app: nginx label 的 Pod,所以可以看到 Service 的 EndPoints 中已经有了一个后端(10.244.1.25)了,Endpoint 也是一种 K8s 资源,可以使用 kubectl get ep 命令查看。\n除了以上使用 yaml 定义 Service 之外,还可以使用以下命令创建 Service:\n# 暴露 Pod kubectl expose pod nginx --name=nginx --port=80 --target-port=80 --protocol=TCP --type=NodePort # 或者 Deployment 管理的 Pod kubectl expose deploy nginx --name=nginx --port=80 --target-port=80 --protocol=TCP --type=LoadBalancer Service 类型 # 通过 Service.spec.type 定义,最常用的三种:ClusterIP(默认),NodePort、LoadBalancer。\nCLusterIP # 指定 type 为 ClusterIP 时,它将被分配一个集群内部的 IP,在集群内部通过访问它来访问后端 Pod,这种 Service 一般只供集群内部访问。\nNodePort # 指定 type 为 NodePort 时,会在所有节点分配一个端口(默认从 30000-32767 )作为 service 的入口,访问方式:\u0026lt;protocol\u0026gt;://\u0026lt;node-ip\u0026gt;:\u0026lt;port\u0026gt;,使用 NodePort 类型时,也会默认分配一个 ClusterIP,除非使用 ClusterIP: None 免除分配。\n特点::\n所有节点都是同一个端口; 端口是有限的,30000-32767 范围内分配,也可以使用 service.spec.ports.nodePort 指定端口,但指定的端口必须在范围内且尚未被占用; 外部访问需要使用到 Node 的 IP。 LoadBalancer # 随机分配一个 LoadBalancer 作为 Service 的入口,一般需要云提供商的支持。请求到达 LoadBalancer 地址后,均衡负载到后端 Pod,使用 LoadBalancer 类型时,也会默认分配一个 ClusterIP,并且也会分配一个 NodePort 端口,实际上来说,LoadBalancer 是基于 NodePort 实现的。\n特点:\n外部网络可以通过 LoadBalancer 的地址和端口访问到集群内 Service 服务。 ExternalName # 创建一个新的 Service 代理到已经存在另外一个 Service,允许跨命名空间。如下,在命令空间 ns-b 下创建 service 转发访问到 ns-a 下的 service。\nkind: Service apiVersion: v1 metadata: name: nginx namespace: ns-b spec: selector: app: nginx type: ExternalName externalName: nginx.ns-a.svc.cluster.local ports: - name: http port: 80 targetPort: 80 如果只是用来创建一个 Service,使用意义不是很大,因为本来就可以直接通过 nginx.ns-a.svc.cluster.local 跨命名空间访问,但是在结合 Ingress 时有一定的使用意义,因为 ingress 无法跨命名空间转发 Service。\n访问 Service # 通过 ClusterIP 访问,限集群内部访问; 通过 Service Name 访问:如果在同一命名空间,可以直接使用服务名称 \u0026lt;service-name\u0026gt; 访问,如果不在同一命名空间,使用服务名称和命名空间名称 \u0026lt;service-name\u0026gt;.\u0026lt;namespace-name\u0026gt; 或 \u0026lt;service-name\u0026gt;.\u0026lt;namespace-name\u0026gt;.svc.cluster.local 访问,集群外可访问; 通过节点端口(NodePort)访问,集群外可访问; 通过 ExternalIP(一般是 LoadBalancer 的地址)访问,集群外可访问。 « 了解 Secret\n» Telepresence\n"},{"id":161,"href":"/kubernetes/telepresence/","title":"Telepresence","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Telepresence\nTelepresence # Telepresence是一款\n« 了解 Service\n» Kubernetes 0-1 使用preStop优雅终止Pod\n"},{"id":162,"href":"/kubernetes/terminate-pod-gracefully/","title":"Terminate Pod Gracefully","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Kubernetes 0-1 使用preStop优雅终止Pod\nKubernetes 0-1 使用preStop优雅终止Pod # Kubernetes允许Pod终止之前,执行自定义逻辑。\n字段定义 # 字段定义:pod.spec.containers.lifecycle.preStop\n$ kubectl explain pod.spec.containers.lifecycle.preStop KIND: Pod VERSION: v1 RESOURCE: preStop \u0026lt;Object\u0026gt; DESCRIPTION: PreStop is called immediately before a container is terminated due to an API request or management event such as liveness/startup probe failure, preemption, resource contention, etc. The handler is not called if the container crashes or exits. The reason for termination is passed to the handler. The Pod\u0026#39;s termination grace period countdown begins before the PreStop hooked is executed. Regardless of the outcome of the handler, the container will eventually terminate within the Pod\u0026#39;s termination grace period. Other management of the container blocks until the hook completes or until the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks Handler defines a specific action that should be taken FIELDS: exec \u0026lt;Object\u0026gt; One and only one of the following should be specified. Exec specifies the action to take. httpGet \u0026lt;Object\u0026gt; HTTPGet specifies the http request to perform. tcpSocket \u0026lt;Object\u0026gt; TCPSocket specifies an action involving a TCP port. TCP hooks not yet supported 有三种preStop方式:\nexec: httpGet: tcpSocket: 示例 # 使用最简单的exec作示例,详细查看一下exec下需要定义的字段:\n$ kubectl explain pod.spec.containers.lifecycle.preStop.exec KIND: Pod VERSION: v1 RESOURCE: exec \u0026lt;Object\u0026gt; DESCRIPTION: ... FIELDS: command \u0026lt;[]string\u0026gt; Command is the command line to execute inside the container, the working directory for the command is root (\u0026#39;/\u0026#39;) in the container\u0026#39;s filesystem. The command is simply exec\u0026#39;d, it is not run inside a shell, so traditional shell instructions (\u0026#39;|\u0026#39;, etc) won\u0026#39;t work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy. 接下来按照字段释义,直接定义一个Pod:\n$ kubectl apply -f pod.yaml # pod.yaml文件内容: apiVersion: v1 kind: Pod metadata: name: busybox namespace: default spec: containers: - name: busybox image: busybox command: [\u0026#34;/bin/sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;sleep 10m\u0026#34;] lifecycle: preStop: exec: command: [ \u0026#34;/bin/sh\u0026#34;, \u0026#34;-c\u0026#34;, \u0026#34;echo this pod is stopping. \u0026gt; /stop.log \u0026amp;\u0026amp; sleep 10s\u0026#34;, ] 删除pod:\nkubectl delete pod busybox 在新终端窗口(因为删除pod会占用终端窗口)获取pod内文件内容,需要在pod完全删除之前(10s内,也可以将该值设置稍长一点):\n$ kubectl exec busybox -c busybox -- cat /stop.log # 可以得到日志内容 this pod is stopping. 这说明,preStop确实生效了。\n使用场景 # 你的请求已经到达了当前Pod,硬终止会导致请求失败,我们希望已经到达了当前Pod的请求处理完成再将其停止掉,尽可能避免请求失败; Pod已经本身已经注册到了服务中心,我们希望在Pod停止之前,主动向服务注册中心通知下线; 结束语 # 与preStop相对应,有一个postStart的概念,在容器创建成功后执行,可用于初始化资源,准备环境等;\n如果preStop与postStart执行失败,将会杀死容器。所以作为钩子函数,应该尽量保证它们是轻量的;\nPod的终止过程:\n删除Pod =\u0026gt; Pod被标记为Terminating状态 =\u0026gt; Service移除该Pod的endpoint =\u0026gt; kubelet甄别Terminating状态的pod,执行pod的preStop钩子 =\u0026gt; 如果执行preStop超时(grace period) ,kubelet发送SIGTERM并等待2秒 =\u0026gt; \u0026hellip;\n« Telepresence\n» Terraform\n"},{"id":163,"href":"/kubernetes/terraform/","title":"Terraform","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Terraform\nTerraform # 创建ec2同时安装应用的三种方式 # Mode 1: userdata # 需要shell脚本文件install_nginx.sh resource \u0026#34;aws_instance\u0026#34; \u0026#34;demo\u0026#34; { # ... # Mode 1: userdata user_data = \u0026#34;${file(\u0026#34;../templates/install_nginx.sh\u0026#34;)}\u0026#34; # ... } Mode 2: remote-exec # 需要连接主机,connection; 密钥文件xxx.pem resource \u0026#34;aws_instance\u0026#34; \u0026#34;demo\u0026#34; { # ... # Mode 2: remote-exec connection { host = \u0026#34;${self.private_ip}\u0026#34; private_key = \u0026#34;${file(\u0026#34;xxx.pem\u0026#34;)}\u0026#34; user = \u0026#34;${var.ansible_user}\u0026#34; } provisioner \u0026#34;remote-exec\u0026#34; { inline = [ \u0026#34;sudo apt-get update\u0026#34;, \u0026#34;sudo apt-get install -y nginx\u0026#34;, \u0026#34;sudo service nginx start\u0026#34; ] } # ... } Mode 3: local-exec with Ansible # 需要执行Terraform命令的主机安装Ansible 密钥文件xxx.pem 额外的ansible-playbook文件,目录../playbooks/install_nginx.yaml 实战过程中发现,使用Ansible在主机远程为ec2安装nginx时,需要等待一定时间(sleep 30)才会成功,猜测可能是等待ec2完全创建成功并运行(安装python,毕竟Ansible对host的唯一要求就是python)之后才可以使用Ansible,这可能会成为一个坑。: resource \u0026#34;aws_instance\u0026#34; \u0026#34;demo\u0026#34; { # ... # Mode 3: local-exec with ansible-playbook provisioner \u0026#34;local-exec\u0026#34; { command = \u0026lt;\u0026lt;EOT sleep 30; \u0026gt;nginx.ini; echo \u0026#34;[nginx]\u0026#34; | tee -a nginx.ini; chmod 600 xxx.pem; echo \u0026#34;${self.private_ip} ansible_user=${var.ansible_user} ansible_ssh_private_key_file=xxx.pem\u0026#34; | tee -a nginx.ini; export ANSIBLE_HOST_KEY_CHECKING=False; ansible-playbook -u ${var.ansible_user} --private-key xxx.pem -i nginx.ini ../playbooks/install_nginx.yaml EOT } # ... } « Kubernetes 0-1 使用preStop优雅终止Pod\n» Velero + Minio 备份与恢复\n"},{"id":164,"href":"/kubernetes/velero-minio-backup-restore-volume/","title":"Velero Minio Backup Restore Volume","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / Velero + Minio 备份与恢复\nVelero + Minio 备份与恢复 # 安装 Minio # docker run -d --name minio \\\\ -p 9000:9000 \\\\ -p 9001:9001 \\\\ -e MINIO_ROOT_USER=minio \\\\ -e MINIO_ROOT_PASSWORD=minio \\\\ -v /minio-data:/data \\\\ quay.io/minio/minio:latest server /data --console-address \u0026#34;:9001\u0026#34; 创建 Bucket # 设置 Region # 点击保存后,会出现一个横幅,点击横幅上的 Restart 即可。\n创建 AccessKey # 保存 AccessKey 和 SecretKey 到文件 credentials-velero:\n[default] aws_access_key_id = \u0026lt;access_key\u0026gt; aws_secret_access_key = \u0026lt;secret_key\u0026gt; 安装 Velero CLI # # linux wget \u0026lt;https://github.com/vmware-tanzu/velero/releases/download/v1.11.1/velero-v1.11.1-linux-amd64.tar.gz\u0026gt; tar -xvf velero-v1.11.1-linux-amd64.tar.gz mv velero-v1.11.1-linux-amd64/velero /usr/local/bin # completion bash source /usr/share/bash-completion/bash_completion echo \u0026#39;source \u0026lt;(velero completion bash)\u0026#39; \u0026gt;\u0026gt;~/.bashrc velero completion bash \u0026gt;/etc/bash_completion.d/velero echo \u0026#39;alias v=velero\u0026#39; \u0026gt;\u0026gt;~/.bashrc echo \u0026#39;complete -F __start_velero v\u0026#39; \u0026gt;\u0026gt;~/.bashrc # completion zsh source \u0026lt;(velero completion zsh) echo \u0026#39;alias v=velero\u0026#39; \u0026gt;\u0026gt;~/.zshrc echo \u0026#39;complete -F __start_velero v\u0026#39; \u0026gt;\u0026gt;~/.zshrc 在集群中安装 Velero # velero install \\ --provider aws \\ --plugins velero/velero-plugin-for-aws:main \\ --use-node-agent=true \\ --use-volume-snapshots=false \\ --bucket \u0026lt;your_minio_bucket\u0026gt; \\ --secret-file ./credentials-velero \\ --backup-location-config \\ region=\u0026lt;your_minio_region\u0026gt;,s3ForcePathStyle=\u0026#34;true\u0026#34;,s3Url=https://\u0026lt;your_minio_server\u0026gt;:9000 1、./credentials-velero 文件中保存 minio 的 AccessKey 和 SecretKey 内容;\n2、修改 bucket、region、和 minio 的服务地址。\n备份 # velero backup create mysql-backup --selector app=mysql --default-volumes-to-fs-backup 还原 # velero restore create --from-backup mysql-backup « Terraform\n» 了解 Volume\n"},{"id":165,"href":"/kubernetes/volume-understood/","title":"Volume Understood","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / 了解 Volume\n了解 Volume # 我们知道容器与容器之间是隔离的,有独立的文件系统。并且存储的文件性质时临时的,当容器被销毁时,容器内的文件一并被清除。\n在 Pod 内可能运行着多个容器,这可能需要容器共享文件。在 K8s 中,抽象除了 Volume 的概念来满足这种需求。\nVolume 介绍 # 在 Docker 中,也有 Volume 的概念,它是将容器内某文件目录挂载到宿主机的目录。\n在 K8s 中,Volume 供 Pod 内的容器使用,一个容器可以使用多个 Volume,同一 Pod 内的多个容器可以同时使用一个 Volume,实现文件共享,或数据持久存储。\n容器与 Volume 的简单关系:\nVolume 定义 # 定义在 pod.spec.container 属性下:\nkind: Pod ... spec: container: ... volumeMounts: - mountPath: \u0026lt;path\u0026gt; name: \u0026lt;volume-name\u0026gt; subPath: \u0026lt;volume-path\u0026gt; volumes: - name: \u0026lt;volume-name\u0026gt; \u0026lt;volume-type\u0026gt;: ... mountPath: 容器内的目录,如果不存在则创建该目录 subPath:默认会将 mountPath 直接映射到 volume 的根目录,使用 subpath 映射到 volume 特定的目录。 Volume 类型 # Volume 有多种类型,有的可以直接在集群中使用,有的则需要第三方服务或云平台的支持。简单罗列几种常见类型,更多了类型参考: https://kubernetes.io/zh/docs/concepts/storage/volumes\nemptyDir # 如果指定 Volume 类型为 emptyDir,它会在 Pod 刚被调度时,在被调度到的节点上创建起来。卷初始时时空的,里面没有文件,Pod 内的容器可以在卷中写入文件。多个容器绑定的 Volume 如果存在目录相同,则目录可以被共享读写。\n如果 Pod 在该节点被销毁,那么 emptyDir 也将被永久删除。Pod 内的容器崩溃或重启是不会影响 emptyDir 的生命周期的。\n由于 emptyDir 的生命周期受Pod影响,emptyDir 适合的使用场景也会受限,emptyDir 适合使用在数据临时计算,临时缓存等,不适合存储配置信息或持久化数据。\n使用示例:\napiVersion: v1 kind: Pod metadata: name: nginx-redis spec: containers: - image: nginx name: nginx volumeMounts: - name: dir mountPath: /shareDir - image: redis name: redis volumeMounts: - name: dir mountPath: /shareDir volumes: - name: dir emptyDir: {} 如果在 K8s 中创建上面这个 Pod,容器都运行起来后,分别进入 nginx 容器和 redis 容器,都可以在根目录下看到 shareDir 目录,并且在一个容器中写入了文件,另一个容器也可以读取到。当删除这个 Pod,再重新创建后,之前写入的文件会消失。\nemptyDir 可以指定存储介质,默认是使用节点的磁盘创建起来的,也可以指定介质为内存,这种读写更快:\nvolumes: - name: dir emptyDir: medium: Memory hostPath # hostPath 卷指向节点文件系统上的特定文件或目录,如果多个 Pod 运行在同一节点,并且指向相同的卷路径,则他们看的文件是相同的。\nhostPath 类型的 Volume 的生命周期不受Pod限制,Pod 被删除,hostPath 下的文件仍然保留。由于 hostPath 数据只会保留在节点上,当 Pod 被重新调度到其他节点时,相对来说数据是丢失的。一般可以使用 hostPath 作为 DaemonSet(每个匹配节点都调度一个 Pod)管理的 Pod 的 Volume。\n使用示例:\napiVersion: v1 kind: Pod metadata: name: redis spec: containers: - image: redis name: redis volumeMounts: - name: dir mountPath: /data volumes: - name: dir hostPath: path: /vol/redis/data type: DirectoryOrCreate 当我们创建以上 Pod 资源,容器运行起来之后,则会在 Pod 所调度的宿主机上创建 /vol/redis/data 目录。如果我们删除Pod,宿主机上的 /vol/redis/data 目录不会被删除。\nhostPath 的 type 类型,默认为空,直接使用 path,其他 type 使用时会检查和初始化 path:\nDirectoryOrCreate,如果 hostPath 定义的 path 目录不存在,则创建目录 Directory,hostPath 定义的 path 目录已经存在,否则会异常 FileOrCreate,如果 hostPath 定义 的path 文件不存在,则创建文件,如果前缀目录不存在也会报错,需要先使用 DirectoryOrCreate 创建目录 File,hostPath 定义的 path 文件已经存在,否则会异常 persistentVolumeClaim # awsElasticBlockStore # 使用 aws 的存储卷作为 Pod 的 Volume,它可以被多个 Pod 共同使用,并且它的生命周期不受 Pod 限制,当 Pod 被销毁,只是取消了绑定关系,存储不会被删除。\n使用 awsElasticBlockStore 作为 Volume,需要提前在 aws 中创 volume,获取到 volumeID,然后 Pod 的 Volume 指定awsElasticBlockStore 的 volumeID 即可。\n使用 aws-cli 创建 aws 中的 volume:\naws ec2 create-volume --availability-zone=ap-southeast-1a --size=2 --volume-type=gp2 以上命令完成会输出 volumeID。\n使用 awsElasticBlockStore 作为 Pod 的 Volume 简单示例,前提我们已经获取到 了volumeID 为 vol-07afc8d24f8f08d2a:\napiVersion: v1 kind: Pod metadata: name: redis labels: name: redis spec: containers: - name: redis image: redis volumeMounts: - mountPath: /data name: redis-data volumes: - name: redis-data awsElasticBlockStore: volumeID: vol-07afc8d24f8f08d2a 当我们创建以上 Pod 资源,容器运行起来之后,我们进入 redis 容器,使用 redis-cli往redis 中写入一个key-value后,立即退出容器,删除 Pod 并重新创建后,再次进入容器,是依然可以获取 key-value 的,这说明 Volume 数据没有随 Pod 被删除。\nconfigMap # 存储配置项信息,供 Volume 使用,后续可能会单独介绍它的使用。\nSecret # 存储敏感信息的配置,供 Volume 使用,后续可能会单独介绍它的使用。\n« Velero + Minio 备份与恢复\n» VPA\n"},{"id":166,"href":"/kubernetes/vpa/","title":"Vpa","section":"Kubernetes","content":" 🏠 首页 / Kubernetes / VPA\nVPA # 安装 # git clone https://github.com/kubernetes/autoscaler.git cd ./autoscaler/vertical-pod-autoscaler ./hack/vpa-up.sh 卸载:\n./hack/vpa-down.sh VPA运行模式 # Auto(默认模式):VPA在pod创建时分配资源请求,并使用首选的更新机制在现有的pod上更新它们。目前,这相当于“重建”(见下文)。一旦pod请求的免费重启(“原位”)更新可用,它就可以被“Auto”模式用作首选的更新机制。注意:VPA的这个特性是实验性的,可能会导致您的应用程序停机。\nRecreate:VPA在pod创建时分配资源请求,并在现有pod上更新它们,当请求的资源与新建议有显著差异时(如果定义了pod中断预算,则考虑到它们),将它们赶出现有pod。这种模式应该很少使用,只有当您需要确保在资源请求更改时重新启动pods时才会使用。否则更喜欢“自动”模式,这可能会利用重新启动免费更新,一旦他们可用。注意:VPA的这个特性是实验性的,可能会导致您的应用程序停机。\nInitial:VPA只在pod创建时分配资源请求,以后不会更改它们。\nOff:VPA不会自动更改pods的资源需求。计算并可以在VPA对象中检查建议。\n示例 # « 了解 Volume\n"},{"id":167,"href":"/linux/","title":"Linux","section":"","content":" 🏠 首页 / Linux\nLinux # certbot-auto 生成证书\nLinux-history 输出附带日期\nLinux 命令\nLinux常用命令\nLinux 启用 crontab 日志\nLinux-安全登录\nshell 命令间隔符\nshell 基础\n使用 SSH Tunnel 连接中间件\ntee 保存 stderr 到文件\nvim 使用\n"},{"id":168,"href":"/linux/certbot-auto-gen-cert/","title":"Certbot Auto Gen Cert","section":"Linux","content":" 🏠 首页 / Linux / certbot-auto 生成证书\ncertbot-auto 生成证书 # 安装 # wget https://dl.eff.org/certbot-auto chmod a+x ./certbot-auto cp ./certbot-auto /usr/local/bin 生成证书 # 条件:\n提前已经将域名解析到本服务器; 本服务器端口 80、443 处于未被占用的状态,如果 web 服务占用了 80 端口,需要临时关闭。 certbot-auto certonly --standalone --email poneding@gmail.com -d test.poneding.com 以上命令执行完成后,将会在 /etc/letsencrypt/live 目录下生成域名证书文件。默认证书有效期为 3 个月。\nnginx 配置证书 # 参考示例:\nserver { listen 80; server_name abc.com; rewrite ^(.*) https://test.poneding.com permanent; } server{ listen 443 ssl default_server; listen [::]:443 ssl default_server; ssl_certificate /etc/letsencrypt/live/test.poneding.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/test.poneding.com/privkey.pem; server_name test.poneding.com; root /web/test.poneding.com/; } 证书续期 # 配置定时任务\n0 3 1 * * certbot-auto renew --pre-hook \u0026#34;systemctl stop nginx\u0026#34; --renew-hook \u0026#34;systemctl start nginx\u0026#34; 参考 # certbot 文档: https://eff-certbot.readthedocs.io/en/stable/index.html » Linux-history 输出附带日期\n"},{"id":169,"href":"/linux/history-with-date/","title":"History With Date","section":"Linux","content":" 🏠 首页 / Linux / Linux-history 输出附带日期\nLinux-history 输出附带日期 # 如果我们在 linux 系统中想看历史的命令记录,我们可以通过 command 命令来获取。\nhistory 输出大概会是下面这种样子,只有简单的 command 列表。\n1 ls 2 top 4 docker ps 5 df 6 ls 那么,如果想知道历史执行的 command 的时间该怎么做呢。\n按照如下步骤,一步一步来。\n首先设置 HISTTIMEFORMAT 变量 $ HISTTIMEFORMAT=\u0026#34;%d/%m/%y %T \u0026#34; # OR $ echo \u0026#39;export HISTTIMEFORMAT=\u0026#34;%d/%m/%y %T \u0026#34;\u0026#39; \u0026gt;\u0026gt; ~/.bash_profile 使用 source 命令加载 HISTTIMEFORMAT 变量到当前 shell 命令窗 $ . ~/.bash_profile # OR $ source ~/.bash_profile 再次运行 history 命令,已经可以输出附带执行时间的 history 了。 1 root 2020/02/18 11:28:19 ls 2 root 2020/02/18 11:28:21 top 4 root 2020/02/18 11:28:58 docker ps 5 root 2020/02/18 11:34:09 df 6 root 2020/02/18 11:34:15 ls « certbot-auto 生成证书\n» Linux 命令\n"},{"id":170,"href":"/linux/linux-commands/","title":"Linux Commands","section":"Linux","content":" 🏠 首页 / Linux / Linux 命令\nLinux 命令 # Linux 命令大全\ncat # cat命令用于把档案串连接后传到基本输出(萤幕或加 \u0026gt; fileName 到另一个档案)\n使用权限 # 所有使用者\n语法格式 # cat [-AbeEnstTuv] [--help] [--version] fileName 参数说明 # -n 或 \u0026ndash;number 由 1 开始对所有输出的行数编号\n-b 或 \u0026ndash;number-nonblank 和 -n 相似,只不过对于空白行不编号\n-s 或 \u0026ndash;squeeze-blank 当遇到有连续两行以上的空白行,就代换为一行的空白行\n-v 或 \u0026ndash;show-nonprinting\n实例 # 把 textfile1 的档案内容加上行号后输入 textfile2 这个档案里\ncat -n textfile1 \u0026gt; textfile2 把 textfile1 和 textfile2 的档案内容加上行号(空白行不加)之后将内容附加到 textfile3 里。\ncat -b textfile1 textfile2 \u0026gt;\u0026gt; textfile3 清空/etc/test.txt档案内容\ncat /dev/null \u0026gt; /etc/test.txt cat 也可以用来制作镜像文件。例如要制作软碟的像文件,将软碟放好后打\ncat /dev/fd0 \u0026gt; OUTFILE 相反的,如果想把 image file 写到软碟,请打\ncat IMG_FILE \u0026gt; /dev/fd0 写入多行到文件:\ncat \u0026lt;\u0026lt; EOF \u0026gt; hello.txt Hello World EOF 注:\n\\1. OUTFILE 指输出的镜像文件名。 \\2. IMG_FILE 指镜像文件。 \\3. 若从镜像文件写回 device 时,device 容量需与相当。 \\4. 通常用在制作开机磁片。 chattr # Linux chattr命令用于改变文件属性。\n这项指令可改变存放在ext2文件系统上的文件或目录属性,这些属性共有以下8种模式:\na:让文件或目录仅供附加用途。 b:不更新文件或目录的最后存取时间。 c:将文件或目录压缩后存放。 d:将文件或目录排除在倾倒操作之外。 i:不得任意更动文件或目录。 s:保密性删除文件或目录。 S:即时更新文件或目录。 u:预防以外删除。 语法 # chattr [-RV][-v\u0026lt;版本编号\u0026gt;][+/-/=\u0026lt;属性\u0026gt;][文件或目录...] 参数 # -R 递归处理,将指定目录下的所有文件及子目录一并处理。\n-v\u0026lt;版本编号\u0026gt; 设置文件或目录版本。\n-V 显示指令执行过程。\n+\u0026lt;属性\u0026gt; 开启文件或目录的该项属性。\n-\u0026lt;属性\u0026gt; 关闭文件或目录的该项属性。\n=\u0026lt;属性\u0026gt; 指定文件或目录的该项属性。\n实例 # 用chattr命令防止系统中某个关键文件被修改:\nchattr +i /etc/resolv.conf lsattr /etc/resolv.conf 会显示如下属性\n----i-------- /etc/resolv.conf 让某个文件只能往里面追加数据,但不能删除,适用于各种日志文件:\nchattr +a /var/log/messages chgrp # Linux chgrp命令用于变更文件或目录的所属群组。\n在UNIX系统家族里,文件或目录权限的掌控以拥有者及所属群组来管理。您可以使用chgrp指令去变更文件与目录的所属群组,设置方式采用群组名称或群组识别码皆可。\n语法 # chgrp [-cfhRv][--help][--version][所属群组][文件或目录...] 或 chgrp [-cfhRv][--help][--reference=\u0026lt;参考文件或目录\u0026gt;][--version][文件或目录...] 参数说明 # -c或\u0026ndash;changes 效果类似\u0026quot;-v\u0026quot;参数,但仅回报更改的部分。\n-f或\u0026ndash;quiet或\u0026ndash;silent 不显示错误信息。\n-h或\u0026ndash;no-dereference 只对符号连接的文件作修改,而不更动其他任何相关文件。\n-R或\u0026ndash;recursive 递归处理,将指定目录下的所有文件及子目录一并处理。\n-v或\u0026ndash;verbose 显示指令执行过程。\n\u0026ndash;help 在线帮助。\n\u0026ndash;reference=\u0026lt;参考文件或目录\u0026gt; 把指定文件或目录的所属群组全部设成和参考文件或目录的所属群组相同。\n\u0026ndash;version 显示版本信息。\n实例 # 实例1:改变文件的群组属性:\nchgrp -v bin log2012.log 输出:\n[root@localhost test]# ll ---xrw-r-- 1 root root 302108 11-13 06:03 log2012.log [root@localhost test]# chgrp -v bin log2012.log \u0026ldquo;log2012.log\u0026rdquo; 的所属组已更改为 bin\n[root@localhost test]# ll ---xrw-r-- 1 root bin 302108 11-13 06:03 log2012.log 说明: 将log2012.log文件由root群组改为bin群组\n实例2:根据指定文件改变文件的群组属性\nchgrp --reference=log2012.log log2013.log 输出:\n[root@localhost test]# ll ---xrw-r-- 1 root bin 302108 11-13 06:03 log2012.log -rw-r--r-- 1 root root 61 11-13 06:03 log2013.log [root@localhost test]# chgrp --reference=log2012.log log2013.log [root@localhost test]# ll ---xrw-r-- 1 root bin 302108 11-13 06:03 log2012.log -rw-r--r-- 1 root bin 61 11-13 06:03 log2013.log 说明: 改变文件log2013.log 的群组属性,使得文件log2013.log的群组属性和参考文件log2012.log的群组属性相同\nchmod # Linux/Unix 的文件调用权限分为三级 : 文件拥有者、群组、其他。利用 chmod 可以藉以控制文件如何被他人所调用。\n使用权限 : 所有使用者\n语法 # chmod [-cfvR] [--help] [--version] mode file... 参数说明 # mode : 权限设定字串,格式如下 :\n[ugoa...][[+-=][rwxX]...][,...] 其中:\nu 表示该文件的拥有者,g 表示与该文件的拥有者属于同一个群体(group)者,o 表示其他以外的人,a 表示这三者皆是。 + 表示增加权限、- 表示取消权限、= 表示唯一设定权限。 r 表示可读取,w 表示可写入,x 表示可执行,X 表示只有当该文件是个子目录或者该文件已经被设定过为可执行。 -c : 若该文件权限确实已经更改,才显示其更改动作\n-f : 若该文件权限无法被更改也不要显示错误讯息\n-v : 显示权限变更的详细资料\n-R : 对目前目录下的所有文件与子目录进行相同的权限变更(即以递回的方式逐个变更)\n\u0026ndash;help : 显示辅助说明\n\u0026ndash;version : 显示版本\n实例 # 将文件 file1.txt 设为所有人皆可读取 :\nchmod ugo+r file1.txt 将文件 file1.txt 设为所有人皆可读取 :\nchmod a+r file1.txt 将文件 file1.txt 与 file2.txt 设为该文件拥有者,与其所属同一个群体者可写入,但其他以外的人则不可写入 :\nchmod ug+w,o-w file1.txt file2.txt 将 ex1.py 设定为只有该文件拥有者可以执行 :\nchmod u+x ex1.py 将目前目录下的所有文件与子目录皆设为任何人可读取 :\nchmod -R a+r * 此外chmod也可以用数字来表示权限如 :\nchmod 777 file 语法为:\nchmod abc file 其中a,b,c各为一个数字,分别表示User、Group、及Other的权限。\nr=4,w=2,x=1\n若要rwx属性则4+2+1=7; 若要rw-属性则4+2=6; 若要r-x属性则4+1=5。 chmod a=rwx file 和\nchmod 777 file 效果相同\nchmod ug=rwx,o=x file 和\nchmod 771 file 效果相同\n若用chmod 4755 filename可使此程序具有root的权限\nfile # Linux file命令用于辨识文件类型。\n通过file指令,我们得以辨识该文件的类型。\n语法 # file [-beLvz][-f \u0026lt;名称文件\u0026gt;][-m \u0026lt;魔法数字文件\u0026gt;...][文件或目录...] 参数:\n-b 列出辨识结果时,不显示文件名称。 -c 详细显示指令执行过程,便于排错或分析程序执行的情形。 -f\u0026lt;名称文件\u0026gt; 指定名称文件,其内容有一个或多个文件名称呢感,让file依序辨识这些文件,格式为每列一个文件名称。 -L 直接显示符号连接所指向的文件的类别。 -m\u0026lt;魔法数字文件\u0026gt; 指定魔法数字文件。 -v 显示版本信息。 -z 尝试去解读压缩文件的内容。 [文件或目录\u0026hellip;] 要确定类型的文件列表,多个文件之间使用空格分开,可以使用shell通配符匹配多个文件。 实例 # 显示文件类型:\n[root@localhost ~]# file install.log install.log: UTF-8 Unicode text [root@localhost ~]# file -b install.log \u0026lt;== 不显示文件名称 UTF-8 Unicode text [root@localhost ~]# file -i install.log \u0026lt;== 显示MIME类别。 install.log: text/plain; charset=utf-8 [root@localhost ~]# file -b -i install.log text/plain; charset=utf-8 显示符号链接的文件类型\n[root@localhost ~]# ls -l /var/mail lrwxrwxrwx 1 root root 10 08-13 00:11 /var/mail -\u0026gt; spool/mail [root@localhost ~]# file /var/mail /var/mail: symbolic link to `spool/mail\u0026#39; [root@localhost ~]# file -L /var/mail /var/mail: directory [root@localhost ~]# file /var/spool/mail /var/spool/mail: directory [root@localhost ~]# file -L /var/spool/mail /var/spool/mail: directory find # Linux find 命令用来在指定目录下查找文件。任何位于参数之前的字符串都将被视为欲查找的目录名。如果使用该命令时,不设置任何参数,则 find 命令将在当前目录下查找子目录与文件。并且将查找到的子目录和文件全部进行显示。\n语法 # find path -option [ -print ] [ -exec -ok command ] {} ; 参数说明 :\nfind 根据下列规则判断 path 和 expression,在命令列上第一个 - ( ) , ! 之前的部份为 path,之后的是 expression。如果 path 是空字串则使用目前路径,如果 expression 是空字串则使用 -print 为预设 expression。\nexpression 中可使用的选项有二三十个之多,在此只介绍最常用的部份。\n-mount, -xdev : 只检查和指定目录在同一个文件系统下的文件,避免列出其它文件系统中的文件\n-amin n : 在过去 n 分钟内被读取过\n-anewer file : 比文件 file 更晚被读取过的文件\n-atime n : 在过去 n 天过读取过的文件\n-cmin n : 在过去 n 分钟内被修改过\n-cnewer file :比文件 file 更新的文件\n-ctime n : 在过去 n 天过修改过的文件\n-empty : 空的文件-gid n or -group name : gid 是 n 或是 group 名称是 name\n-ipath p, -path p : 路径名称符合 p 的文件,ipath 会忽略大小写\n-name name, -iname name : 文件名称符合 name 的文件。iname 会忽略大小写\n-size n : 文件大小 是 n 单位,b 代表 512 位元组的区块,c 表示字元数,k 表示 kilo bytes,w 是二个位元组。-type c : 文件类型是 c 的文件。\nd: 目录\nc: 字型装置文件\nb: 区块装置文件\np: 具名贮列\nf: 一般文件\nl: 符号连结\ns: socket\n-pid n : process id 是 n 的文件\n你可以使用 ( ) 将运算式分隔,并使用下列运算。\nexp1 -and exp2\n! expr\n-not expr\nexp1 -or exp2\nexp1, exp2\n实例 # 列出给定目录(base_path)下所有的文件和子目录:\nfind base_path -print 补充:根据文件名和正则表达式进行搜索,使用选项 -name 或 -iname (忽略大小写):\nfind base_path -name ‘xxx’ -print find base_path -iname ’xxx‘ -print 否定参数,可以用 !排除所指定到的模式:\n此处将打印出除 txt 文本文件外的的所有文件。\n基于目录深度的搜索:\nfind 命令指定遍历完所有的子目录。使用 -maxdepth 和-mindefth 可以限制 find 命令遍历的目录深度,并且 find 命令默认不搜索符号链接,可以用 -L 选项改变这种行为。\n例如-maxdepth的参数为1时,只匹配当前目录下。\n-mindepth 的参数代表了开始进行匹配的目录到 base_path 的最短距离。\n基于文件类型搜索:\n使用 -type 可以指定搜索的文件类型,linux/unix 将所有的的一切都视为文件(文件类型有:普通文件 f,目录 d,符号链接 l,字符设备 c,块设备 b,套接字 s,FIFO-p),使用 -type 选项我们能够对文件类型进行过滤。\n此处就会只匹配出特定项下的所有普通文件,和目录。\n根据文件的时间戳进行搜索:\nLinux/Unix 文件系统中的每一个文件都有三种时间戳,访问时间(-atime),修改时间(-mtime),变化时间(-ctime),单位为天数,用整数指定,数字前加上+,表示大于这个时间;加上-,表示小于这个天数;不加表示刚好这个天数。\n此处的文件是我在进行截图之前才创建的,访问,修改,变化时间均小于一天。\n当然相应的用分钟作为单位就可以用选项 (-amin)(-mmin)(-cmin),如下我们测试修改时间\n基于文件大小的搜索:\nfind 提供了指定文件大小的单位选项进而搜索符合大小文件的功能,这个搜索也常常会让用户感到非常舒服(b:块, c:字节, w:字, k:千字节, M:兆字节, G:吉字节)。\n在搜索之前我们先用 ls(list)指令来查看下当前目录下的文件信息:\n信息的第五列就是各文件目录的大小(字节),我们通过指定匹配条件来搜索:\n经过测试,在开始目录下,文件类型为普通目录,文件大小大于30个字节的文件就是zl.txt了\n基于文件权限和所有权的匹配:\n-perm 选项指定了 find 指匹配指定权限的文件,参数为文件对应的权限码。\n我们仍然可参考⑥中的所有文件信息的第一列,此处需要掌握一定关于文件权限的知识。如下我们查找权限为 644 的普通文件,即用户可读写,组用户可读,其他可读。\n也可以用选项 -user,匹配指定用户所拥有的文件,参数为用户名或者UID\n利用find执行相应操作:\n比如删除文件,使用 -delete 选项;删除测试目录下所有的 .txt 普通文件\n还可以利用 -exec 选项结合其他命令对文件进行更高效的操作,更改文件的所属权,复制文件等,find 命令使用一对花括号 {} 代表文件名,对于每一个匹配到的文件,find 命令会将{}替换成相应的文件名; 如果 -exec 的命令有多个参数时,需要注意结尾使用 \u0026quot; ; \u0026quot; 或者 \u0026ldquo;+\u0026quot;,前者表示进行转义,不然系统会以为是 find 命令的结尾。\n我们将测试目录下的所有的 .txt 文件由用户 lihongbo 转换到用户 litao999,我们必须以 root 用户进行此操作,chown 用于更改权限:\n指定 find 跳过特定的目录:\n使用 -prune 选项可以跳过我们在搜寻的的一些明显我们不需要的目录\n跳过了 ./test1 目录\n需要指出的是:选项出现的先后次序我们也应该考虑到内,因为它会影响到整条命令的执行效率。\necho # curl # 返回httpStatusCode\ncurl -I -m 10 -o /dev/null -s -w %{http_code} http://localhost/version -I 仅测试HTTP头 -m 10 最多查询10s -o /dev/null 屏蔽原有输出信息 -s silent 模式,不输出任何东西 -w %{http_code} 控制额外输出 date # 查看今天是当年中的第几天\ndate \u0026#39;+%j\u0026#39; 按照“年-月-日 小时:分钟:秒”的格式查看当前系统时间\ndate \u0026#39;+%Y-%m-%d %H:%M:%S\u0026#39; 将系统的当前时间设置为2017年9月1日8点30分\ndate -s \u0026#39;20170901 8:30:00\u0026#39; source 命令 # source命令将指定函数文件加载到某个shell脚本文件或当前命令窗口。\n语法 # 基本使用语法如下:\nsource \u0026lt;filename\u0026gt; [arguments] source functions.sh source /path/to/functions.sh arg1 arg2 source functions.sh WWWROOT=/apache.jail PHPROOT=/fastcgi.php_jail 示例 # 创建一个shell脚本文件mylib.sh,如下:\n#!/bin/bash JAIL_ROOT=/www/httpd is_root(){ [$(id -u) -eq 0] \u0026amp;\u0026amp; return $TRUE || return $FALSE } function to_lower() { local str=\u0026#34;$@\u0026#34; local output output=$(tr \u0026#39;[A-Z]\u0026#39; \u0026#39;[a-z]\u0026#39;\u0026lt;\u0026lt;\u0026lt;\u0026#34;${str}\u0026#34;) echo $output } 再创建一个shell脚本文件test.sh,在该文件中,可以通过使用如下的语法使用mylib.sh的JAIL_ROOT变量,调用is_root函数:\n#!/bin/bash # 使用source命令加载mylib.sh source mylib.sh echo \u0026#34;JAIL_ROOT is set to $JAIL_ROOT\u0026#34; # 调用is_root函数,并打印结果 is_root \u0026amp;\u0026amp; echo \u0026#34;You are logged in as root.\u0026#34; || echo \u0026#34;You are not logged in as root.\u0026#34; 运行该文件:\nchmod +x test.sh ./test.sh 运行结果如下:\nJAIL_ROOT is set to /www/httpd You are not logged in as root. 在当前命令直接运行\nsource mylib.sh echo $JAIL_ROOT 运行结果:\n/www/httpd « Linux-history 输出附带日期\n» Linux常用命令\n"},{"id":171,"href":"/linux/linux-common-commands/","title":"Linux Common Commands","section":"Linux","content":" 🏠 首页 / Linux / Linux常用命令\nLinux常用命令 # 1.文件和目录相关 # cd .. 切换到上一级目录 cd ../.. 切换到上两级目录 cd [dir] 进入 dir 目录 cd 进入个人的主目录 cd ~[username] 进入个人的主目录 cd - 进入上次目录 pwd 查看目录路径 ls 查看当前目录下的目录和文件(不包含隐藏目录或文件) ls -a 查看当前目录下的所有目录和文件 ls -F 查看当前目录下的文件 (不包含目录和隐藏文件) ls -l 查看文件和目录的详细资料 ls *[0-9]* 查看包含字符的文件和目录 mkdir dir1 创建目录 mkdir dir1 dir2 mkdir -p dir1/dir1/dir1 创建一个目录树 rm -f file1 删除文件 rmdir dir1 删除空 rm -rf dir1 删除包含内容的目录 rm -rf dir1 dir2 删除多个目录 mv dir1 dir2 重命名或移动一个目录(看 dir2 是否存在) cp file1 file2 复制文件 cp dir/* . 复制某目录下所有内容到当前目录 cp -a dir1/dir2 . 复制目录下所有内容到当前目录 cp -a dir1 dir2 复制一个目录 ln -s dir1|file1 link1 创建文件或目录 link 的软件链接 ln dir1|file1 link1 创建文件或目录 link 的物理链接,也叫硬链接 [ 软链接和物理链接的区别: 1. 软链接可以用于目录和文件,物理链接只能用于文件; 2. 源文件删除后,物理链接仍然可以打开访问(更像复制),软链接则已经损坏 ] find /home/[user] -name 某目录下搜索文件 find ~ -iname \u0026#39;*test*\u0026#39; # iname 忽略大小写 zip file1.zip file1 file2 dir1 将文件和目录压缩成 zip 文件(dir 不包含内容) zip -r file1.zip file1 file2 dir1 将文件和目录压缩成 zip 文件(dir 包含内容) unzip file1.zip 解压 zip 文件 tar -zxvf ***.tar.gz cat file1 查看一个文件 tac file1 从最后一行开始反向查看一个文件的内容 more file1 查看一个长文件的内容 less file1 类似于 \u0026#39;more\u0026#39; 命令,但是它允许在文件中和正向操作一样的反向操作 head -2 file1 查看一个文件的前两行 tail -2 file1 查看一个文件的最后两行 grep hello file1 查询关键字 ‘hello’ dump -0aj -f /tmp/home0.bak /home 制作一个 \u0026#39;/home\u0026#39; 目录的完整备份 dump -1aj -f /tmp/home0.bak /home 制作一个 \u0026#39;/home\u0026#39; 目录的交互式备份 vi file1 //编辑文件 # lsof (https://www.cnblogs.com/sparkbj/p/7161669.html) lsof # 列出所有打开的文件 lsof /path/file # 查看谁在使用某个文件 losf -u username # 查看某用户打开的文件 2.系统 # date 显示当前时间 shutdown -h now 关闭系统 reboot 重启 logout 注销 groupadd group_name 创建一个新用户组 groupdel group_name 删除一个用户组 groupmod -n new_group_name old_group_name 重命名一个用户组 useradd -c \u0026#34;Name Surname \u0026#34; -g admin -d /home/user1 -s /bin/bash user1 创建一个属于 \u0026#34;admin\u0026#34; 用户组的用户 usermod -a -G sudo dp 将用户添加到sudo组 useradd user1 创建一个新用户 userdel -r user1 删除一个用户 ( \u0026#39;-r\u0026#39; 排除主目录) usermod -c \u0026#34;User FTP\u0026#34; -g system -d /ftp/user1 -s /bin/nologin user1 修改用户属性 passwd 修改密码 passwd user1 修改一个用户的口令 (只允许root执行) # 以下两个命令可用于查看user属于哪个组 id user1 groups user1 # screen screen -dmS dp screen -ls screen -rd dp # 查看linux系统 cat /proc/version uname -a lsb_release -a (首选) 3.技巧 # # 上行命令的最后一个参数 !$ $ mv /path/to/wrongfile /some/other/place mv: cannot stat \u0026#39;/path/to/wrongfile\u0026#39;: No such file or directory $ mv /path/to/rightfile !$ # 上行命令的第 n 个参数,0,1,2,..,n !:n $ tar -cvf afolder afolder.tar # 写反参数 tar: failed to open $ !:0 !:1 !:3 !:2 tar -cvf afolder.tar afolder # 上行命令参数范围 $ grep \u0026#39;(ping|pong)\u0026#39; afile # 写错 egrep # Linux 下 grep 显示前后几行信息 # 标准 unix/linux 下的 grep 通过下面參数控制上下文 grep -C 5 foo file 显示 file 文件里匹配 foo 字串那行以及上下 5 行 grep -B 5 foo file 显示 foo 及前 5 行 grep -A 5 foo file 显示 foo 及后 5 行 $ egrep !:1-$ egrep \u0026#39;(ping|pong)\u0026#39; afile ping # 前一个命令结果过滤 $ ps -ef | grep nginx # 查看端口 lsof -i:[port] netstat -tunlp |grep [port] # 远程 sudo ssh -i xxx.pem ubuntu@192.168.1.1 # 远程直接进入某目录 sudo ssh -i xxx.pem ubuntu@192.168.1.1 -t \u0026#39;/some/path; bash --login\u0026#39; # linux 系统间文件复制 sudo scp -i ~/dp/k8s.dp.io/.ssh/id_rsa /home/ubuntu/hello.txt admin@bastion-k8s-dp-com-qcojf8-1198362181.ap-southeast-1.elb.amazonaws.com:~/ # shell 文件出错即退出 #!/usr/bin/env bash set -e -o pipefail # 查看系统是 32 位还是 64 位 uname -a # 退出 telnet ctrl+],然后输入 quit # base64 编码\u0026amp;反编码 echo -n \u0026#34;JayChou\u0026#34; | base64 echo -n SmF5Q2hvdQ== | base64 --decode apt # apt upgrade apt update [-y] apt install [pkg] [-y] apt remove/autoremove [pkg] [-y] apt purge CentOS # sudo dhclient sudo yum update -y su root vi /etc/sudoers # root ALL=(ALL) ALL 后添加 xxx ALL=(ALL) ALL # 或者 xxx ALL=(ALL) NOPASSWD: ALL (不用密码) # wq!退出 ip addr cd /etc/sysconfig/network-scripts/ ls sudo vi ifcfg-ens33 # ONBOOT=no 改成yes sudo ifup ens33 # 查看文件大小 ls -ll ls -lh # 查看目录大小 du -sh * du -sh /path/* 虚拟机镜像文件:\nCentOS7\n下载地址: http://mirrors.aliyun.com/centos/7/isos/x86_64/CentOS-7-x86_64-DVD-2003.iso\nKitematic # Download: https://github.com/docker/kitematic/releases\ninstall on ubutnu:\nsudo dpkg -i Kitematic-0.17.11_amd64.deb sudo groupadd docker sudo usermod -aG docker $USER reboot ubutnu, then using Kitematic.\nsed替换文本 # 比如 hello.txt 文本中内容如下:\nhello, Name! 现使用真实 Name 替换掉 #Name#。\nexport Name=JayChou sed -i \u0026#34;s/Name/$Name/g\u0026#34; hello.txt 但是如果替换的文本中包含 / 的话,那么上面这个命令会失败:sed: -e expression #1, char 12: unknown option to\n例如\nexport Name=Jay/Chou sed -i \u0026#34;s/Name/$Name/g\u0026#34; hello.txt 这里为了保留被替换文本的 /,有两种方法\n在被替换文本中使用转义符号:\nexport Name=Jay\\/Chou sed -i \u0026#34;s/Name/$Name/g\u0026#34; hello.txt 使用 # 代替 / 作为 sed 中替换与被替换字符的分割符:\nexport Name=Jay/Chou sed -i \u0026#34;s#Name#$Name#g\u0026#34; hello.txt 修改命令行文件路径显示 # 文件路径太长,命令行很难看,只显示当前目录:\nvim ~/.bashrc # 将下行的w改成W PS1=\u0026#39;${debian_chroot:+($debian_chroot)}\\u@\\h:\\W\\$\u0026#39; 网络命名空间 # sudo ip netns add newnetns sudo ip netns exec newnetns bash # 进入命名空间后 使用exit退出 sudo ip netns exec newnetns bash -rcfile \u0026lt;(echo \u0026#34;PS1=\\\u0026#34;greenland\u0026gt; \\\u0026#34;\u0026#34;) sudo ip netns delete newnetns 清理工作 # 一、删除缓存\n1,非常有用的清理命令:\nsudo apt-get autoclean 清理旧版本的软件缓存 sudo apt-get clean 清理所有软件缓存 sudo apt-get autoremove 删除系统不再使用的孤立软件 这三个命令主要清理升级缓存以及无用包的。\n2,清理 opera firefox 的缓存文件:\nls ~/.opera/cache4 ls ~/.mozilla/firefox/*.default/Cache 3,清理 Linux 下孤立的包: 终端命令下我们可以用:\nsudo apt-get install deborphan -y 4,卸载:tracker 这个东西一般我只要安装 ubuntu 就会第一删掉 tracker 他不仅会产生大量的 cache 文件而且还会影响开机速度。再软件中心删除。\n附录: 包管理的临时文件目录: 包在 /var/cache/apt/archives 没有下载完的在 /var/cache/apt/archives/partial\n二、删除软件\nubuntu 软件的删除一般用“ubuntu 软件中心”或“新立得”就能搞定,但有时用命令似乎更快更好~~\nsudo apt-get remove --purge 软件名 sudo apt-get autoremove 删除系统不再使用的孤立软件 sudo apt-get autoclean 清理旧版本的软件缓存 dpkg -l |grep ^rc|awk \u0026#39;{print $2}\u0026#39; |sudo xargs dpkg -P 清除残余的配置文件 保证干净。\n三、删除多余内核\n1,首先要使用这个命令查看当前 Ubuntu 系统使用的内核\nuname -a 2,再查看所有内核\ndpkg --get-selections|grep linux 3,最后小心翼翼地删除吧\nsudo apt-get remove linux-image-2.6.32-22-generic ps:linux-image-xxxxxx-generic 就是要删除的内核版本 还有 linux-headers-xxxxxx linux-headers-xxxxxx-generic 总之中间有“xxxxxx”那段的旧内核都能删,注意一般选内核号较小的删除。\n« Linux 命令\n» Linux 启用 crontab 日志\n"},{"id":172,"href":"/linux/linux-enable-crontab-log/","title":"Linux Enable Crontab Log","section":"Linux","content":" 🏠 首页 / Linux / Linux 启用 crontab 日志\nLinux 启用 crontab 日志 # You can enable logging for cron jobs in order to track problems.\nYou need to edit the /etc/rsyslog.conf or /etc/rsyslog.d/50-default.conf (on Ubuntu) file and make sure you have the following line uncommented or add it if it is missing:\ncron.* /var/log/cron.log Then restart rsyslog and cron:\nsudo service rsyslog restart sudo service cron restart Cron jobs will log to /var/log/cron.log .\n« Linux常用命令\n» Linux-安全登录\n"},{"id":173,"href":"/linux/linux-secure-login/","title":"Linux Secure Login","section":"Linux","content":" 🏠 首页 / Linux / Linux-安全登录\nLinux-安全登录 # 我们都知道\nroot 是 linux 系统默认的最高权限账号 linux 系统默认的 ssh 端口是 22 大多数人习惯使用 user/password 来登录 linux 系统 很遗憾,如果你的系统没有做特殊等登陆配置,那么其他人便可以利用 ssh ip:22 root/暴力密码 来破解登入你 的 linux 系统,一旦被他破解,你的系统就可以为他所用了。\n但是,我们可以通过以下三种方式来避免发生这类安全问题。\n1. 禁用 root 账号登录 # 禁用 root 账号,那么我们就必须创建其他登录账号,这里建议账号名不要为 admin 这类常见用户名。\n创建用户(以下都基于 ubuntu 系统操作) adduser dp 用户赋权 此时创建的用户不能使用 sudo 权限,考虑将用户加入 sudo 组\nusermod -a -G sudo dp 并且,为了避免使用 sudo 权限需要时不时的输入密码的麻烦,进行免密设置。在 /etc/sudoers 文件中新增行。\ndp ALL=(ALL) NOPASSWD: ALL 到了这一步,应该尝试使用新用户登录系统,如果成功登录再往下继续。 禁用 root 登录 打开 /etc/ssh/sshd_config 文件,找到 PermitRootLogin 项,修改该项成如下:\nPermitRootLogin no 保存配置后,重启 ssh 服务:\nsystemctl restart ssh 至此,root 账号已经被禁用远程登录了。\n2. 更改 ssh 远程端口 # 弃用 22 端口,使用 0~65535 范围内随机端口作为 ssh 端口。\n要做的是修改 /etc/ssh/sshd_conf 文件,找到 Port 项,修改该项成如下:\nPort 39855 保存配置后,重启 ssh 服务:\nservice ssh restart 至此,服务器 ssh 远程端口已经修改为 39855,不能再使用 22 端口登陆系统了。\n3. 使用密钥文件登录 # 本地创建密钥文件:\nssh-keygen -t rsa -C poneding@gmail.com -f ~/.ssh/id_rsa_poneding 以上命令会默认在 ~/.ssh 目录下生成 id_rsa_poneding 私钥文件和 id_rsa_poneding.pub 文件,将 id_rsa_poneding.pub 文件内容追加到服务器 ~/.ssh/authorized_keys 文件中即可。\n« Linux 启用 crontab 日志\n» shell 命令间隔符\n"},{"id":174,"href":"/linux/shell-command-interval-character/","title":"Shell Command Interval Character","section":"Linux","content":" 🏠 首页 / Linux / shell 命令间隔符\nshell 命令间隔符 # 我们经常能看到Shell命令间有很多中间隔符:|,||,\u0026amp;\u0026amp; 等,它们到底有着什么样的作用呢?一一来看:\n1. |:\n间隔符 | 起着管道的作用,是将上一条命令的 stdout 作为下一条命令的 stdin:\n示例:\necho hello world! | tee hello.txt 2. ||:\n命令被 || 分割,只有当前面的命令发生错误,才会执行后面的命令。\n示例:\n# 如果创业失败,那么就继续打工 sh chuangye.sh || sh dagong.sh 3. \u0026amp;\u0026amp;:\n命令被 \u0026amp;\u0026amp; 分割,命令会连续执行,但是如果前面的命令发生错误,会影响后面的命令继续执行。\n示例:\n# 洗了手才能吃饭 sh wash_hand.sh \u0026amp;\u0026amp; sh eat.sh 4. ;:\n命令被 ; 分割,命令会连续执行,即使前面的命令发生错误,也不影响后面的命令继续执行。\n示例:\n# 不管有没有赚到钱,都要回家过年 sh earn_money.sh; sh go_home.sh 5. \u0026gt;:\n输出到指定文件(文件不存在则创建文件,文件存在则会覆盖文件内容)\necho \u0026#34;Hello World\u0026#34; \u0026gt; hello.txt 6. \u0026raquo;:\n追加到指定文件(文件不存在则创建文件,文件存在则追加文件内容)\necho \u0026#34;Hello World\u0026#34; \u0026gt;\u0026gt; hello.txt « Linux-安全登录\n» shell 基础\n"},{"id":175,"href":"/linux/shell/","title":"Shell","section":"Linux","content":" 🏠 首页 / Linux / shell 基础\nshell 基础 # shell 注释 # 单行注释:\n# 注释内容 多行注释:\n:\u0026lt;\u0026lt;EOF 注释内容 注释内容 注释内容 EOF 或\n:\u0026lt;\u0026lt;! 注释内容 注释内容 注释内容 ! 或\n:\u0026lt;\u0026lt;\u0026#39; 注释内容 注释内容 注释内容 \u0026#39; shell 变量 # 定义变量:\nmy_name=\u0026#34;Ding Peng\u0026#34; 使用变量:\n$my_name ${my_name} 只读变量:\nmy_name=\u0026#34;Ding Peng\u0026#34; readonly my_name 删除变量:\nunset my_name 变量类型:\n局部变量:在脚本或命令中定义,仅在当前shell实例有效 环境变量:所有shell实例有效 shell变量:shell程序设置的特殊变量 shell 字符串 # 单引号与双引号的区别:\n单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的; 单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用; 双引号里可以有变量; 双引号里可以出现转义字符 my_name=\u0026#34;Ding Peng\u0026#34; hello_string=\u0026#34;Hllo,\\\u0026#34;$my_name\\\u0026#34;!\u0026#34; echo $hello_string 获取字符串长度:\nmy_name=\u0026#34;dp\u0026#34; echo ${#my_name} # 输出5 截取字符串:\nhello_world=\u0026#34;Hello World!\u0026#34; # 从index 1截取4个字符 echo ${hello_world:1:4} # 输出ello shell 数组 # 只有以为数组,没有多维数组\n# 定义数组,空格隔开 names=(\u0026#39;Ding Peng\u0026#39; \u0026#39;Jay Chou\u0026#39; \u0026#34;Lebron James\u0026#34;) # 获取数组元素,根据index获取 echo ${names[1]} # 获取数组长度 echo ${#names[@]} echo ${#names[*]} shell 传参 # 执行shell脚本文件时,传递参数\n假如有一个test.sh文件内容如下:\n#!/bin/bash echo \u0026#34;Shell 传递参数实例!\u0026#34;; # 使用$n接收参数 echo \u0026#34;执行的文件名:$0\u0026#34;; echo \u0026#34;第一个参数为:$1\u0026#34;; echo \u0026#34;第二个参数为:$2\u0026#34;; chmod +x test.sh ./test.sh 1 2 第一行命令给test.sh添加可执行权限\n第二行命令执行test.sh文件\n以上命令将输出:\nShell 传递参数实例! 执行的文件名:./test.sh 第一个参数为:1 第二个参数为:2 其他参数处理:\n参数 说明 $# 传递到脚本的参数个数 $* 以一个单字符串显示所有向脚本传递的参数 $$ 脚本运行的当前进程ID号 $@ 与$*相同,但是使用时加引号,并在引号中返回每个参数。 shell if 控制语句 # if 后面需要接者 then:\nif [ condition-for-test ] then command ... fi 或者,\nif [ condition-for-test ]; then command ... fi 如:\n#!/bin/bash VAR=myvar if [ $VAR = myvar ]; then echo \u0026#34;1: \\$VAR is $VAR\u0026#34; # 1: $VAR is myvar fi if [ \u0026#34;$VAR\u0026#34; = myvar ]; then echo \u0026#34;2: \\$VAR is $VAR\u0026#34; # 2: $VAR is myvar fi if [ $VAR = \u0026#34;myvar\u0026#34; ]; then echo \u0026#34;3: \\$VAR is $VAR\u0026#34; # 3: $VAR is myvar fi if [ \u0026#34;$VAR\u0026#34; = \u0026#34;myvar\u0026#34; ]; then echo \u0026#34;4: \\$VAR is $VAR\u0026#34; # 4: $VAR is myvar fi 上面,我们在比较时,可以用双引号把变量引用起来。\n但要注意单引号的使用。\n#!/bin/bash VAR=myvar if [ \u0026#39;$VAR\u0026#39; = \u0026#39;myvar\u0026#39; ]; then echo \u0026#39;5a: $VAR is $VAR\u0026#39; else echo \u0026#34;5b: Not equal.\u0026#34; fibas # Output: # 5b: Not equal. 上面这个就把 '$VAR' 当一个字符串了。\n但如果变量是多个单词,我们就必须用到双引号了,如\n#!/bin/bash # 这样写就有问题 VAR1=\u0026#34;my var\u0026#34; if [ $VAR1 = \u0026#34;my var\u0026#34; ]; then echo \u0026#34;\\$VAR1 is $VAR1\u0026#34; fi # Output # error [: too many arguments # 用双引号 if [ \u0026#34;$VAR1\u0026#34; = \u0026#34;my var\u0026#34; ]; then echo \u0026#34;\\$VAR1 is $VAR1\u0026#34; fi 总的来说,双引号可以一直加上。\n空格问题 # 比较表达式中,如果 = 前后没有空格,那么整个表法式会被认为是一个单词,其判断结果为 True.\n#!/bin/bash VAR2=2 # 由于被识别成一个单词, [] 里面为 true if [ \u0026#34;$VAR2\u0026#34;=1 ]; then echo \u0026#34;$VAR2 is 1.\u0026#34; else echo \u0026#34;$VAR2 is not 1.\u0026#34; fi # Output # 2 is 1. # 前后加上空格就好了 if [ \u0026#34;$VAR2\u0026#34; = 1 ]; then echo \u0026#34;$VAR2 is 1.\u0026#34; else echo \u0026#34;$VAR2 is not 1.\u0026#34; fi # Output # 2 is not 1. 另外需要注意的是, 在判断中,中括号 [ 和变量之间一定要有一个空格,= 或者 ==。 如果缺少了空格,你可能会到这类似这样的错误:unary operator expected’ or missing]` 。\n# 正确, 符号前后有空格 if [ $VAR2 = 1 ]; then echo \u0026#34;\\$VAR2 is 1.\u0026#34; else echo \u0026#34;It\u0026#39;s not 1.\u0026#34; fi # Output # 2 is 1. # 错误, 符号前后无空格 if [$VAR2=1]; then echo \u0026#34;$VAR2 is 1.\u0026#34; else echo \u0026#34;It\u0026#39;s not 1.\u0026#34; fi # Output # line 3: =1: command not found # line 5: [=1]: command not found # It\u0026#39;s not 1. 文件测试表达式 # 对文件进行相关测试,判断的表达式如下:\n表达式 True file1 -nt file2 file1 比 file2 新。 file1 -ot file2 file1 比 file2 老。 -d file 文件file存在,且是一个文件夹。 -e file 文件 file 存在。 -f file 文件file存在,且为普通文件。 -L file 文件file存在,且为符号连接。 -O file 文件 flle 存在, 且由有效用户 ID 拥有。 -r file 文件 flle 存在, 且是一个可读文件。 -s file 文件 flle 存在, 且文件大小大于 0。 -w file 文件 flle 可写入。 -x file 文件 flle 可写执行。 可以使用 man test 查看详细的说明。\n当表达式为 True 时,测试命令返回退出状态 0,而表达式为 False 时返回退出状态1。\n#!/bin/bash FILE=\u0026#34;/etc/resolv.conf\u0026#34; if [ -e \u0026#34;$FILE\u0026#34; ]; then if [ -f \u0026#34;$FILE\u0026#34; ]; then echo \u0026#34;$FILE is a file.\u0026#34; fi if [ -d \u0026#34;$FILE\u0026#34; ]; then echo \u0026#34;$FILE is a directory.\u0026#34; fi if [ -r \u0026#34;$FILE\u0026#34; ]; then echo \u0026#34;$FILE is readable.\u0026#34; fi fi 字符串比较表达式 # 表达式 True string1 = string2 或 string1 == string2 两字符相等 string1 != string2 两个字符串不相等 string1 \u0026gt; string2 string1 大于 string2. string1 \u0026lt; string2 string1 小于string2. -n string 字符串长度大于0 -z string 字符串长度等于0 #!/bin/bash STRING=\u0026#34;\u0026#34; if [ -z \u0026#34;$STRING\u0026#34; ]; then echo \u0026#34;There is no string.\u0026#34; \u0026gt;\u0026amp;2 exit 1 fi # Output # There is no string. 其中 \u0026gt;\u0026amp;2 将错误信息定位到标准错误输出。\n数字比较表达式 # 下面这些是用来比较数字的一些表达式。\n[…] ((…)) True [ “int1” -eq “int2” ] (( “int1” == “int2” )) 相等. [ “int1” -nq “int2” ] (( “int1” != “int2” )) 不等. [ “int1” -lt “int2” ] (( “int1” \u0026lt; “int2” )) int2 大于 int1. [ “int1” -le “int2” ] (( “int1” \u0026lt;= “int2” )) int2 大于等于 int1. [ “int1” -gt “int2” ] (( “int1 \u0026gt; “int2” )) int1 大于 int2 [ “int1” -ge “int2” ] (( “int1 \u0026gt;= “int2” )) int1 大于等于 int2 shell 运算符 # 算数运算符 # val1=`expr 2 + 2` val2=`expr 2 - 2` val3=`expr 2 \\* 2` # *前面必须加\\ val4=`expr 2 / 2` val5=`expr 2 % 2` echo \u0026#34;2 + 2: $val1\u0026#34; echo \u0026#34;2 - 2: $val2\u0026#34; echo \u0026#34;2 * 2: $val3\u0026#34; echo \u0026#34;2 / 2: $val4\u0026#34; echo \u0026#34;2 % 2: $val5\u0026#34; 注意:\n使用反引号而不是单引号; 关键字expr; 运算符如:+-*/% 字符前后都需要空格,否则被认为是字符串 其他算数运算符:\n运算符 说明 示例 = 赋值 a=$b == 判等 [ $a == $b ] 返回0 != 不等 [ $a != $b ] 返回1 关系运算符 # 运算符 说明 示例 -eq 等于 [ $a -eq $b ] -ne 不等于 -gt 大于 -lt 小于 -ge 大于等于 -le 小于等于 注意:\n关系运算符只适用于数字,不支持字符串,除非字符串的值是数字 布尔运算符 # 运算符 说明 示例 ! 非运算 [ $a != $b ] -o 或运算 [ $a -eq $b -o $c -eq $d ] -a 与运算 [ $a -eq $b -a $c -eq $d ] 逻辑运算符 # 运算符 说明 示例 \u0026amp;\u0026amp; 与运算 [[ $a -eq $b \u0026amp;\u0026amp; $c -eq $d ]] || 或运算 `[[ $a -eq $b 说明:\n需要两层[] 字符串运算符 # 运算符 说明 示例 = 判等 [ $a = $b ] != 不等 [ $a != $b ] -z 字符长度是否为0 [ -z $a ] -n 字符串长度不为0 [ -n $b ] $ 字符串不为空,empty或whitespace [ $a ] 文件测试运算符 # 运算符 说明 示例 -b file 是否为块设备文件 [ -b $filepath ] -c file 是否为字符设备文件 -d file 是否为目录 -f file 是否为普通文件(非设备文件) -g file 是否设置SGID位 -k file 是否设置了粘着位(sticky bit) -p file 是否有名管道 -u file 是否设置SUID位 -r file 是否可读 -w file 是否可写 -x file 是否可执行 -s file 是否不为空文件(文件大小为0) -e file 文件(目录)是否存在 -S file 文件是否socket -L file 文件是否存在并且是一个符号链接 shell 命令 # echo # 打印字符串:\n可以是带双引号,单引号,不带引号 可以转义 可以用参数 echo \u0026#34;Hello World\u0026#34; echo \u0026#39;Hello World\u0026#39; echo Hello World name=\u0026#34;Ding Peng\u0026#34; echo \u0026#34;Hello, $name!\u0026#34; # -e 开启转义 echo -e \u0026#34;Hi Ding,\\n\u0026#34; # 两行之间空一行 echo \u0026#34;Nice to meet you.\u0026#34; echo -e \u0026#34;Hi Jay,\\c\u0026#34; # 不会换行,都在一行内输出 echo \u0026#34;Nice to meet you.\u0026#34; echo -e \u0026#34;Hi James,\\r\u0026#34; # 会换行,但是没有\\r也会默认换行的。 echo -e \u0026#34;Nice to meet you.\u0026#34; 打印到某文件:\necho \u0026#34;Hello World\u0026#34; \u0026gt; temp.txt 变量不转义,原样输出:\necho \u0026#39;$name\u0026#39; echo \u0026#39;\\*\u0026#39; 说明:\n需要用单引号 显示命令执行结果:\necho `date` 说明:\n使用反引号,不是单引号 read # 交互,获取控制台输入并赋值给某变量\nread name echo $name printf # 输出命令,移植性优于echo。默认不换行,可以在字符串后添加\\n\nprintf \u0026#34;Hello\\n\u0026#34; 通过以下脚本学习printf命令的一些格式化功能\nprintf \u0026#34;%-10s %-8s %-4s\\n\u0026#34; 姓名 性别 体重kg printf \u0026#34;%-10s %-8s %-4.2f\\n\u0026#34; 郭靖 男 66.1234 printf \u0026#34;%-10s %-8s %-4.2f\\n\u0026#34; 杨过 男 48.6543 printf \u0026#34;%-10s %-8s %-4.2f\\n\u0026#34; 郭芙 女 47.9876 %s %c %d %f都是格式替代符 %-10s 指一个宽度为10个字符(-表示左对齐,没有则表示右对齐),任何字符都会被显示在10个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。 %-4.2f 指格式化为小数,其中.2指保留2位小数。 shell 变量引用 # 当你要使用变量的时候,用 $ 来引用, 如果后面要接一些其他字符,可以用 {} 括起来。\n#!/bin/bash WORLD=\u0026#34;world world\u0026#34; echo \u0026#34;hello $WORLD\u0026#34; # hello world world echo \u0026#34;hello ${WORLD}2\u0026#34; # hello world world2 在 Bash 中要注意 单引号 ' ,双引号 \u0026quot; ,反引号 ` 的区别。\n单引号,双引号都能用来保留引号内的为文字值,其差别在于,双引号在遇到 $(参数替换) ,反引号 `(命令替换) 的时候有例外,单引号则剥夺其中所有字符的特殊含义。\n而反引号的作用 和 $() 是差不多的。 在执行一条命令的时候,会先执行其中的命令,再把结果放到原命令中。\n#!/bin/bash var=\u0026#34;music\u0026#34; sports=\u0026#39;sports\u0026#39; echo \u0026#34;I like $var\u0026#34; # I like music echo \u0026#34;I like ${var}\u0026#34; # I like music echo I like $var # I like music echo \u0026#39;I like $var\u0026#39; # I like $var echo \u0026#34;I like \\$var\u0026#34; # I like $var echo \u0026#39;I like \\$var\u0026#39; # I like \\$var echo `bash -version` # GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu)... echo \u0026#39;bash -version\u0026#39; # bash -version shell 经典实例 # 删除确认 # #!/bin/bash delete_sure delete_sure(){ cat \u0026lt;\u0026lt; eof $(echo -e \u0026#34;\\033[1;36mNote:\\033[0m\u0026#34;) Delete the KubeSphere cluster, including the module kubesphere-system kubesphere-devops-system kubesphere-monitoring-system kubesphere-logging-system openpitrix-system. eof read -p \u0026#34;Please reconfirm that you want to delete the KubeSphere cluster. (yes/no) \u0026#34; ans while [[ \u0026#34;x\u0026#34;$ans != \u0026#34;xyes\u0026#34; \u0026amp;\u0026amp; \u0026#34;x\u0026#34;$ans != \u0026#34;xno\u0026#34; ]]; do read -p \u0026#34;Please reconfirm that you want to delete the KubeSphere cluster. (yes/no) \u0026#34; ans done if [[ \u0026#34;x\u0026#34;$ans == \u0026#34;xno\u0026#34; ]]; then exit fi } « shell 命令间隔符\n» 使用 SSH Tunnel 连接中间件\n"},{"id":176,"href":"/linux/ssh-tunnel-connect-middleware/","title":"SSH Tunnel Connect Middleware","section":"Linux","content":" 🏠 首页 / Linux / 使用 SSH Tunnel 连接中间件\n使用 SSH Tunnel 连接中间件 # 背景 # 一般线上的数据库是不允许本机直接访问的,只能通过跳板机访问。但是这么多的开发人员都要访问数据库的话,跳板机的数量就有压力了。\n本篇介绍如何使用 SSH Tunnel 的方式访问数据库,数据库不限于 Sql Server、MySql、Mongodb、Redis 等。\n前提条件 # 已经拥有数据库的登录信息,如数据库访问的 host、port、user、password; 拥有一台可以访问数据库的跳板机登录权限,如跳板机的 IP、user、password(或密钥文件); 本机安装了有 SSH Tunnel 功能的数据库的可视化工具,如 DBeaver,Navicate,Robo 3T 等。 RDB # 使用 DBeaver 或 Navicate 等工具通过 SSH Tunnel 方式访问关系型数据库,以 Sql Server 为例。\n打开DBeaver,选择 Sql Server 连接。\n在连接配置页面 Main,输入 Sql Server 连接的基本信息,这里 host 直接使用原本的数据库 host 即可。\n切至 SSH,勾选 Use SSH Tunnel,输入跳板机的连接配置即可。\n配置完成,Ok连接即可。\n使用 SSMS + SSH Tunnel 连接 Sql Server # 本机需要安装 SSMS 和 Putty 工具。\n这篇文档对我帮助很大: https://courses.cs.washington.edu/courses/cse444/11wi/resources/tunneling-instructions.html\n打开 Putty 工具,在 Session 创建跳板机的连接。\n并且在 Connection 中配置登录账号和密码(或密钥文件)。\n然后在 Connection=\u0026gt;SSH=\u0026gt;Tunnels 中添加 Sql Server 的 server 信息\n在 Source Port 中输入 \u0026lt;port\u0026gt;(Sql Server的默认端口是1433);\n在 Destination 中输入 \u0026lt;host\u0026gt;:\u0026lt;port\u0026gt;(例如:db.example.com:1433);\n输入完之后点击Add按钮。\n完成操作之后你的页面也应是下面这个样子。\n回到 Connection=\u0026gt;SSH,勾选 Don’t start as shell or command at all.\n上面的配置完成后,点击 Open,应该会跳入如下的远程界面。\n**注意:**如果本机已经安装了 Sql Server 数据库,需要现在 Service 中停掉本机的 SqlServer 服务,否则可能会造成端口冲突。\n这时,你可以使用 SSMS 连接数据库服务了。\n**注意:**SSMS 连接配置页面,Server name 必须是 127.0.0.1 而不是原本的数据库 host。\nNoSql # 打开 Robo 3T,新建连接,并在 Connection 页面配置 mongo 的连接信息。\n切至 SSH 页面,勾选 Use SSH Tunnel,输入跳板机的连接配置即可。\n配置完成,Save 后连接即可。\n其他例如Redis数据库,可以使用 Redis Desktop Manager SSH Tunnel 连接。\n« shell 基础\n» tee 保存 stderr 到文件\n"},{"id":177,"href":"/linux/tee-keep-stderr/","title":"Tee Keep Stderr","section":"Linux","content":" 🏠 首页 / Linux / tee 保存 stderr 到文件\ntee 保存 stderr 到文件 # tee命令是将 stdout 写入到某文件当中,但是如何将 stderr 也写入到文件当中?\n示例如下:\n1.sh\necho exec 1.sh start! \u0026amp;\u0026amp; \\ cat hello.txt \u0026amp;\u0026amp; \\ echo exec 1.sh end! 假如 hello.txt 文件不存在,执行 1.sh 文件中的 cat 命令将报错。如果我们想将执行 1.sh 文件的输出写入到一个 log 文件,例如:\nsh 1.sh | tee 1.log 执行以上命令,控制台的输出是:\n$ sh 1.sh | tee 1.log exec 1.sh start! cat: hello.txt: No such file or directory 但是写入到 1.log 日志文件中的内容是:\nexec 1.sh start! 可以看到,并没有将错误输出到日志文件中,如果我们想将错误一并输出到日志文件,该怎么做呢?\n使用以下办法:\nsh 1.sh 2\u0026amp;\u0026gt;1 | tee 1.log 这下我们就能同时在控制台和日志文件中看到错误输出了。\n科普:\n上述命令中使用到的 2\u0026gt;\u0026amp;1 是什么意思?\n0 stdin,1 stdout,2 stderr\n2\u0026gt;\u0026amp;1:将标准错误重定向至标准输出\n« 使用 SSH Tunnel 连接中间件\n» vim 使用\n"},{"id":178,"href":"/linux/vim-common-commands/","title":"Vim Common Commands","section":"Linux","content":" 🏠 首页 / Linux / vim 使用\nvim 使用 # 设置 # vim /etc/vim/vimrc 或者当前环境设置\n:set paste :set nopaste :set number :set nonumber :set hlsearch :set nohlsearch :set cursorline :set nocursorline vim删除所有行 # ggdG 撤销 # u ctrl+r 查找 # 大小写问题:\n默认大小写敏感。\n大小写不敏感:/hello\\c\n大小写敏感:/hello\\C\n设置大小写敏感:\necs+:set ignorecase:设置默认忽略大小写敏感\necs+:set smartcase:如果查找字符中存在大写则自动大小写敏感\n查找当前字符:\n光标移动 # h:向左 j:向下 k:向上 l:向右 替换 # :s/jay/dp/g 替换当前行中所有匹配 jay =\u0026gt; dp :1,$s/jay/dp/g 替换所有 :1,5s/jay/dp/g 替换 1 到 5 行 翻页 # ctrl+f:下一页 ctrl+d:下半页 ctrl+b:上一页 ctrl+u:上半页 行操作 # dG:删除当前行到尾行\ndd:删除当前行\nyy:复制当前行\np:粘贴行\nddp:下一行\n退出 # 如果你没有 sudo 权限打开了文件,但是你已经编写了很多内容,怎么保存推出呢:\n:w ! sudo tee % 保存到另外一个文件:\n:w otherfile # 会生成新文件,修改内容体现在新文件中,原文件不变 查找 # 在 normal 模式下按下 / 即可进入查找模式,输入要查找的字符串并按下回车。 Vim 会跳转到第一个匹配。按下 n 查找下一个,按下 N 查找上一个。\nVim 查找支持正则表达式,例如 /vim$ 匹配行尾的 \u0026quot;vim\u0026quot;。 需要查找特殊字符需要转义,例如 /vim\\$ 匹配 \u0026quot;vim$\u0026quot;。\n注意查找回车应当用 \\n,而替换为回车应当用 \\r(相当于 \u0026lt;CR\u0026gt;)。\n大小写敏感查找 # 在查找模式中加入 \\c 表示大小写不敏感查找,\\C 表示大小写敏感查找。例如:\n/foo\\c 将会查找所有的 \u0026quot;foo\u0026quot;,\u0026quot;FOO\u0026quot;,\u0026quot;Foo\u0026quot; 等字符串。\n大小写敏感配置 # Vim 默认采用大小写敏感的查找,为了方便我们常常将其配置为大小写不敏感:\n\u0026#34; 设置默认进行大小写不敏感查找 set ignorecase \u0026#34; 如果有一个大写字母,则切换到大小写敏感查找 set smartcase 将上述设置粘贴到你的 ~/.vimrc,重新打开 Vim 即可生效。\n查找当前单词 # 在 normal 模式下按下 * 即可查找光标所在单词(word), 要求每次出现的前后为空白字符或标点符号。例如当前为 foo, 可以匹配 foo bar 中的 foo,但不可匹配 foobar 中的 foo。 这在查找函数名、变量名时非常有用。\n按下 g* 即可查找光标所在单词的字符序列,每次出现前后字符无要求。 即 foo bar 和 foobar 中的 foo 均可被匹配到。\n其他设置 # :set incsearch 可以在敲键的同时搜索,按下回车把移动光标移动到匹配的词; 按下 Esc 取消搜索。\n:set wrapscan 用来设置到文件尾部后是否重新从文件头开始搜索。\n查找与替换 # :s(substitute)命令用来查找和替换字符串。语法如下:\n:{作用范围}s/{目标}/{替换}/{替换标志} 例如 :%s/foo/bar/g 会在全局范围(%)查找 foo 并替换为 bar,所有出现都会被替换(g)。\n作用范围 # 作用范围分为当前行、全文、选区等等。\n当前行:\n:s/foo/bar/g 全文:\n:%s/foo/bar/g 选区,在 Visual 模式下选择区域后输入:,Vim 即可自动补全为 :'\u0026lt;,'\u0026gt;。\n:\u0026#39;\u0026lt;,\u0026#39;\u0026gt;s/foo/bar/g 2-11行:\n:5,12s/foo/bar/g 当前行 . 与接下来两行 +2:\n:.,+2s/foo/bar/g 替换标志 # 上文中命令结尾的 g 即是替换标志之一,表示全局 global 替换(即替换目标的所有出现)。 还有很多其他有用的替换标志:\n空替换标志表示只替换从光标位置开始,目标的第一次出现:\n:%s/foo/bar i 表示大小写不敏感查找,I 表示大小写敏感:\n:%s/foo/bar/i # 等效于模式中的\\c(不敏感)或\\C(敏感) :%s/foo\\c/bar c 表示需要确认,例如全局查找 \u0026quot;foo\u0026quot; 替换为 \u0026quot;bar\u0026quot; 并且需要确认:\n:%s/foo/bar/gc 回车后Vim会将光标移动到每一次 \u0026quot;foo\u0026quot; 出现的位置,并提示\nreplace with bar (y/n/a/q/l/^E/^Y)? 按下 y 表示替换,n 表示不替换,a 表示替换所有,q 表示退出查找模式, l 表示替换当前位置并退出。^E 与 ^Y 是光标移动快捷键,参考: Vim中如何快速进行光标移动。\n高亮设置 # 高亮颜色设置 # 如果你像我一样觉得高亮的颜色不太舒服,可以在 ~/.vimrc 中进行设置:\nhighlight Search ctermbg=yellow ctermfg=black highlight IncSearch ctermbg=black ctermfg=yellow highlight MatchParen cterm=underline ctermbg=NONE ctermfg=NONE 上述配置指定 Search 结果的前景色(foreground)为黑色,背景色(background)为灰色; 渐进搜索的前景色为黑色,背景色为黄色;光标处的字符加下划线。\n更多的CTERM颜色可以查阅: http://vim.wikia.com/wiki/Xterm256_color_names_for_console_Vim\n禁用/启用高亮 # 有木有觉得每次查找替换后 Vim 仍然高亮着搜索结果? 可以手动让它停止高亮,在 normal 模式下输入:\n:nohighlight \u0026#34; 等效于 :nohl 其实上述命令禁用了所有高亮,只禁用搜索高亮的命令是 :set nohlsearch。 下次搜索时需要 :set hlsearch 再次启动搜索高亮。\n延时禁用 # 怎么能够让 Vim 查找/替换后一段时间自动取消高亮,发生查找时自动开启呢?\n\u0026#34; 当光标一段时间保持不动了,就禁用高亮 autocmd cursorhold * set nohlsearch \u0026#34; 当输入查找命令时,再启用高亮 noremap n :set hlsearch\u0026lt;cr\u0026gt;n noremap N :set hlsearch\u0026lt;cr\u0026gt;N noremap / :set hlsearch\u0026lt;cr\u0026gt;/ noremap ? :set hlsearch\u0026lt;cr\u0026gt;? noremap * *:set hlsearch\u0026lt;cr\u0026gt; 将上述配置粘贴到 ~/.vimrc,重新打开 vim 即可生效。\n一键禁用 # 如果延时禁用搜索高亮仍然不够舒服,可以设置快捷键来一键禁用/开启搜索高亮:\nnoremap n :set hlsearch\u0026lt;cr\u0026gt;n noremap N :set hlsearch\u0026lt;cr\u0026gt;N noremap / :set hlsearch\u0026lt;cr\u0026gt;/ noremap ? :set hlsearch\u0026lt;cr\u0026gt;? noremap * *:set hlsearch\u0026lt;cr\u0026gt; nnoremap \u0026lt;c-h\u0026gt; :call DisableHighlight()\u0026lt;cr\u0026gt; function! DisableHighlight() set nohlsearch endfunc 希望关闭高亮时只需要按下 Ctrl+H,当发生下次搜索时又会自动启用。\n参考阅读 # XTERM 256色: http://vim.wikia.com/wiki/Xterm256_color_names_for_console_Vim Vim Wikia - 查找与替换: http://vim.wikia.com/wiki/Search_and_replace 用 Vim 打造 IDE 环境: https://harttle.land/2015/11/04/vim-ide.html 本文采用 知识共享署名 4.0 国际许可协议(CC-BY 4.0)进行许可,转载注明来源即可: https://harttle.land/2016/08/08/vim-search-in-file.html。学识粗浅写作仓促,如有错误辛苦评论或 邮件 指出。\n写入、保存、退出 # :q[uit] \u0026#34; 退出 :q! \u0026#34; 强制退出 :w[rite] \u0026#34; 保存 :w! \u0026#34; 强制保存,能不能保存成功取决于用户对文件的权限 :w ! sudo tee % \u0026#34; 如果没有权限保存,试试这个命令 ZZ \u0026#34; 两个大写的 Z,没有修改直接退出,有修改保存后退出 :w newfilename \u0026#34; 另存为新文件 :1, 10 w newfilename \u0026#34; 将 1 到 10 行的内容另存为新文件 :1, 10 w \u0026gt;\u0026gt; filename \u0026#34; 将 1 到 10 行的内容另存为新文件 :r filename \u0026#34; 将目标文件的内容追加到当前光标下一行 :3 r filename \u0026#34; 将目标文件的内容追加到第 3 行一下 :! ls \u0026#34; 暂时离开 Vim 查看当前目录的文件,回车后返回 Vim 光标移动 # h \u0026#34; 方向键 ← j \u0026#34; 方向键 ↓ k \u0026#34; 方向键 ↑ l \u0026#34; 方向键 → 0 \u0026#34; 移动到行首 $ \u0026#34; 移动到行尾的回车符 g_ \u0026#34; 移动到行尾最后一个非空字符 gg \u0026#34; 移动到第一行 G \u0026#34; 移动到最后一行\u0026#34; w \u0026#34; 移动到下一个单词开头 e \u0026#34; 移动到单词的结尾 b \u0026#34; 移动到单词的开头 \u0026#34; 不常用 nh \u0026#34; 向左移动 n 格,n 为数字 nl \u0026#34; 向右移动 n 格 nj \u0026#34; 向下移动 n 行 nk \u0026#34; 向上移动 n 行 n\u0026lt;space\u0026gt; \u0026#34; 向右移动 n 格,同 nl H \u0026#34; 移动到当前屏幕第一行的第一个字符 M \u0026#34; 移动到当前屏幕中间行的第一个字符 L \u0026#34; 移动到当前屏幕最后一行的第一个字符 + \u0026#34; 移动到非空白字符的下一行 - \u0026#34; 移动到非空白字符的上一行 :n\u0026lt;cr\u0026gt; \u0026#34; 跳转到第 n 行h \u0026#34; 方向键 ← j \u0026#34; 方向键 ↓ k \u0026#34; 方向键 ↑ l \u0026#34; 方向键 → 0 \u0026#34; 移动到行首 $ \u0026#34; 移动到行尾的回车符 g_ \u0026#34; 移动到行尾最后一个非空字符 gg \u0026#34; 移动到第一行 G \u0026#34; 移动到最后一行\u0026#34; w \u0026#34; 移动到下一个单词开头 e \u0026#34; 移动到单词的结尾 b \u0026#34; 移动到单词的开头 \u0026#34; 不常用 nh \u0026#34; 向左移动 n 格,n 为数字 nl \u0026#34; 向右移动 n 格 nj \u0026#34; 向下移动 n 行 nk \u0026#34; 向上移动 n 行 n\u0026lt;space\u0026gt; \u0026#34; 向右移动 n 格,同 nl H \u0026#34; 移动到当前屏幕第一行的第一个字符 M \u0026#34; 移动到当前屏幕中间行的第一个字符 L \u0026#34; 移动到当前屏幕最后一行的第一个字符 + \u0026#34; 移动到非空白字符的下一行 - \u0026#34; 移动到非空白字符的上一行 :n\u0026lt;cr\u0026gt; \u0026#34; 跳转到第 n 行 翻页 # \u0026lt;c-f\u0026gt; \u0026#34; 向下移动一页 \u0026lt;c-d\u0026gt; \u0026#34; 向下移动半页 \u0026lt;c-b\u0026gt; \u0026#34; 向上移动一页 \u0026lt;c-u\u0026gt; \u0026#34; 向上移动半页 查找与替换 # /word \u0026#34; 从光标位置向下搜索 word 单词 ?word \u0026#34; 从光标位置向上搜索 word 单词 n \u0026#34; 英文字母 n,根据 / 或 ? 搜索的方向定位到下一个匹配目标 N \u0026#34; 与 n 相反,定位匹配目标 :n1,n2s/word1/word2/g \u0026#34; n1, n2 表示数字,替换 n1 行到 n2 行的单词 :1,$s/word1/word2/g \u0026#34; 全文替换,也可以写成 :%s/word1/word2/g :1,$s/word1/word2/gc \u0026#34; 全文替换,并出现确认提示 复制、粘贴、删除 # x \u0026#34; 向后删除一个字符 nx \u0026#34; 向后删除 n 个字符 X \u0026#34; 向前删除一个字符 nX \u0026#34; 向前删除 n 个字符 dd \u0026#34; 删除当前行 ndd \u0026#34; 向下删除 n 行 d1G / dgg \u0026#34; 删除第一行到当前行的数据 dG \u0026#34; 删除当前行到最后一行的数据 d$ \u0026#34; 删除当前字符到行尾 D \u0026#34; 删除当前字符到行尾 d0 \u0026#34; 从行首删除到当前字符 yy \u0026#34; 复制当前行 Y \u0026#34; 复制当前行 nyy \u0026#34; 从当前行开始复制 n 行 y1G / ygg \u0026#34; 从第一行复制到当前行 yG \u0026#34; 从当前行复制到最后一行 y0 \u0026#34; 从行首复制到当前字符 y$ \u0026#34; 从当前字符复制到行尾 p, P \u0026#34; 黏贴,p 黏贴到光标下一行,P 黏贴到光标上一行 yyp \u0026#34; 复制并粘贴 ddp \u0026#34; 删除并粘贴,相当于下移当前行 \u0026#34;+y \u0026#34; 复制本文到系统剪切板 \u0026#34;+p \u0026#34; 粘贴系统剪切板到 Vim(不会影响文本格式) 插入 # i \u0026#34; 在光标前进入 insert 模式 I \u0026#34; 在当前行左边第一个非空字符前进入 insert 模式,类似其他编辑器的 \u0026lt;c-a\u0026gt; 快捷键 a \u0026#34; 在光标后进入 insert 模式 A \u0026#34; 在当前行右边第一个非空字符前进入 insert 模式,类似其他编辑器的 \u0026lt;c-e\u0026gt; 快捷键 o \u0026#34; 在光标的下一行插入 O \u0026#34; 在光标的上一行插入 s \u0026#34; 删除当前字符,并进入 insert 模式 S \u0026#34; 删除当前行,并进入插入模式 vc \u0026#34; 删除当前字符,并进入 insert 模式 cc \u0026#34; 删除当前行,并进入插入模式 c0 \u0026#34; 删除光标位置到行首,并进入 insert 模式 cg_ \u0026#34; 删除光标位置到行尾最后一个非空字符,并进入 insert 模式 ce \u0026#34; 删除光标位置到单词末尾,并进入 insert 模式 cw \u0026#34; 删除光标位置到单词末尾,并进入 insert 模式 ciw \u0026#34; 删除当前单词,并进入 insert 模式 cip \u0026#34; 删除整个段落,并进入 insert 模式 ci( \u0026#34; 删除当前括号内的内容,并进入 insert 模式 适用于 ([{\u0026lt;\u0026#39;` 等所有成对的标签 撤销重做 # u \u0026#34; 撤销 \u0026lt;c-r\u0026gt; \u0026#34; 重做 . \u0026#34; 重复完成操作 替换 # r \u0026#34; 替换单个字符,自动返回 normal 模式 R \u0026#34; 连续替换多个字符,手动 \u0026lt;esc\u0026gt; 返回 normal 模式 大小写 # ~ \u0026#34; 当前字符大小写反转 g~~ \u0026#34; 正行字符大小写反转 vu \u0026#34; 当前字符小写 vU \u0026#34; 当前字符大写 vU \u0026#34; 当前字符大写 viwu \u0026#34; 当前字符小写 viwU \u0026#34; 当前字符大写 ggguG \u0026#34; 文本所有字符小写 gggUG \u0026#34; 文本所有字符大写 :%s/\\\u0026lt;./\\u\u0026amp;/g \u0026#34; 将所有单词首字母大写(需要使用 :nohls 去掉高亮) :%s/\\\u0026lt;./\\l\u0026amp;/g \u0026#34; 将所有单词首字母小写 :%s/.*/\\u\u0026amp; \u0026#34; 将每行第一个字母大写 :%s/.*/\\l\u0026amp; \u0026#34; 将每行第一个字母小写 多窗口操作 # :sp filename \u0026#34; 上下分割窗口 :vs[p] filename \u0026#34; 左右分割窗口 \u0026lt;c-w\u0026gt;h[j[k[l]]] \u0026#34; 根据方向键移动光标到该方向的窗口上 \u0026lt;c-w\u0026gt;[N]\u0026gt; \u0026#34; N 位数字,可选,增加当前窗口 N 列宽\u0026#34; \u0026lt;c-w\u0026gt;[N]\u0026lt; \u0026#34; N 位数字,可选,减少当前窗口 N 列宽\u0026#34; \u0026lt;c-w\u0026gt;[N]+ \u0026#34; N 位数字,可选,增加当前窗口 N 行高\u0026#34; \u0026lt;c-w\u0026gt;[N]- \u0026#34; N 位数字,可选,减少当前窗口 N 行高\u0026#34; \u0026lt;c-w\u0026gt;= \u0026#34; 将所有窗口设置等宽高 \u0026lt;c-w\u0026gt;[N]n \u0026#34; N 位数字,可选,打开一个新窗口 N 行高,默认为整个窗口的一半\u0026#34; \u0026lt;c-w\u0026gt;[N]s \u0026#34; N 位数字,可选,将当前窗口垂直分割为上下两个窗口展示\u0026#34; \u0026#34; 新窗口可以为 N 行高,默认为整个窗口的一半\u0026#34; \u0026#34; 类似于 :sp current_file\u0026#34; \u0026lt;c-w\u0026gt;[N]v \u0026#34; N 位数字,可选,将当前窗口水平分割为左右两个窗口展示\u0026#34; \u0026#34; 新窗口可以为 N 列宽,默认为整个窗口的一半\u0026#34; \u0026#34; 类似于 :vs current_file\u0026#34; \u0026lt;c-w\u0026gt;o \u0026#34; 关闭除当前窗口外的所有窗口 \u0026lt;c-w\u0026gt;r \u0026#34; 顺时针转动窗口 \u0026lt;c-w\u0026gt;R \u0026#34; 逆时针转动窗口 \u0026lt;c-w\u0026gt;x \u0026#34; 对调左右或上下两个对应的窗口 \u0026lt;c-w\u0026gt;q \u0026#34; 退出窗口 :q \u0026#34; 退出窗口 多文件编辑 # vim file1 file2 \u0026#34; 同时打开两个文件 :files \u0026#34; 查看现在编辑的文件列表,%a 代表正在操作哪个文件 1 %a \u0026#34;file1\u0026#34; line 1 2 \u0026#34;file2\u0026#34; line 0 :n \u0026#34; 跳到下一个文件,这里的 n 就是字母 :N \u0026#34; 跳到上一个文件 « tee 保存 stderr 到文件\n"},{"id":179,"href":"/middleware/","title":"Middleware","section":"","content":" 🏠 首页 / 数据中间件\n数据中间件 # Elasticsearch\nMongoDB\nMySQL\nPostgres\nRedis\n"},{"id":180,"href":"/middleware/elasticsearch/","title":"Elasticsearch","section":"Middleware","content":" 🏠 首页 / 数据中间件 / Elasticsearch\nElasticsearch # 全文搜索\nAPI # 题外话:\n幂等性:多次执行同样的请求,资源只能创建或修改一次\nPOST 请求不是幂等性的,同样的数据请求,会造成不同的影响\nPUT 是幂等性的,同样的请求造成的影响是一样的\n创建索引 # PUT /users 查询索引 # 获取单个索引\nGET /users 获取所有索引\nGET /_cat/indices?v 删除索引 # DELETE /users 创建文档 # 这个操作是在单个索引下的\nPOST /users/_doc # 一定需要body,否则报错 body: { \u0026#34;name\u0026#34;: \u0026#34;dp\u0026#34;, \u0026#34;age\u0026#34;: 18 } 上面这个文档创建时会生成随机 ID(返回结果中的 _id),不便维护,使用下面的方法自定义文档 ID,此时由于 ID 自定义了,就要求幂等,所以可以使用 PUT 方法\nPOST | PUT /users/_doc/1002 PUT /users/_create/1003 # 一定需要body,否则报错 body: { \u0026#34;name\u0026#34;: \u0026#34;dp\u0026#34;, \u0026#34;age\u0026#34;: 18 } 查询文档 # 获取单个文档\nGET /users/_doc/1001 获取所有文档\nGET /users/_search 修改文档 # PUT /users/_doc/1001 body: { \u0026#34;name\u0026#34;: \u0026#34;dp\u0026#34;, \u0026#34;age\u0026#34;: 28 } 你可能发现了,PUT 既可以创建也可以修改。\n修改特定字段(非幂等)\nPOST /users/_update/1001 body: { \u0026#34;doc\u0026#34;: { \u0026#34;age\u0026#34;: 29 } } 删除文档 # DELETE /users/_doc/1001 复杂查询 # 条件查询 # GET /users/_search?q=name:dp 或者通过请求体查询\nGET /users/_search { \u0026#34;query\u0026#34;: { // \u0026#34;match\u0026#34;: { // \u0026#34;name\u0026#34;: \u0026#34;dp\u0026#34; // } // 查询所有 \u0026#34;match_all\u0026#34;: { } } // 分页 \u0026#34;from\u0026#34;: 0, \u0026#34;size\u0026#34;: 10, // 过滤字段 \u0026#34;_source\u0026#34;: [\u0026#34;name\u0026#34;, \u0026#34;age\u0026#34;] // 排序 \u0026#34;sort\u0026#34;: { \u0026#34;age\u0026#34;: { \u0026#34;order\u0026#34;: \u0026#34;asc|desc\u0026#34; } }, \u0026#34;highlight\u0026#34;: { \u0026#34;fields\u0026#34;: { \u0026#34;name\u0026#34;: {} } } } 使用 match,如果查询条件是 \u0026ldquo;name\u0026rdquo;: \u0026ldquo;Hello World\u0026rdquo;,查询结果会是 Hello,World 分别查询的结果集合,因为 ES 会将查询关键词拆解,每个单词都单独匹配索引。\n如果香要完全匹配,使用 match_phrase。\n多条件查询 # GET /users/_search { \u0026#34;query\u0026#34;: { \u0026#34;bool\u0026#34;: { // must =\u0026gt; and; should =\u0026gt; or \u0026#34;must\u0026#34;: [ { \u0026#34;match\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;dp\u0026#34; } }, { \u0026#34;match\u0026#34;: { \u0026#34;age\u0026#34;: 28 } } ], \u0026#34;filter\u0026#34;: { \u0026#34;range\u0026#34;: { \u0026#34;age\u0026#34;: { \u0026#34;gt\u0026#34;: 18 } } } } } } 聚合查询 # { \u0026#34;aggs\u0026#34;: { // 聚合操作 \u0026#34;age_group\u0026#34;: { // 名称,随意命名 \u0026#34;terms\u0026#34;: { // 分组 \u0026#34;field\u0026#34;: \u0026#34;age\u0026#34; // 分组字段 }, \u0026#34;avg\u0026#34;: { // 平均值 \u0026#34;field\u0026#34;: \u0026#34;age\u0026#34; // 分组字段 } // 此外还有 max, min } }, \u0026#34;size\u0026#34;: 0 //不显示原始数据 } 设置 # 设置集群最大索引数 # 如果遇到错误:\nValidation Failed: 1: this action would add [2] total shards, but this cluster currently has [3000]/[3000] maximum shards open; 那么导致该问题的原因可能是由于现创建的 index 太多,已经达到了 cluster.max_shards_per_node 的限制,需要计划清除一些无用的 index 或者增加es集群节点 index 限制:\nPUT /_cluster/settings { \u0026#34;transient\u0026#34;: { \u0026#34;cluster.max_shards_per_node\u0026#34;:5000 } } 或者使用 curl 调用集群设置的 api\ncurl -XPUT $CLUSTER_URL/_cluster/settings -H \u0026#39;Content-type: application/json\u0026#39; --data-binary $\u0026#39;{\u0026#34;transient\u0026#34;:{\u0026#34;cluster.max_shards_per_node\u0026#34;:5000}}\u0026#39; 设置删除权限 # 删除权限需要\nPUT /_cluster/settings { \u0026#34;persistent\u0026#34; : { \u0026#34;action.destructive_requires_name\u0026#34; : \u0026#34;false\u0026#34; } } 或者使用 curl 调用集群设置的 api\ncurl -XPUT $CLUSTER_URL/_cluster/settings -H \u0026#39;Content-type: application/json\u0026#39; --data-binary $\u0026#39;{\u0026#34;persistent\u0026#34;:{\u0026#34;action.destructive_requires_name\u0026#34;:\u0026#34;false\u0026#34;}}\u0026#39; » MongoDB\n"},{"id":181,"href":"/middleware/mongodb/","title":"Mongodb","section":"Middleware","content":" 🏠 首页 / 数据中间件 / MongoDB\nMongoDB # 资料 # http://cw.hubwiz.com/card/c/543b2f3cf86387171814c026/1/1/1/ http://cw.hubwiz.com/card/c/5438c259032c7817c40298b5/1/1/1/ 安装 # 按照官网给出的指南在 ubuntu 系统安装 mongod,参考 https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/\n验证 mongo 是否安装成功:进入 ubuntu shell 窗口,直接输入\nmongo --version 窗口正常输出 mongo 版本就说明 mongo 安装成功\n启动 mongo 服务\nsudo systemctl statt mongod #/stop/restart 创建 dba 用户并添加权限验证\nmongodb 没有开启权限验证之前,使用 mongo 命令可以直接连接本地 mongodb;\nsudo mongo 使用 db.createUser 命令创建 dba 用户,为 dba 用户添加所有 database 的管理员权限;\n\u0026gt; db.createUser({user:\u0026#34;dba\u0026#34;,pwd:\u0026#34;[your pass]\u0026#34;,roles:[ {role:\u0026#34;readWriteAnyDatabase\u0026#34;,db:\u0026#34;admin\u0026#34;},{role:\u0026#34;dbAdminAnyDatabase\u0026#34;,db:\u0026#34;admin\u0026#34;},{role:\u0026#34;userAdminAnyDatabase\u0026#34;,db:\u0026#34;admin\u0026#34;},{role:\u0026#34;clusterAdmin\u0026#34;,db:\u0026#34;admin\u0026#34;},{role:\u0026#34;restore\u0026#34;,db:\u0026#34;admin\u0026#34;},{role:\u0026#34;backup\u0026#34;,db:\u0026#34;admin\u0026#34;} ]}) Successfully added user: { \u0026#34;user\u0026#34; : \u0026#34;dba\u0026#34;, \u0026#34;roles\u0026#34; : [ // ... ] } dba 包含的 role:\nreadWriteAnyDatabase dbAdminAnyDatabase userAdminAnyDatabase clusterAdmin restore backup 修改 mongod.conf 文件,mongodb 可以对外访问,并开启权限验证\nsudo vim /etc/mongod.conf mongod.conf 文件原始内容: mongod.conf 修改后内容:\n注意:如果仅仅是将 bindIPAll 配置为 true,即允许外部网络网络,而没有开启权限验证,那么外部对 mongodb 拥有很大的操作权限,存在很大的安全问题。\nsudo service mongod restart 修改完 mongod.conf 文件后一定要重启 mongo 服务生效。\n设置权限验证后就,如果直接通过 mongo 命令连接 mongodb,绝大多数操作都是被禁用的,需要配置权限连接\nsudo mongo [ip/domain name]:[port]/[database] -u username -p pwd # 如: sudo mongo 10.0.0.1:27017/admin -u dba -p [your-pass] 创建 database\nuse devops 使用 use [database],如果 database 不存在则会默认新建; 新创建但是不存在数据的 database,使用 show dbs 将看不到,除非插入数据 也可以使用 db.createdatabase 为专有 database 创建用户\n\u0026gt; db.createUser({user:\u0026#34;devops\u0026#34;,pwd:\u0026#34;[your pass]\u0026#34;,roles:[{role:\u0026#34;readWrite\u0026#34;,db:\u0026#34;devops\u0026#34;}]}) Successfully added user: { \u0026#34;user\u0026#34; : \u0026#34;devops\u0026#34;, \u0026#34;roles\u0026#34; : [ { \u0026#34;role\u0026#34; : \u0026#34;readWrite\u0026#34;, \u0026#34;db\u0026#34; : \u0026#34;devops\u0026#34; } ] } devops 插入数据\nsudo mongo 10.0.0.1:27017/devops -u devops -p [your-pass] \u0026gt;use devops \u0026gt;db.temp.insert({\u0026#34;name\u0026#34;:\u0026#34;devops.mongodb\u0026#34;}) 注意:\ndb.temp.insert,如果没有 temp collection,则会默认创建。 坑:连接 mongo 时,如果密码带有特殊字符,如!(其他没测)需要在密码前后使用单引号引用起来!!!\nmongodb 角色: https://docs.mongodb.com/manual/reference/built-in-roles/\n数据库用户角色:read、readWrite; 数据库管理角色:dbAdmin、dbOwner、userAdmin; 集群管理角色:clusterAdmin、clusterManager、clusterMonitor、hostManager; 备份恢复角色:backup、restore; 所有数据库角色:readAnyDatabase、readWriteAnyDatabase、userAdminAnyDatabase、dbAdminAnyDatabase 超级用户角色:root // 这里还有几个角色间接或直接提供了系统超级用户的访问(dbOwner 、userAdmin、userAdminAnyDatabase) 内部角色:__system 基础查询 # like 查询\nmongodb 查询\ndb.getCollection(\u0026#39;test\u0026#39;).find({\u0026#34;name\u0026#34;:{$regex:/dp/i}}) /dp/i 忽略大小写 ​ python mongo 查询\ntest._get_collection().find( {\u0026#34;name\u0026#34;: {\u0026#39;$regex\u0026#39;: \u0026#39;dp\u0026#39;, \u0026#39;$options\u0026#39;: \u0026#39;i\u0026#39;}}) not like查询\nmongodb查询\ndb.getCollection(\u0026#39;test\u0026#39;).find({\u0026#34;name\u0026#34;:{$not:/dp/i}}) ​ python mongo 查询\nimport re test._get_collection().find({\u0026#34;path\u0026#34;: {\u0026#39;$not\u0026#39;: re.compile(\u0026#39;dp\u0026#39;)}}) 创建索引 # db.getCollection(\u0026#34;ColName\u0026#34;).createIndex({\u0026#34;Name\u0026#34;, 1}) # 如果集合数据量比较大了 那么创建索引会非常耗时,建议使用后台创建 db.getCollection(\u0026#34;ColName\u0026#34;).createIndex({\u0026#34;Name\u0026#34;, 1}, {\u0026#34;background\u0026#34;: true}) 用户 # 修改用户密码:\ndb.changeUserPassword(\u0026#34;username\u0026#34;,\u0026#34;new_pwd\u0026#34;) « Elasticsearch\n» MySQL\n"},{"id":182,"href":"/middleware/mysql/","title":"Mysql","section":"Middleware","content":" 🏠 首页 / 数据中间件 / MySQL\nMySQL # 安装 # Windows 安装 MySQL # 下载 Mysql 安装包: https://dev.mysql.com/downloads/installer/\n下载完成后,双击 msi 文件安装。\nUbuntu 安装 MySQL # sudo apt update sudo apt install mysql-server -y # 只安装 mysql 客户端 sudo apt install mysql-client -y Docker 安装 MySQL # docker run -d --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7 Troubleshooting # Q1. root 用户本地登录 # 使用命令 mysql -u root -p,输入密码后登录失败,提示如下:\nAccess denied for user \u0026#39;root\u0026#39;@\u0026#39;localhost\u0026#39; 解决方案::\n修改 mysqld.cnf 配置文件\nsudo vim /etc/mysql/mysql.conf.d/mysqld.cnf 在 [mysqld] 块的 skip-external-locking 下添加 skip-grant-tables\n重启mysql服务\nsudo systemctl restart mysql.service root 无密码登录 mysql\nmysql -u root -p 修改 root 用户密码以及 plugin\n# 修改 root 用户密码 use mysql; update user set authentication_string=password(\u0026#34;123456\u0026#34;),plugin=\u0026#39;mysql_native_password\u0026#39; where user=\u0026#39;root\u0026#39;; flush privileges; # quit 退出 quit 注释 etc/mysql/mysql.conf.d/mysqld.cnf 刚新加的行\n$ sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf ... #skip-grant-tables ... 再次重启 mysql 服务\nsudo systemctl restart mysql.service 使用root密码登录mysql\nmysql -u root -p # Enter Password:123456 Q2. 创建用户 # create user zhangsan identified by \u0026#39;zhangsan\u0026#39;; grant all privileges on zhangsanDb.* to zhangsan@\u0026#39;%\u0026#39; identified by \u0026#39;zhangsan\u0026#39;; flush privileges; 遇到 Your password does not satisfy the current policy requirements 问题。密码验证无法通过。\n解决方案::\nSHOW VARIABLES LIKE \u0026#39;validate_password%\u0026#39;; set global validate_password_policy=LOW; MySQL配置 # 创建用户 # use mysql; create user \u0026#39;admin\u0026#39;@\u0026#39;%\u0026#39; identified by \u0026#39;Admin@123\u0026#39;; %,表示允许所有访问地址远程访问,一般 root 账号是使用的\u0026rsquo;root\u0026rsquo;@\u0026rsquo;localhost'\ngrant all privileges on *.* to \u0026#39;admin\u0026#39;@\u0026#39;%\u0026#39; identified by \u0026#39;Admin@123\u0026#39; with grant option; *.*,第一个 * 表示数据库资源,第二个表示表资源,也可以使用为 store_db.t_order\nwith grant option,表示允许级联授权\nflush privileges; # 查看用户 select user, host from user; MySQL 开启 binary logging # 修改配置文件,如果 MySQL 部署在 Ubuntu 上,修改 /etc/mysql/mysql.conf.d/mysqld.cnf 文件,取消 server-id,log_bin 的配置项的注释; sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf server-id = 1 log_bin = /var/log/mysql/mysql-bin.log 重启 MySQL。 sudo service mysql restart AWS MySQL 开启 binary logging # 可以参考官方文档: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_LogAccess.Concepts.MySQL.html#USER_LogAccess.MySQL.BinaryFormat\nMySQL 执行进程 # show processlist # 结束执行进程,只删除 command 不为 Sleep 的即可 kill \u0026lt;id\u0026gt; Mysql Join # Inner Join # select * from t_a inner join t_b on t_a.bid = t_b.id Right Join # select * from t_a right join t_b on t_a.bid = t_b.id Left Join # select * from t_a left join t_b on t_a.bid = t_b.id MySQL常用 # 字符串 # 拼接字符串:\n直接拼接字符串\nselect concat(\u0026#34;hello, \u0026#34;,\u0026#34;world!\u0026#34;); # 输出 hello, world 以某个分隔符拼接字符串\nselect concat_ws(\u0026#39;;\u0026#39;,\u0026#34;Apple\u0026#34;,\u0026#34;Orange\u0026#34;,\u0026#34;Banana\u0026#34;); # 输出 Apple;Orange;Banana 截取字符串:\n某个字符串,从第 n 个,返回长度为 l 的字符串\nselect substring(\u0026#39;hello, world!\u0026#39;,2,4); # 或者 select mid(\u0026#39;hello, world!\u0026#39;,2,4) # 输出 ello 从左/右开始截图长度为 l 的字符串\nselect left(\u0026#39;hello, world!\u0026#39;,4) # 输出 hell select right(\u0026#39;hello, world!\u0026#39;,4) # 输出 rld! 替换字符串:\n某个字符串,从第 n 个,长度为 l 的字符串 str1 替换为 str2\nselect insert(\u0026#39;hello, world!\u0026#39;,2,4,\u0026#39;xxxx\u0026#39;) # 输出 hxxx, world! 替换特定字符串\nselect replace(\u0026#39;hello, world!\u0026#39;,\u0026#39;ello\u0026#39;,\u0026#39;xxxx\u0026#39;) # 输出 hxxx, world! 查询字符串位置:\nselect locate(\u0026#39;ello\u0026#39;,\u0026#39;hello, world!\u0026#39;) # 或者 select position(\u0026#39;ello\u0026#39; IN \u0026#39;hello, world!\u0026#39;) # 或者 select instr(\u0026#39;hello, world!\u0026#39;,\u0026#39;ello\u0026#39;) # 均输出 2 时间 # 获取当前时间:\nselect now() 格式化时间:\nselect date_format(now(),\u0026#39;%y-%m-%d\u0026#39;); 格式化参数\n%S, %s 两位数字形式的秒( 00,01, \u0026hellip;, 59) %I, %i 两位数字形式的分( 00,01, \u0026hellip;, 59) %H 两位数字形式的小时,24 小时(00,01, \u0026hellip;, 23) %h 两位数字形式的小时,12 小时(01,02, \u0026hellip;, 12) %k 数字形式的小时,24 小时(0,1, \u0026hellip;, 23) %l 数字形式的小时,12 小时(1, 2, \u0026hellip;, 12) %T 24 小时的时间形式(hh:mm:ss) %r 12 小时的时间形式(hh:mm:ss AM 或hh:mm:ss PM) %p AM或PM %W 一周中每一天的名称(Sunday, Monday, \u0026hellip;, Saturday) %a 一周中每一天名称的缩写(Sun, Mon, \u0026hellip;, Sat) %d 两位数字表示月中的天数(00, 01,\u0026hellip;, 31) %e 数字形式表示月中的天数(1, 2, \u0026hellip;, 31) %D 英文后缀表示月中的天数(1st, 2nd, 3rd,\u0026hellip;) %w 以数字形式表示周中的天数( 0 = Sunday, 1=Monday, \u0026hellip;, 6=Saturday) %j 以三位数字表示年中的天数( 001, 002, \u0026hellip;, 366) %U 周(0, 1, 52),其中Sunday 为周中的第一天 %u 周(0, 1, 52),其中Monday 为周中的第一天 %M 月名(January, February, \u0026hellip;, December) %b 缩写的月名( January, February,\u0026hellip;., December) %m 两位数字表示的月份(01, 02, \u0026hellip;, 12) %c 数字表示的月份(1, 2, \u0026hellip;., 12) %Y 四位数字表示的年份 %y 两位数字表示的年份 %% 直接值“%”\n数据导入导出 # A 表数据写入 B 表:\n如果 a 表字段和 b 表字段一致,则直接使用以下语句\ninsert into t_b select * from t_a 如果只希望 A 表部分字段写入 B 表,则使用以下语句\ninsert into t_b (t_b.f1,t_b.f2,...) select t_a.f1, t_a.f2, ... from t_a 可以使用 as 使字段映射,可以使用 where 过滤数据\n« MongoDB\n» Postgres\n"},{"id":183,"href":"/middleware/postgres/","title":"Postgres","section":"Middleware","content":" 🏠 首页 / 数据中间件 / Postgres\nPostgres # 自增 Id 数据插入权限\nGRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public TO \u0026lt;user_name\u0026gt;; 修改用户密码\nalter user postgres with password \u0026#39;admin123\u0026#39;; « MySQL\n» Redis\n"},{"id":184,"href":"/middleware/redis/","title":"Redis","section":"Middleware","content":" 🏠 首页 / 数据中间件 / Redis\nRedis # 安装 # Docker 安装 redis # sudo docker run --name redis-01 \\ -p 2501:6379 \\ -v /home/dp/apps/redis/redis-01/conf/redis.conf:/etc/redis/redis.conf \\ -v /home/dp/apps/redis/redis-01/data:/data \\ -d \\ redis:6.0 redis-server /etc/redis/redis.conf --appendonly yes redis.conf 是配置文件 redis-server /etc/redis/redis.conf,启用配置,如果没有 redis-server 则 redis 默认是无配置启动 \u0026ndash;appendonly yes 启用数据持久化 redis.conf 参照:\nbind 127.0.0.1 # 注释掉这部分,使 redis 可以外部访问 daemonize no # 用守护线程的方式启动 requirepass your_pwd # 给 redis 设置密码 appendonly yes # redis 持久化 默认是 no tcp-keepalive 300 # 防止出现远程主机强迫关闭了一个现有的连接的错误 默认是 300 « Postgres\n"},{"id":185,"href":"/os/","title":"Os","section":"","content":" 🏠 首页 / 操作系统\n操作系统 # MacOS\nohmyzsh\nopenssl\nUbuntu\nWindows 使用姿势\n"},{"id":186,"href":"/os/macos/","title":"Macos","section":"Os","content":" 🏠 首页 / 操作系统 / MacOS\nMacOS # Git 忽略 .DS_Store 文件 # # 配置全局忽略文件 git config --global core.excludesfile ~/.gitignore_global # 添加 .DS_Store 文件到全局忽略文件 echo .DS_Store \u0026gt;\u0026gt; ~/.gitignore_global echo ._.DS_Store \u0026gt;\u0026gt; ~/.gitignore_global echo **/.DS_Store \u0026gt;\u0026gt; ~/.gitignore_global echo **/._.DS_Store \u0026gt;\u0026gt; ~/.gitignore_global 配置 PATH # 在终端使用 export 命令设置 PATH 并不能全局生效,如果你想设置全局 PATH ,可以使用以下这个方法:\nsudo mkdir /etc/paths.d/mypath vim /etc/paths.d/mypath /your/path 查看端口占用并退出程序 # 有时候使用 VSCode 调试或运行程序后,无法成功推出程序,端口一直占用。\n查看端口占用:\n# [port] 替换成你想查看的端口号,例如:sudo lsof -i tcp:8080 sudo lsof -i tcp:[port] 上述命令可以得到程序的进程 PID,退出进程:\n# [PID] 替换成程序的进程 PID sudo kill -9 [PID] 重置 Downie 试用 # rm -rfv ~/Library/Containers/com.charliemonroe.Downie-4/Data/Library/Application\\ Support/Downie\\ 4 配置快捷命令:\nvim ~/.zsh alias reset-downie-trial=\u0026#39;rm -rfv ~/Library/Containers/com.charliemonroe.Downie-4/Data/Library/Application\\ Support/Downie\\ 4\u0026#39; 自定义 PATH # mkdir -p ~/.dev/bin echo \u0026#34;export PATH=$PATH:~/.dev/bin\u0026#34; 更新 Python 的证书包 # /Applications/Python\\ 3.6/Install\\ Certificates.command Mac 缓存清理 # Go 缓存清理 # go clean -cache npm 缓存清理 # npm cache clean --force sudo npm cache clean --force yarn 缓存清理 # yarn cache clean rust 缓存清理 # # install cargo-cache cargo install cargo-cache # clean cache cargo cache --autoclean iTerm2 # 解决 iTerm2 下使用 Solarized Dark 主题时,zsh-autosuggestions 显示问题:\nvim .oh-my-zsh/custom/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh 找到并配置:ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=10'\nexec zsh Go 调试问题 # 如果遇到 Failed to launch: could not launch process: can not run under Rosetta, check that the installed build of Go is right for your CPU architecture 问题,尝试以下解决方案:\n安装最新版本的 delve:\ngo install github.com/go-delve/delve/cmd/dlv@latest » ohmyzsh\n"},{"id":187,"href":"/os/ohmyzsh/","title":"Ohmyzsh","section":"Os","content":" 🏠 首页 / 操作系统 / ohmyzsh\nohmyzsh # macos # echo $SHELL /bin/zsh 安装 ohmyzsh sh -c \u0026#34;$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\u0026#34; 安装 zsh-autosuggestions git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions 配置 .zshrc 文件添加 ohmyzsh 插件: plugins=(git docker docker-compose kubectl autojump zsh-autosuggestions) linux # 安装 zsh: sudo apt update sudo apt install zsh -y 修改 shell: chsh -s /usr/bin/zsh 打开新的终端,将使用 zsh\necho $SHELL /usr/bin/zsh 安装 ohmyzsh: sh -c \u0026#34;$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\u0026#34; 安装 zsh-autosuggestions: git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions 配置 .zshrc 文件添加 ohmyzsh 插件: plugins=(git docker docker-compose kubectl autojump zsh-autosuggestions globalias) GLOBALIAS_FILTER_VALUES=(ls grep) « MacOS\n» openssl\n"},{"id":188,"href":"/os/openssl/","title":"Openssl","section":"Os","content":" 🏠 首页 / 操作系统 / openssl\nopenssl # openssl 常用于生成证书、签名、加密等操作。它是一个开源的工具,可以在 Linux、Windows、MacOS 等操作系统上运行。\n包含三个组件:\nopenssl:命令行工具 libcrypto:加密算法库 libssl:加密模块应用库,实现了 SSL 和 TLS 协议 对称加密 # echo test \u0026gt; test.txt # 加密 openssl enc -e -des3 -a -salt -in test.txt -out test.txt.enc # 解密 openssl enc -d -des3 -a -salt -in test.txt.enc -out test.txt.dec -salt:加盐,增加破解难度,使用 openssl 默认盐值 -S [salt]:指定盐值\n非对称加密 # 生成密钥对\nopenssl genrsa -out rsa_private_key.pem 2048 openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem 加密(公钥加密,私钥解密)\necho test \u0026gt; test.txt # 加密 openssl rsautl -encrypt -inkey rsa_public_key.pem -pubin -in test.txt -out test.txt.enc # 解密 openssl rsautl -decrypt -inkey rsa_private_key.pem -in test.txt.enc -out test.txt.dec 数字签名(私钥签名,公钥验证)\necho test \u0026gt; test.txt # 签名 openssl dgst -sha256 -sign rsa_private_key.pem -out test.txt.sign test.txt # 验证 openssl dgst -sha256 -verify rsa_public_key.pem -signature test.txt.sign test.txt CA 证书 \u0026amp; 颁发证书 # 生成 CA 私钥和证书\n# 至少需要输入 4 位密码口令 openssl req -new -x509 -days 3650 -keyout ca.key -out ca.crt 生成服务端私钥和证书\n# 服务端私钥 openssl genrsa -out server.key 2048 # 服务端证书签署请求(csr) openssl req -new -key server.key -out server.csr # 服务端证书 openssl x509 -req -days 3650 -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt 查看证书信息\n# 查看 CA 证书 openssl x509 -in ca.crt -noout -text # 查看服务端证书 openssl x509 -in server.crt -noout -text « ohmyzsh\n» Ubuntu\n"},{"id":189,"href":"/os/ubuntu/","title":"Ubuntu","section":"Os","content":" 🏠 首页 / 操作系统 / Ubuntu\nUbuntu # 终端默认使用英文 # LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=en_US.UTF-8 下载 arm64 桌面镜像 # https://cdimage.ubuntu.com/jammy/daily-live/current/jammy-desktop-arm64.iso 关闭防火墙 # sudo ufw disable sudo ufw status 修改时区 # sudo cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime sudo timedatectl set-timezone Asia/Shanghai date # 同步时间 sudo apt install ntpdate -y sudo ntpdate cn.pool.ntp.org 解决英文系统下中文显示问题 # 修改字体优先级\nsudo vim /etc/fonts/conf.avail/64-language-selector-prefer.conf 注意:在 Ubuntu 23.10 或更新版本的系统上修改文件:\nsudo vim /etc/fonts/conf.d/64-language-selector-cjk-prefer.conf 将 `JP` 和 `KR` 所在行往下调整即可,调整成如下所示: ```xml \u0026lt;?xml version=\u0026#34;1.0\u0026#34;?\u0026gt; \u0026lt;!DOCTYPE fontconfig SYSTEM \u0026#34;fonts.dtd\u0026#34;\u0026gt; \u0026lt;fontconfig\u0026gt; \u0026lt;alias\u0026gt; \u0026lt;family\u0026gt;sans-serif\u0026lt;/family\u0026gt; \u0026lt;prefer\u0026gt; \u0026lt;family\u0026gt;Noto Sans CJK SC\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Noto Sans CJK TC\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Noto Sans CJK HK\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Noto Sans CJK JP\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Noto Sans CJK KR\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Lohit Devanagari\u0026lt;/family\u0026gt; \u0026lt;/prefer\u0026gt; \u0026lt;/alias\u0026gt; \u0026lt;alias\u0026gt; \u0026lt;family\u0026gt;serif\u0026lt;/family\u0026gt; \u0026lt;prefer\u0026gt; \u0026lt;family\u0026gt;Noto Serif CJK SC\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Noto Serif CJK TC\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Noto Serif CJK JP\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Noto Serif CJK KR\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Lohit Devanagari\u0026lt;/family\u0026gt; \u0026lt;/prefer\u0026gt; \u0026lt;/alias\u0026gt; \u0026lt;alias\u0026gt; \u0026lt;family\u0026gt;monospace\u0026lt;/family\u0026gt; \u0026lt;prefer\u0026gt; \u0026lt;family\u0026gt;Noto Sans Mono CJK SC\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Noto Sans Mono CJK TC\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Noto Sans Mono CJK HK\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Noto Sans Mono CJK JP\u0026lt;/family\u0026gt; \u0026lt;family\u0026gt;Noto Sans Mono CJK KR\u0026lt;/family\u0026gt; \u0026lt;/prefer\u0026gt; \u0026lt;/alias\u0026gt; \u0026lt;/fontconfig\u0026gt; 安装搜狗输入法 # 参照官方文档: https://pinyin.sogou.com/linux/help.php\n# 禁用ctrl+shift+f vim ~/.config/sogoupinyin/conf/env.ini # 找到这行ShortCutFanJian=1,改为ShortCutFanJian=0 vim ~/.config/fcitx/conf/fcitx-chttrans.config # 找到 #Hotkey=CTRL_SHIFT_F,取消注释,并改为 #Hotkey=CTRL_SHIFT_] # 保存之后,重新登录即可 修复 No Wi-Fi Adapter Found 问题 # rfkill unblock all sudo /etc/init.d/networking restart sudo rm /etc/udev/rules.d/70-persistent-net.rules sudo reboot M720 鼠标优联连接 # 下载 Solaar\nsudo apt install solaar 下载完成之后,插入 USB 接收器,M720 切换至未被占用的 Channel,并重新启动(Off =\u0026gt; On)。\n打开 Solaar 应用\nsolaar 如果没有发现 M720 鼠标,则重启 Ubuntu,重启之后再次打开 Solaar 之后,可以看到列表中出现 Solaar,点击 \u0026ldquo;Pair the device\u0026rdquo; 即可。\nUbuntu 启用中文输入法 # 好像是 Ubuntu 23.10 后支持。\n首先需要安装中文语言; 选择输入法,Chinese,双击打开; 输入法设置,取消英文模式。 虚拟机 # VMWare Fusion 开启宿主机复制粘贴功能,桌面版 Ubuntu 系统安装插件:\nsudo apt install open-vm-tools open-vm-tools-desktop -y 安装完之后,重启虚拟机即可。\n升级 # 22.04 -\u0026gt; 23.04 # sudo apt update sudo apt upgrade -y sudo apt install update-manager-core -y sudo vim /etc/update-manager/release-upgrades # modify Prompt=lts to Prompt=normal sudo sed -i \u0026#39;s/jammy/lunar/g\u0026#39; /etc/apt/sources.list sudo apt update sudo apt upgrade -y sudo apt dist-upgrade -y 23.04 -\u0026gt; 23.10 # sudo apt update sudo apt upgrade -y sudo do-release-upgrade 输入法 # RIME 输入法:\nsudo apt install ibus-rime 安装完成后,需要注销当前用户重新登录或者直接重启生效。\n配置:\n输入时按 F4,可以直接在输入界面配置,例如配置默认使用简体,默认使用英文等。\ngnome-tweak 工具 # 下载:\nsudo apt install gnome-tweak -y 应用界面找到 Tweak 应用运行,或者直接终端输入 gnome-tweak 运行。\n配置:\n在 2K 分辨率屏幕下可以调整配置:字体 -\u0026gt; Scaling Factor -\u0026gt; 调整为 1.2; « openssl\n» Windows 使用姿势\n"},{"id":190,"href":"/os/windows/","title":"Windows","section":"Os","content":" 🏠 首页 / 操作系统 / Windows 使用姿势\nWindows 使用姿势 # 0. 激活 windows # 以管理员身份运行命令行,输入以下三行命令:\nslmgr /ipk W269N-WFGWX-YVC9B-4J6C9-T83GX # 等待弹窗出现,点击确定之后,再继续执行下一行命令 slmgr /skms kms.loli.best # slmgr /skms kms.03k.org # 同样需要等待弹窗出现,点击确定之后,再继续执行 slmgr /ato # 正常情况下应该会出现激活成功的弹窗 1. Windows Terminal SSH 连接超时自动断开 # 使用 Windows Terminal SSH 连接 linux 服务器,每过一段时间后,就会自动断开。\n解决方案:\n打开配置文件 %USERPROFILE%/.ssh/config,在该配置文件中添加配置行:\nServerAliveInterval 60 2. VSCode 搭配 Remote-SSH # 配置远程访问文件 %USERPROFILE%/.ssh/config:\n密钥文件进行SSH连接 # Host aliyun HostName 11.11.11.11 User root IdentityFile ~/.ssh/aliyun_key 用户密码进行SSH连接 # Host ubuntu HostName 192.168.11.11 User dp 但是如果你不是使用密钥形式配置的话,每次连接时都需要输入密码。\n解决方案:\ncd ~/.ssh ssh-keygen -t rsa -C \u0026#34;remote\u0026#34; -f ubuntu 将生成 ubuntu 和 ubuntu.pub 文件,将 ubuntu.pub 文件内容追加拷贝至远程服务器 ~/.ssh/authorized_keys 文件即可,如果文件不存在,则创建。\n3. Powershell 命令 # 清除历史命令\nRemove-Item (Get-PSReadlineOption).HistorySavePath 4. 安装 windows11 虚拟机 # 安装 windwos 虚拟机跳过网络的方法,按下 Shift+F10 或者是 Fn+Shift+F10 快捷键调出命令提示符窗口,执行命令:\noobe\\BypassNRO « Ubuntu\n"},{"id":191,"href":"/reading/","title":"Reading","section":"","content":" 🏠 首页 / 阅读\n阅读 # 云原生应用开发:Operator原理与实践\n我的第一本算法书\n深入理解计算机网络.md\n"},{"id":192,"href":"/reading/%E4%BA%91%E5%8E%9F%E7%94%9F%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91Operator%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E8%B7%B5/","title":"云原生应用开发: Operator原理与实践","section":"Reading","content":" 🏠 首页 / 阅读 / 云原生应用开发:Operator原理与实践\n云原生应用开发:Operator原理与实践 # 2. Operator原理 # 2.2 client-go原理 # client-go主要用在Kubernetes的Controller中,包括内置Controller(如kube-controller-manager)和CRD控制器;\n实现对各类K8s API资源的增删改查操作;\n包含Reflector,Informer,Indexr等组件。\n2.2.1 client-go介绍 # 是操作k8s集群资源的编程式交互客户端,利用对kube-apiserver的交互访问,实现对各类K8s API资源的增删改查操作;\nclient-go不仅被k8s项目本身使用(如kubectl),还在基于k8s的二次开发中被外部用户广泛使用:自定义控制器,Operator等。\n使用:\nclient-go库抽象封装了与k8s reset api的交互,便于开发者基于k8s做二次开发。利用client-go操作k8s资源的流程基本如下:\n通过kubeconfig信息构造Config实例,该实例记录了集群证书,k8s apiserver地址等信息; 根据Config实例携带的信息构建特定的客户端(clientset,dynamicset等); 利用客户端向k8s apiserver发起请求,操作k8s资源。 以下是使用 client-go 获取 pod 的代码清单:\nfunc main() { var kubernetes *string if home := homeDir(); home != \u0026#34;\u0026#34; { kubeconfig := flag.String( \u0026#34;kubeconfig\u0026#34;, filePath.Join(home, \u0026#34;.kube\u0026#34;, \u0026#34;config\u0026#34;), \u0026#34;(optional) absolute path to the kubeconfig file\u0026#34;, ) } else { kubeconfig := flag.String(\u0026#34;kubeconfig\u0026#34;, \u0026#34;\u0026#34;, \u0026#34;absolute path to the kubeconfig file\u0026#34;) } flag.Parse() // use the current context in kubeconfig config, err := clientcmd.BuildConfigFromFlags(\u0026#34;\u0026#34;, *kubeconfig) if err != nil { panic(err.Error()) } // create the clientset clientset, err := kubernetes.NewForConfig(config) if err != nil { panic(err.Error()) } for { pods, err := clientset.CoreV1().Pods(\u0026#34;\u0026#34;).List(context.TODO(), metav1.ListOptions{}) if err != nil { panic(err.Error) } // ... } } func homeDir() string { if h := os.Getenv(\u0026#34;HOME\u0026#34;); h != \u0026#34;\u0026#34; { return h } return os.Getenv(\u0026#34;USERPROFILE\u0026#34;) // windows } 2.2.2 client-go 主体结构 # client-go 支持 4 种与 k8s apiserver 交互的客户端:\nRESTClient # 最基础的客户端,主要对 HTTP 请求进行封装,支持 JSON 和 Protobuf 格式数据;\npackage main import ( \u0026#34;context\u0026#34; \u0026#34;fmt\u0026#34; corev1 \u0026#34;k8s.io/api/core/v1\u0026#34; metav1 \u0026#34;k8s.io/apimachinery/pkg/apis/meta/v1\u0026#34; \u0026#34;k8s.io/client-go/kubernetes/scheme\u0026#34; \u0026#34;k8s.io/client-go/reset\u0026#34; \u0026#34;k8s.io/client-go/tools/clientcmd\u0026#34; ) func main() { // 加载配置文件,生成config对象 config, err := clientcmd.BuildConfigFromFlags(\u0026#34;\u0026#34;, \u0026#34;/root/.kube/config\u0026#34;) if err != nil { panic(err.Error()) } // 配置API路径和请求的GV config.APIPath = \u0026#34;api\u0026#34; config.GroupVersion = \u0026amp;corev1.SchemeGroupVersion // 配置数据的编解码器 config.NegotiatedSerializer = scheme.Codecs // 实例化RESTClient对象 restclient, err := rest.RESTClientFor(config) if err != nil { panic(err.Error()) } result := \u0026amp;corev1.PodList{} err = restclient.Get(). Namespace(\u0026#34;default\u0026#34;). Resource(\u0026#34;pods\u0026#34;). VersionedParams(\u0026amp;metav1.ListOptions{Limit: 100}, scheme.ParameterCodec). Do(context.TODO()).Into(result) if err != nil { panic(err.Error) } for _, d := range result.Items { fmt.Printf( \u0026#34;NAMESPACE: %v \\t NAME: %v \\t STATUS: %v\\n\u0026#34;, d.Namespace, d.Name, d.Status.Phase, ) } } Clientset # k8s 自身内置资源的客户端集合,仅能操作已知类型的内置资源,如 Pod,Service 等;\npackage main DynamicClient # 动态客户端,可以对任意k8s资源执行通用操作,包括CRD。\n通过动态指定GVR操作任意k8s资源,以 map[string]interface{} 结构存储 k8s apiserver 的返回数据,再利用反射机制在运行时进行数据绑定。这种松耦合的方式意味着更高的灵活性,但与此同时,也无法获取强数据类型检查和验证的好处。\n理解DynamicClient,你可能需要对这 Object.runtime 接口和 Unstructured 结构体有所了解:\nObject.runtime:k8s中所有资源都实现了该接口,包括 DeepCopyObject 和 GetObjectKind 方法: Unstructured:包含 DiscoveryClient # 发现客户端,发现apiserver支持的资源组,资源版本和资源信息(GVR),使用kubectl api-versions可以获取支持的资源;\n» 我的第一本算法书\n"},{"id":193,"href":"/reading/%E6%88%91%E7%9A%84%E7%AC%AC%E4%B8%80%E6%9C%AC%E7%AE%97%E6%B3%95%E4%B9%A6/","title":"我的第一本算法书","section":"Reading","content":" 🏠 首页 / 阅读 / 我的第一本算法书\n我的第一本算法书 # 作者:石田保辉,宫崎修一\n1. 算法的基础知识 # 1.1 什么是算法 # 算法是计算机计算和解决问题的步骤。\n选择排序:\nfunc selectionSort(nums []int) []int { for i := 0; i \u0026lt; len(nums); i++ { min := i for j := i + 1; j \u0026lt; len(nums); j++ { if nums[j] \u0026lt; nums[min] { min = j } } nums[i], nums[min] = nums[min], nums[i] } return nums } « 云原生应用开发:Operator原理与实践\n» 深入理解计算机网络.md\n"},{"id":194,"href":"/reading/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/","title":"深入理解计算机网络","section":"Reading","content":" 🏠 首页 / 阅读 / 深入理解计算机网络.md\n二进制数的四种表示方法 # 原码 # 二进制数第一位用来表示正负符号,0 表示 +,1 表示 -。 原码就是带正负符号的二进制数,例如,+3 原码为 00000011,-3 原码为 10000011。 原码表示方法中,0 有 +0 和 -0 表现形式。\n反码 # 补码 # 原码在加减法运算中的不便,\n移码 # « 我的第一本算法书\n"},{"id":195,"href":"/rust/","title":"Rust","section":"","content":" 🏠 首页 / Rust 编程\nRust 编程 # Rust cargo 管理工具\nRust 开发环境配置\nRust 入门\n查看根目录\nRust VSCode 调试\nRust WASM 编程\n"},{"id":196,"href":"/rust/cargo/","title":"Cargo","section":"Rust","content":" 🏠 首页 / Rust 编程 / Rust cargo 管理工具\nRust cargo 管理工具 # cargo 是 Rust 的构建系统和包管理器。\n创建项目 # cargo new hello-world cd hello-world 可以使用 cargo new --vcs git hello-world 创建项目并初始化 git 仓库,它将自动创建一个 .gitignore 文件。\n编译项目 # cargo build # 编译之后将在 target/debug 目录下生成可执行文件 # 可以通过以下命令运行 ./target/debug/hello-world 默认构建模式是 debug,里面包含了大量的符号和调试信息,优化级别不高。建议使用 relase 模式构建发布到生产环境。\nrelease 模式构建花费的时间较长,但是构建出来的二进制文件则要精简很多。\ncargo build --release 运行项目 # cargo run 追踪 panic 位置运行:\nRUST_BACKTRACE=1 cargo run 创建类包 # cargo new --lib mylib 检测项目是否可以编译 # cargo check 安装可执行文件(更新) # cargo install --path . 卸载可执行文件 # cargo uninstall hello-world 发布项目 # 发布到 crates.io,需要注册账号。\n并且,需要在 Cargo.toml 中添加部分内容,例如作者、描述、许可证必要信息以及其他信息:\n[package] name = \u0026#34;hello-world\u0026#34; version = \u0026#34;0.1.0\u0026#34; edition = \u0026#34;2021\u0026#34; authors = [\u0026#34;Pone Ding \u0026lt;poneding@gmail.com\u0026gt;\u0026#34;] description = \u0026#34;A hello world program in Rust\u0026#34; license = \u0026#34;MIT OR Apache-2.0\u0026#34; readme = \u0026#34;README.md\u0026#34; keywords = [\u0026#34;hello\u0026#34;, \u0026#34;world\u0026#34;] categories = [\u0026#34;hello-world\u0026#34;] cargo publish # 忽略未提交的更改 cargo publish --allow-dirty 添加依赖 # cargo add rand cargo add hello-world # 添加本地依赖 cargo add hello-world --path ../hello-world 依赖外部类库以及引用:\nCargo.toml:\n... [dependencies] rand = \u0026#34;0.8.5\u0026#34; main.rs:\nextern crate rand; use rand::Rng; 依赖内部类库以及引用:\nCargo.toml:\n... [dependencies] hello_lib = { path = \u0026#34;../hello_lib\u0026#34; } main.rs:\nextern crate hello_lib; 更新依赖 # # 更新所有依赖 cargo update # 更新指定依赖 cargo update rand 移除依赖 # cargo rm rand 清除编译 # cargo clean 生成文档 # cargo doc 测试 # cargo test workspace # 目前需要手动在 workspace 目录下创建 Cargo.toml 文件。\nvim Cargo.toml [workspace] resolver = \u0026#34;2\u0026#34; members = [ \u0026#34;hello_world\u0026#34;, \u0026#34;hello_lib\u0026#34;, ] 检查 workspace 下编译:\ncargo check --workspace 构建\n# 构建所有工作区成员 cargo build # 构建单个工作区成员 cargo build -p hello_world » Rust 开发环境配置\n"},{"id":197,"href":"/rust/dev-env-config/","title":"Dev Env Config","section":"Rust","content":" 🏠 首页 / Rust 编程 / Rust 开发环境配置\nRust 开发环境配置 # 安装 # Linux \u0026amp; Mac:\ncurl --proto \u0026#39;=https\u0026#39; --tlsv1.2 https://sh.rustup.rs -sSf | sh 一些常用的 Rust 包依赖于 C 代码,因此可能需要额外安装 C 编译器,在 Mac 上通过运行以下命令可以获得 C 编译器:\nxcode-select --install Ubuntu 上通过运行以下命令可以获得 C 编译器:\nsudo apt install build-essential 更新 # rustup update 卸载 # rustup self uninstall 配置命令补全 # 第一种方式,zsh 添加 rust 插件:\nvim ~/.zshrc 找到 plugins 配置位置,追加 rust:\nplugins=(... rust) 第二种方式:\n查看帮助:\nrustup completions --help 以 Ubuntu 为例,创建目录:\nmkdir ~/.zfunc 在 .zshrc 文件中添加内容:\nfpath+=~/.zfunc autoload -Uz compinit \u0026amp;\u0026amp; compinit 生成补全脚本:\nrustup completions zsh \u0026gt; ~/.zfunc/_rustup rustup completions zsh cargo \u0026gt; ~/.zfunc/_cargo 注销重新登录以生效,或者直接运行以下命令:\nexec zsh « Rust cargo 管理工具\n» Rust 入门\n"},{"id":198,"href":"/rust/getting-started/","title":"Getting Started","section":"Rust","content":" 🏠 首页 / Rust 编程 / Rust 入门\nRust 入门 # Rust 是一种系统编程语言,类似于 C 和 C++。它的设计目标是提供安全性和并发性,同时保持高性能。Rust 通过所有权系统来实现这些目标。\n安装 Rust # MacOS,linux 或其他类 Unix 系统用户可以直接在终端中运行以下命令安装 Rust:\ncurl --proto \u0026#39;=https\u0026#39; --tlsv1.2 -sSf https://sh.rustup.rs | sh Windows 用户可以在 Rust 官网 下载安装程序。\nHello, World # 让我们从一个简单的 \u0026ldquo;Hello, World!\u0026rdquo; 程序开始。创建一个新文件 main.rs 并输入以下内容:\nfn main() { println!(\u0026#34;Hello, World!\u0026#34;); } 要运行这个程序,使用 rustc 编译器:\nrustc main.rs \u0026amp;\u0026amp; ./main 执行后,你应该看到输出 Hello, World!。\n« Rust 开发环境配置\n» 查看根目录\n"},{"id":199,"href":"/rust/rust-programming/","title":"Rust Programming","section":"Rust","content":" 🏠 首页 / Rust 编程 / 查看根目录\nRust 编程\n信息 # # 查看根目录 rustc --print sysroot # 二进制程序位置 $(rustc --print sysroot)/bin # 源码位置 $(rustc --print sysroot)/lib/rustlib/src/ String 与 \u0026amp;str # String:字符串\n\u0026amp;str:字符串切片\nlet s: \u0026amp;str = \u0026#34;Hello World!\u0026#34;; let s1 = s.to_string(); let s1 = String::from(s); let s2 = \u0026amp;s1[..]; let s2 = s1.as_ref(); Panic # 设置 RUST_BACKTRACE=1 环境变量值,可以追踪到 panic 位置,例如:\n« Rust 入门\n» Rust VSCode 调试\n"},{"id":200,"href":"/rust/vscode-debugging/","title":"Vscode Debugging","section":"Rust","content":" 🏠 首页 / Rust 编程 / Rust VSCode 调试\nRust VSCode 调试 # 1. 安装插件 # CodeLLDB rust-analyzer 2. 配置 # 项目根目录配置 .vscode/launch.json,调试运行时打开 main.rs 文件。\n{ \u0026#34;version\u0026#34;: \u0026#34;0.2.0\u0026#34;, \u0026#34;configurations\u0026#34;: [ { \u0026#34;type\u0026#34;: \u0026#34;lldb\u0026#34;, \u0026#34;request\u0026#34;: \u0026#34;launch\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Debug Rust Project\u0026#34;, \u0026#34;cargo\u0026#34;: { \u0026#34;args\u0026#34;: [ \u0026#34;build\u0026#34;, \u0026#34;--target-dir=${fileDirname}/../target\u0026#34;, \u0026#34;--manifest-path=${fileDirname}/../Cargo.toml\u0026#34; ] }, \u0026#34;args\u0026#34;: [], \u0026#34;cwd\u0026#34;: \u0026#34;${workspaceFolder}\u0026#34; }, { \u0026#34;type\u0026#34;: \u0026#34;lldb\u0026#34;, \u0026#34;request\u0026#34;: \u0026#34;launch\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;Debug Rust Unit Tests\u0026#34;, \u0026#34;cargo\u0026#34;: { \u0026#34;args\u0026#34;: [ \u0026#34;test\u0026#34;, \u0026#34;--no-run\u0026#34;, \u0026#34;--target-dir=${fileDirname}/../target\u0026#34;, \u0026#34;--manifest-path=${fileDirname}/../Cargo.toml\u0026#34; ] }, \u0026#34;args\u0026#34;: [], \u0026#34;cwd\u0026#34;: \u0026#34;${workspaceFolder}\u0026#34; } ] } 支持 Wrokspace 下多 Rust 项目调试。\n« 查看根目录\n» Rust WASM 编程\n"},{"id":201,"href":"/rust/wasm-programming/","title":"Wasm Programming","section":"Rust","content":" 🏠 首页 / Rust 编程 / Rust WASM 编程\nRust WASM 编程 # 1. 初始化项目 # cargo new hello-wasm cd hello-wasm 2. 安装 wasm-pack # cargo install wasm-pack 3. 编写代码 # 编辑 src/main.rs 文件:\n// 使用 wasm-bindgen 在 Rust 与 JavaScript 之间通信 extern crate wasm_bindgen; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern { pub fn alert(s: \u0026amp;str); } #[wasm_bindgen] pub fn greet(name: \u0026amp;str){ alert(\u0026amp;format!(\u0026#34;Hello, {}!\u0026#34;,name)); } 编辑 Cargo.toml 文件:\n[package] name = \u0026#34;hello-wasm\u0026#34; version = \u0026#34;0.1.0\u0026#34; edition = \u0026#34;2021\u0026#34; authors = [\u0026#34;Pone Ding \u0026lt;poneding@gmail.com\u0026gt;\u0026#34;] description = \u0026#34;A sample project with wasm-pack\u0026#34; license = \u0026#34;MIT/Apache-2.0\u0026#34; repository = \u0026#34;https://github.com/poneding/hello-wasm\u0026#34; [lib] crate-type = [\u0026#34;cdylib\u0026#34;] [dependencies] wasm-bindgen = \u0026#34;0.2\u0026#34; 4. 构建项目 # wasm-pack build --scope [npm-username] « Rust VSCode 调试\n"}] \ No newline at end of file diff --git a/cn.search.min.e455a84312850374a504e30c4b1d27589df914ed87d8397c49c9b6148a70a882.js b/cn.search.min.ad6f19a2caf8c64396b027e6fde472e0bef15c8879cb75e24b5b224891daf6a3.js similarity index 91% rename from cn.search.min.e455a84312850374a504e30c4b1d27589df914ed87d8397c49c9b6148a70a882.js rename to cn.search.min.ad6f19a2caf8c64396b027e6fde472e0bef15c8879cb75e24b5b224891daf6a3.js index db88eb60..ed857d46 100644 --- a/cn.search.min.e455a84312850374a504e30c4b1d27589df914ed87d8397c49c9b6148a70a882.js +++ b/cn.search.min.ad6f19a2caf8c64396b027e6fde472e0bef15c8879cb75e24b5b224891daf6a3.js @@ -1 +1 @@ -"use strict";(function(){const o="/cn.search-data.min.495215f6d4282bbc8a5b922de4710cd6b33ec859629954392204982c5d00fa30.json",i=Object.assign({encode:!1,tokenize:function(e){return e.replace(/[\x00-\x7F]/g,"").split("")}},{includeScore:!0,useExtendedSearch:!0,fieldNormWeight:1.5,threshold:.2,ignoreLocation:!0,keys:[{name:"title",weight:.7},{name:"content",weight:.3}]}),e=document.querySelector("#book-search-input"),t=document.querySelector("#book-search-results");if(!e)return;e.addEventListener("focus",n),e.addEventListener("keyup",s),document.addEventListener("keypress",a);function a(t){if(t.target.value!==void 0)return;if(e===document.activeElement)return;const n=String.fromCharCode(t.charCode);if(!r(n))return;e.focus(),t.preventDefault()}function r(t){const n=e.getAttribute("data-hotkeys")||"";return n.indexOf(t)>=0}function n(){e.removeEventListener("focus",n),e.required=!0,fetch(o).then(e=>e.json()).then(e=>{window.bookSearchIndex=new Fuse(e,i)}).then(()=>e.required=!1).then(s)}function s(){for(;t.firstChild;)t.removeChild(t.firstChild);if(!e.value)return;const n=window.bookSearchIndex.search(e.value).slice(0,10);n.forEach(function(e){const n=c("

  • "),s=n.querySelector("a"),o=n.querySelector("small");s.href=e.item.href,s.textContent=e.item.title,o.textContent=e.item.section,t.appendChild(n)})}function c(e){const t=document.createElement("div");return t.innerHTML=e,t.firstChild}})() \ No newline at end of file +"use strict";(function(){const o="/cn.search-data.min.7eb822a030f71374703a53128fdf74db4a0934550de0457d2e380a5a6833769f.json",i=Object.assign({encode:!1,tokenize:function(e){return e.replace(/[\x00-\x7F]/g,"").split("")}},{includeScore:!0,useExtendedSearch:!0,fieldNormWeight:1.5,threshold:.2,ignoreLocation:!0,keys:[{name:"title",weight:.7},{name:"content",weight:.3}]}),e=document.querySelector("#book-search-input"),t=document.querySelector("#book-search-results");if(!e)return;e.addEventListener("focus",n),e.addEventListener("keyup",s),document.addEventListener("keypress",a);function a(t){if(t.target.value!==void 0)return;if(e===document.activeElement)return;const n=String.fromCharCode(t.charCode);if(!r(n))return;e.focus(),t.preventDefault()}function r(t){const n=e.getAttribute("data-hotkeys")||"";return n.indexOf(t)>=0}function n(){e.removeEventListener("focus",n),e.required=!0,fetch(o).then(e=>e.json()).then(e=>{window.bookSearchIndex=new Fuse(e,i)}).then(()=>e.required=!1).then(s)}function s(){for(;t.firstChild;)t.removeChild(t.firstChild);if(!e.value)return;const n=window.bookSearchIndex.search(e.value).slice(0,10);n.forEach(function(e){const n=c("
  • "),s=n.querySelector("a"),o=n.querySelector("small");s.href=e.item.href,s.textContent=e.item.title,o.textContent=e.item.section,t.appendChild(n)})}function c(e){const t=document.createElement("div");return t.innerHTML=e,t.firstChild}})() \ No newline at end of file diff --git a/cs/index.html b/cs/index.html index 3a77687c..96093a2d 100644 --- a/cs/index.html +++ b/cs/index.html @@ -5,7 +5,7 @@ 计算机科学 # 互联网如何运作? 网络通信 虚拟内存">Cs | 秋河落叶 - +
    Cs

    🏠 首页 / 计算机科学

    计算机科学 diff --git a/cs/internet/index.html b/cs/internet/index.html index aae40915..1b74a888 100644 --- a/cs/internet/index.html +++ b/cs/internet/index.html @@ -33,7 +33,7 @@ 协议在互联网中的作用 # 协议在通过互联网进行通信和数据交换方面发挥着至关重要的作用。协议是一组规则和标准,定义设备和系统之间如何交换信息。 互联网通信中使用许多不同的协议,包括互联网协议 (IP)、传输控制协议 (TCP)、用户数据报协议 (UDP)、域名系统 (DNS) 等。 IP 负责将数据包路由到正确的目的地,而 TCP 和 UDP 则确保数据包可靠且高效地传输。 DNS 用于将域名转换为 IP 地址,HTTP 用于在客户端和服务器之间传输数据。">Internet | 秋河落叶 - +
    Internet

    🏠 首页 / diff --git a/cs/networking/index.html b/cs/networking/index.html index 701c31ea..747467df 100644 --- a/cs/networking/index.html +++ b/cs/networking/index.html @@ -7,7 +7,7 @@ 阻塞 I/O (Blocking I/O) 非阻塞 I/O (Nonblocking I/O) I/O 多路复用 (I/O multiplexing) 信号驱动 I/O (Signal driven I/O) 异步 I/O (Asynchronous I/O) 操作系统上的 I/O 是用户空间和内核空间的数据交互。 « 互联网如何运作? » 虚拟内存">Networking | 秋河落叶 - +

    Networking

    🏠 首页 / diff --git a/cs/virtual-memory/index.html b/cs/virtual-memory/index.html index 6ee2c885..35870d13 100644 --- a/cs/virtual-memory/index.html +++ b/cs/virtual-memory/index.html @@ -15,7 +15,7 @@ 物理内存 虚拟内存 虚拟内存核心原理 # 为每个程序设置一段"连续"的虚拟地址空间,把这个地址空间分割成多个具有连续地址范围的页 (Page),并把这些页和物理内存做映射,在程序运行期间动态映射到物理内存。当程序引用到一段在物理内存的地址空间时,由硬件立刻执行必要的映射;而当程序引用到一段不在物理内存中的地址空间时,由操作系统负责将缺失的部分装入物理内存并重新执行失败的指令。 内存交换(swap) # 在进程运行期间只分配映射当前使用到的内存,暂时不使用的数据则写回磁盘作为副本保存,需要用的时候再读入内存,动态地在磁盘和内存之间交换数据。 参考 # 虚拟内存精粹 « 网络通信'>Virtual Memory | 秋河落叶 - +

    Virtual Memory

    🏠 首页 / diff --git a/dapr/dapr/index.html b/dapr/dapr/index.html index 42316f70..608d13eb 100644 --- a/dapr/dapr/index.html +++ b/dapr/dapr/index.html @@ -19,7 +19,7 @@ 安装cli # https://github.com/dapr/cli 以在linux中安装dapr为例: wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O-|/bin/bash dapr init --runtime-version Service Invocation # 解决微服务之间通信的问题。">Dapr | 秋河落叶 - +

    Dapr

    🏠 首页 / diff --git a/dapr/index.html b/dapr/index.html index d7c62db2..308be119 100644 --- a/dapr/index.html +++ b/dapr/index.html @@ -1,7 +1,7 @@ Dapr | 秋河落叶 - +

    Dapr

    🏠 首页 / Dapr

    Dapr diff --git a/design-pattern/cicd/index.html b/design-pattern/cicd/index.html index e64b6ce0..d69b5109 100644 --- a/design-pattern/cicd/index.html +++ b/design-pattern/cicd/index.html @@ -5,7 +5,7 @@ CI/CD # Concepts # Pipeline # PipelineStage # Fields: Name Type Desc ID string PipelineID string PrevPipelineStageID string Status uint8 Pending, Runing, Success, Failed, Abort Name string PipelineStageTask # Fields: Name Type Desc ID string PipelineStageID string PrevPipelineStageTaskID string Image string Container image task running on. DindRequired boolean Docker in docker? DindImage string dependency DindRequired default: docker:18-dind. Scripts string CacheDirs string EnvironmentVariables string Status string EnvironmentVariables string EnvironmentVariables string ">Cicd | 秋河落叶 - +
    Cicd

    🏠 首页 / diff --git a/design-pattern/index.html b/design-pattern/index.html index 303a684a..7f70fbf7 100644 --- a/design-pattern/index.html +++ b/design-pattern/index.html @@ -1,7 +1,7 @@ Design Pattern | 秋河落叶 - +

    Design Pattern

    🏠 首页 / 设计模式

    设计模式 diff --git a/devops/agile/index.html b/devops/agile/index.html index 0d891aa9..bf711185 100644 --- a/devops/agile/index.html +++ b/devops/agile/index.html @@ -5,7 +5,7 @@ Agile # 敏捷 交付产品可以看作饭店上菜 顾客点了十个菜 后厨把十个菜做完,最后十个菜一起上桌 ——不敏捷 后厨做完一个菜就上一个菜 ——敏捷 敏捷的优点: 做一盘上一盘,顾客早早就能吃上了,优先横扫饥饿;尽早给用户体验上产品; 做一盘上一盘,每个菜都是新出锅,顾客能吃上一口热的;对比十盘菜一起上,可能先炒的菜已经凉了,凉的菜换做成产品的话,可能就是已经过时的功能了,不符合需求了 做一盘上一盘, 如果前面的菜咸了,可以反馈给饭店,后面的菜做淡点;对比十盘菜一起上,顾客就无法从中间反馈意见了,做一个用户插不上意见的产品,严重的后果可能是用户已经不感兴趣了。 » Ansible">Agile | 秋河落叶 - +
    Agile

    🏠 首页 / diff --git a/devops/ansible/index.html b/devops/ansible/index.html index e2a6900e..88518a58 100644 --- a/devops/ansible/index.html +++ b/devops/ansible/index.html @@ -37,7 +37,7 @@ 配置示例 # ​ inventory:主机清单配置文件位置,在使用Ansible命令时,也开始-i 指定; ​ host_key_checking:当know_hosts中不存在的主机(即尚未访问过的主机,是否需要输入密钥); ​ become_user:sudo用户;">Ansible | 秋河落叶 - +

    Ansible

    🏠 首页 / diff --git a/devops/bule-green-rollback-gray/index.html b/devops/bule-green-rollback-gray/index.html index 5eb476a4..3887c1bb 100644 --- a/devops/bule-green-rollback-gray/index.html +++ b/devops/bule-green-rollback-gray/index.html @@ -39,7 +39,7 @@ 同样无需增加服务器,能较为平稳的过渡到新版本,并且当有bug时也能做到快速回滚。 « Ansible » 混沌工程原则 (PRINCIPLES OF CHAOS ENGINEERING)">Bule Green Rollback Gray | 秋河落叶 - +

    Bule Green Rollback Gray

    🏠 首页 / diff --git a/devops/chaos-engineering/index.html b/devops/chaos-engineering/index.html index 49a51a90..965e2733 100644 --- a/devops/chaos-engineering/index.html +++ b/devops/chaos-engineering/index.html @@ -25,7 +25,7 @@ 建立一个围绕稳定状态行为的假说 # 要关注系统的可测量输出, 而不是系统的属性。对这些输出在短时间内的度量构成了系统稳定状态的一个代理。 整个系统的吞吐量、错误率、延迟百分点等都可能是表示稳态行为的指标。 通过在实验中的系统性行为模式上的关注, 混沌工程验证了系统是否正常工作, 而不是试图验证它是如何工作的。 多样化真实世界的事件 # 混沌变量反映了现实世界中的事件。 我们可以通过潜在影响或估计频率排定这些事件的优先级。考虑与硬件故障类似的事件, 如服务器宕机、软件故障 (如错误响应) 和非故障事件 (如流量激增或伸缩事件)。 任何能够破坏稳态的事件都是混沌实验中的一个潜在变量。 在生产环境中运行实验 # 系统的行为会依据环境和流量模式都会有所不同。 由于资源使用率变化的随时可能发生, 因此通过采集实际流量是捕获请求路径的唯一可靠方法。 为了保证系统执行方式的真实性与当前部署系统的相关性, 混沌工程强烈推荐直接采用生产环境流量进行实验。">Chaos Engineering | 秋河落叶 - +

    Chaos Engineering

    🏠 首页 / diff --git a/devops/commercial-canvas/index.html b/devops/commercial-canvas/index.html index eb2363ef..3162fa02 100644 --- a/devops/commercial-canvas/index.html +++ b/devops/commercial-canvas/index.html @@ -27,7 +27,7 @@ 基本认知 # 客户细分 # 客户是商业模式的核心。哪些是重要客户 « 混沌工程原则 (PRINCIPLES OF CHAOS ENGINEERING) » 使用grafana监控5xx服务">Commercial Canvas | 秋河落叶 - +

    Commercial Canvas

    🏠 首页 / diff --git a/devops/grafana-monite-service-with-5xx/index.html b/devops/grafana-monite-service-with-5xx/index.html index cfa40f10..5e5fd468 100644 --- a/devops/grafana-monite-service-with-5xx/index.html +++ b/devops/grafana-monite-service-with-5xx/index.html @@ -31,7 +31,7 @@ 修改查询sql语句,域名修改为要监控的域名或服务名,比如你想监控www.example.com域名下所有服务,那么你可以定制sql如下: SELECT "service_code" FROM "service_status" WHERE ("health_code" = 500 AND "domain_name" = 'www.example.com') AND $timeFilter GROUP BY "service_name" ,当然你可能只想监控某个域名下的其中一个服务,如你想监控www.example.com域名下operationplatgform服务,那么你可以定制sql如下: SELECT "service_code" FROM "service_status" WHERE ("health_code" = 500 AND "domain_name" = 'www.'>Grafana Monite Service With 5xx | 秋河落叶 - +

    Grafana Monite Service With 5xx

    🏠 首页 / diff --git a/devops/grafana-monite-service/index.html b/devops/grafana-monite-service/index.html index 38fde466..f0014638 100644 --- a/devops/grafana-monite-service/index.html +++ b/devops/grafana-monite-service/index.html @@ -15,7 +15,7 @@ 目前数据源已经配置完成,选择Influxdb_Elb_Logs作为QUuery DataSource,并且开始配置query 查询语句可以参考: SELECT mean("service_code") FROM "service_status" WHERE ("domain_name" = 'service.example.com' AND "health_code" = 500) AND $timeFilter GROUP BY time($__interval), "service_name" 根据自身需求修改query即可。'>Grafana Monite Service | 秋河落叶 - +

    Grafana Monite Service

    🏠 首页 / diff --git a/devops/grafana/index.html b/devops/grafana/index.html index 187e1518..38395ab1 100644 --- a/devops/grafana/index.html +++ b/devops/grafana/index.html @@ -29,7 +29,7 @@ 此节可以参考: https://medium.com/@eng.mohamed.m.saeed/monitoring-jenkins-with-grafana-and-prometheus-a7e037cbb376 « 使用Grafana监控service » Jaeger">Grafana | 秋河落叶 - +

    Grafana

    🏠 首页 / diff --git a/devops/index.html b/devops/index.html index 0fac071e..1c36c7af 100644 --- a/devops/index.html +++ b/devops/index.html @@ -19,7 +19,7 @@ Grafana Jaeger nginx">Devops | 秋河落叶 - +

    Devops

    🏠 首页 / DevOps

    DevOps diff --git a/devops/jeager/index.html b/devops/jeager/index.html index 51c29677..c1413c50 100644 --- a/devops/jeager/index.html +++ b/devops/jeager/index.html @@ -33,7 +33,7 @@ OpenTracing # 分布式的追踪系统其实不止Jaeger一种,但是它们的核心原理都大相径庭,都是从入侵到代码中埋点,然后像追踪系统上报数据信息,最终我们在追踪系统得到数据,从而实现追踪分析。 为了兼容统一各追踪系统API,OpenTracing规范诞生了,它与平台无关,与厂商无关。有了它的存在,你可以方便的切换你想使用的追踪系统。 安装 # Docker # docker run -d --name jaeger \ -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \ -p 5775:5775/udp \ -p 6831:6831/udp \ -p 6832:6832/udp \ -p 5778:5778 \ -p 16686:16686 \ -p 14268:14268 \ -p 14250:14250 \ -p 9411:9411 \ jaegertracing/all-in-one:1.">Jeager | 秋河落叶 - +
    Jeager

    🏠 首页 / diff --git a/devops/nginx/index.html b/devops/nginx/index.html index 7d3b2686..177b3039 100644 --- a/devops/nginx/index.html +++ b/devops/nginx/index.html @@ -11,7 +11,7 @@ 配置location: localtion [ = | ~ | ~* | ^~] uri { } =:用于不包含正则表达式的url前,要求请求字符串与uri严格匹配; ~:用于表示uri包含正则表达式,并且区分大小写; ~*:用于表示uri包含正则表达式,并且不区分大小写; ^~:用于不包含正则表达式的uri前,要求nginx服务器找到表示ui和请求字符串匹配度最高的location后,立即使用此location处理请求 配置负载均衡 # 将负载分摊到不同的服务单元,保证服务的快速响应,高可用。 upstream myserver { server 192.168.0.1:8081; server 192.168.0.2:8082; } server { listen 80; server_name 192.168.0.1; location / { proxy_pass http://myserver; root html; index index.html index.htm; } } 均衡策略:">Nginx | 秋河落叶 - +

    Nginx

    🏠 首页 / diff --git a/docker/container-diff/index.html b/docker/container-diff/index.html index 8cd893e1..b9f7ba02 100644 --- a/docker/container-diff/index.html +++ b/docker/container-diff/index.html @@ -5,7 +5,7 @@ container-diff 工具的使用 # 简介 # container-diff 是 google 开源的一款用于分析和比较 Docker 镜像的工具,它可以从多个维度分析一个或者比较两个容器镜像: 镜像构建历史 镜像文件系统 镜像大小 软件包管理 项目地址: https://github.com/GoogleContainerTools/container-diff 安装 # macOS # curl -LO https://storage.googleapis.com/container-diff/latest/container-diff-darwin-amd64 && chmod +x container-diff-darwin-amd64 && sudo mv container-diff-darwin-amd64 /usr/local/bin/container-diff Linux # curl -LO https://storage.googleapis.com/container-diff/latest/container-diff-linux-amd64 && chmod +x container-diff-linux-amd64 && sudo mv container-diff-linux-amd64 /usr/local/bin/container-diff # or curl -LO https://storage.googleapis.com/container-diff/latest/container-diff-linux-amd64 && chmod +x container-diff-linux-amd64 && mkdir -p $HOME/bin && export PATH=$PATH:$HOME/bin && mv container-diff-linux-amd64 $HOME/bin/container-diff Windows # 下载地址: https://storage.">Container Diff | 秋河落叶 - +

    Container Diff

    🏠 首页 / diff --git a/docker/dind/index.html b/docker/dind/index.html index 5cea5ec9..ced78a7f 100644 --- a/docker/dind/index.html +++ b/docker/dind/index.html @@ -13,7 +13,7 @@ 1. 挂载主机 /var/run/docker.sock # Docker 容器: docker run -v /var/run/docker.sock:/var/run/docker.sock --name docker-in-docker -it docker 在运行起来的容器中使用docker: $ docker run -v /var/run/docker.sock:/var/run/docker.sock --name docker-in-docker -it docker / # docker run hello-world Hello from Docker! This message shows that your installation appears to be working correctly.">Dind | 秋河落叶 - +

    Dind

    🏠 首页 / diff --git a/docker/docker-buildx/index.html b/docker/docker-buildx/index.html index 43614f84..47203003 100644 --- a/docker/docker-buildx/index.html +++ b/docker/docker-buildx/index.html @@ -1,7 +1,7 @@ Docker Buildx | 秋河落叶 - +

    Docker Buildx

    🏠 首页 / diff --git a/docker/docker-commands/index.html b/docker/docker-commands/index.html index c51e467c..6484919e 100644 --- a/docker/docker-commands/index.html +++ b/docker/docker-commands/index.html @@ -15,7 +15,7 @@ -it:i-与容器交互,t-终端 以root权限进入容器 # sudo docker exec -it -u root nginx bash 让容器一直睡眠 # 使用 curlimages/curl 镜像,并让其一直睡眠。 docker run -d --name sleep curlimages/curl sleep infinity 操作镜像命令 # 查看镜像 # sudo docker images 删除镜像 # sudo docker rmi # or sudo docker image rm 删除所有镜像 # sudo docker rmi $(docker images -q) 清除未使用镜像 # sudo docker image prune # or sudo docker rmi $(sudo docker images | grep "^" | awk "{print $3}") 模糊清除镜像 # docker rmi $(docker images | grep 'query' | awk '{print $3}') 操作容器命令 # 查看已经退出的容器 # sudo docker ps -a | grep Exited 清理已经退出的容器 # sudo docker rm $(sudo docker ps -qf status=exited) # or sudo docker rm `sudo docker ps -a | grep Exited | awk '{print $1}'` 清除所有容器 # 使用 -f 参数才能清除所有容器,不使用则只会清理已经退出的容器">Docker Commands | 秋河落叶 - +

    Docker Commands

    🏠 首页 / diff --git a/docker/docker-compose-practice/index.html b/docker/docker-compose-practice/index.html index d349119f..60e5842d 100644 --- a/docker/docker-compose-practice/index.html +++ b/docker/docker-compose-practice/index.html @@ -19,7 +19,7 @@ docker-compose start docker-compose restart 退出 docker-compose down 使用 docker-compose -h 查看更多命令及参数。 实践 # 使用 Docker Compose 运行一个简单的 golang web 程序。">Docker Compose Practice | 秋河落叶 - +

    Docker Compose Practice

    🏠 首页 / diff --git a/docker/docker-container-install-pfx-cert/index.html b/docker/docker-container-install-pfx-cert/index.html index d4418496..2f334834 100644 --- a/docker/docker-container-install-pfx-cert/index.html +++ b/docker/docker-container-install-pfx-cert/index.html @@ -9,7 +9,7 @@ using (var certificate = new X509Certificate2(pfxFileBytes, pfxPassword, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet)) using (var store = new X509Store(storeName, storeLocation, OpenFlags.ReadWrite)) { store.Add(certificate); store.Close(); } Dockerfile 中编写 # 使用 dotnet-certificate-tool 工具安装 pfx 证书。 首先获取到 Pfx 文件的 Thumbprint,这在 dotnet-certificate-tool 命令中作为参数被使用。 使用 Powershell Get-PfxCertificate 函数获取 Thumbprint Get-PfxCertificate -FilePath C:\Pfx\Hello-to-World.">Docker Container Install Pfx Cert | 秋河落叶 - +

    Docker Container Install Pfx Cert

    🏠 首页 / diff --git a/docker/docker-copy-between-host-container/index.html b/docker/docker-copy-between-host-container/index.html index 505c4b0a..b2a223e2 100644 --- a/docker/docker-copy-between-host-container/index.html +++ b/docker/docker-copy-between-host-container/index.html @@ -13,7 +13,7 @@ sudo docker cp b3e608e28f21:/app/appsettings.json ~/temp/appsettings.json # 不指定文件名亦可,默认使用原文件名 sudo docker cp b3e608e28f21:/app/appsettings.json ~/temp/ 2. 将 Host 文件拷贝至 Docker 容器 # 同样,需要先获取容器的 Container ID 或 Name; 使用以下命令将文件拷贝至容器内 sudo docker cp [HOST_PATH] [CONTAINER ID/NAME]:[CONTAINER_PATH] 例如我需要将宿主机的 ~/temp/hello.">Docker Copy Between Host Container | 秋河落叶 - +

    Docker Copy Between Host Container

    🏠 首页 / diff --git a/docker/docker-manifest-build-cross-arch-image/index.html b/docker/docker-manifest-build-cross-arch-image/index.html index 2dd04bf8..7195a4a0 100644 --- a/docker/docker-manifest-build-cross-arch-image/index.html +++ b/docker/docker-manifest-build-cross-arch-image/index.html @@ -5,7 +5,7 @@ 使用 docker manifest 命令构建多架构镜像 # # 创建 docker manifest create poneding/myimage:v1 poneding/myimage-amd64:v1 poneding/myimage-arm64:v1 # 注解 docker manifest annotate poneding/myimage:v1 poneding/myimage-amd64:v1 --arch amd64 docker manifest annotate poneding/myimage:v1 poneding/asmyimageh-arm64:v1 --arch arm64 # 检查 docker manifest inspect poneding/myimage:v1 # 推送 docker manifest push poneding/myimage:v1 在 x86 机器上构建 arm64 镜像 docker run --rm --privileged multiarch/qemu-user-static --reset --persistent yes « Docker 主机容器互拷贝文件 » 理解 docker run –link">Docker Manifest Build Cross Arch Image | 秋河落叶 - +

    Docker Manifest Build Cross Arch Image

    🏠 首页 / diff --git a/docker/docker-run-link/index.html b/docker/docker-run-link/index.html index 159cdcd8..821cbe51 100644 --- a/docker/docker-run-link/index.html +++ b/docker/docker-run-link/index.html @@ -5,7 +5,7 @@ 理解 docker run –link # 使用方式 # # 前提已经存在一个 container2 在运行 docker run img1 --name container1 --link container2 作用 # container1 连接 container2,达到: 与 container2 直接通信 获取 container2 的环境变量 « 使用 docker manifest 命令构建多架构镜像 » Docker 可视化工具 Kitematic">Docker Run Link | 秋河落叶 - +

    Docker Run Link

    🏠 首页 / diff --git a/docker/docker-visiable-tool-kitematic/index.html b/docker/docker-visiable-tool-kitematic/index.html index 57466c9a..f8458a69 100644 --- a/docker/docker-visiable-tool-kitematic/index.html +++ b/docker/docker-visiable-tool-kitematic/index.html @@ -11,7 +11,7 @@ 将当前用户加入到 docker 组: sudo usermod -aG docker $USER # 重启 docker sudo systemctl restart docker sudo chmod a+rw /var/run/docker.sock 完成上面操作后,重启主机,应该就可以使用 Kitamatic 了。 使用 Kitematic # 第一次启动 Kitematic,需要登录 docker 账号,登录完成后,界面如下。">Docker Visiable Tool Kitematic | 秋河落叶 - +

    Docker Visiable Tool Kitematic

    🏠 首页 / diff --git a/docker/dockerfile/index.html b/docker/dockerfile/index.html index 8d515fa6..c7f7923c 100644 --- a/docker/dockerfile/index.html +++ b/docker/dockerfile/index.html @@ -15,7 +15,7 @@ ARG USERNAME FROM alpine RUN echo hello, ${USERNAME} FROM alpine RUN echo hi, ${USERNAME} CMD # CMD 指令的目的是为一个可执行容器提供初始运行命令或运行参数。 CMD 指令有三种形式: 可执行命令 + 命令参数列表,推荐使用 CMD ["executable","param1","param2"] 命令参数列表,作为 ENTRYPOINT 的参数 CMD ["param1","param2"] Shell 形式,字符串形式的命令 CMD command param1 param2 单个 build stage 只允许存在一个 CMD 指令,如果存在多个 CMD 指令,只有最后一个 CMD 指令生效。'>Dockerfile | 秋河落叶 - +

    Dockerfile

    🏠 首页 / diff --git a/docker/index.html b/docker/index.html index ce8b1731..c71ad5ac 100644 --- a/docker/index.html +++ b/docker/index.html @@ -27,7 +27,7 @@ Linux 容器 非 root 账号获取 docker 权限 some-apps.md">Docker | 秋河落叶 - +

    Docker

    🏠 首页 / Docker

    Docker diff --git a/docker/linux-container/index.html b/docker/linux-container/index.html index 677c936a..2314cdb4 100644 --- a/docker/linux-container/index.html +++ b/docker/linux-container/index.html @@ -9,7 +9,7 @@ 容器镜像 # 联合文件系统 # 允许文件存放在不同的层级上,但是最终可以通过统一的视图查看到这些层级的所有文件。 cgroup # namespace # mount:文件系统隔离 uts:hostname domain pid:1号进程 network user ipc:进程间通信 cgroup « Dockerfile » 非 root 账号获取 docker 权限">Linux Container | 秋河落叶 - +
    Linux Container

    🏠 首页 / diff --git a/docker/non-root-account-get-docker-permission/index.html b/docker/non-root-account-get-docker-permission/index.html index 0e10c90e..218a2585 100644 --- a/docker/non-root-account-get-docker-permission/index.html +++ b/docker/non-root-account-get-docker-permission/index.html @@ -13,7 +13,7 @@ sudo chmod a+rw /var/run/docker.sock 快去试试吧。 « Linux 容器 » some-apps.md">Non Root Account Get Docker Permission | 秋河落叶 - +

    Non Root Account Get Docker Permission

    🏠 首页 / diff --git a/docker/some-apps/index.html b/docker/some-apps/index.html index d026f7f4..9063f85a 100644 --- a/docker/some-apps/index.html +++ b/docker/some-apps/index.html @@ -5,7 +5,7 @@ Docker 应用 Cloudreve # 项目地址: https://github.com/cloudreve/Cloudreve docker run -d --name cloudreve \ -p 5212:5212 \ --mount type=bind,source=/root/apps/cloudreve/conf.ini,target=/cloudreve/conf.ini \ --mount type=bind,source=/root/apps/cloudreve/cloudreve.db,target=/cloudreve/cloudreve.db \ -v /root/apps/cloudreve/uploads:/cloudreve/uploads \ -v /root/apps/cloudreve/avatar:/cloudreve/avatar \ cloudreve/cloudreve:latest Etcd # docker run -d --name etcd \ -p 12379:2379 \ -p 12380:2380 \ -e ALLOW_NONE_AUTHENTICATION=yes \ -e ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379 \ -e ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379,http://0.0.0.0:2379 \ -v /root/apps/etcd/data:/var/run/etcd \ quay.io/coreos/etcd:v3.5.6 Minio # docker run -d --name minio \ -p 9000:9000 \ -p 9001:9001 \ -e MINIO_ROOT_USER=minio \ -e MINIO_ROOT_PASSWORD='pd1n9@1024' \ -v /root/apps/minio/data:/data \ quay.">Some Apps | 秋河落叶 - +

    Some Apps

    🏠 首页 / diff --git a/ebpf/ebpf/index.html b/ebpf/ebpf/index.html index e04345da..94f6d46b 100644 --- a/ebpf/ebpf/index.html +++ b/ebpf/ebpf/index.html @@ -19,7 +19,7 @@ 如果不存在更高层次的抽象,则需要直接编写程序。Linux 内核期望 eBPF 程序以字节码的形式加载。虽然直接编写字节码当然是可能的,但更常见的开发实践是利用像 LLVM 这样的编译器套件将伪 c 代码编译成 eBPF 字节码。 安全性 # 由于 eBPF 允许我们在内核中运行任意代码,需要有一种机制来确保它的安全运行,不会使用户的机器崩溃,也不会损害他们的数据。这个机制就是 eBPF 验证器。 验证器对 eBPF 程序进行分析,以确保无论输入什么,它都会在一定数量的指令内安全地终止。">Ebpf | 秋河落叶 - +

    Ebpf

    🏠 首页 / diff --git a/ebpf/index.html b/ebpf/index.html index ad7b76df..c41536bd 100644 --- a/ebpf/index.html +++ b/ebpf/index.html @@ -1,7 +1,7 @@ Ebpf | 秋河落叶 - +

    Ebpf

    🏠 首页 / EBPF

    EBPF diff --git a/front-end/build-blog-site/index.html b/front-end/build-blog-site/index.html index 1040d0fc..753134b0 100644 --- a/front-end/build-blog-site/index.html +++ b/front-end/build-blog-site/index.html @@ -6,11 +6,11 @@ 搭建博客站点 # 1. Hugo 搭建博客 # Hugo 是一个用 Go 语言编写的静态网站生成器。Hugo 的速度非常快,因为它是一个独立的二进制文件,不需要任何运行时依赖。Hugo 的主要特点是速度快、易于安装、易于使用、易于定制。 1.1 安装 Hugo # 参考: https://gohugo.io/installation 1.2 创建博客 # hugo new site blog --format yaml cd blog git init 1.3 选择主题 # 使用 hugo-book 主题。 -git submodule add https://github.com/alex-shpak/hugo-book themes/hugo-book 2. 定制 # 2.1 配置 hugo.yaml # # hugo server --minify --themesDir ../.. --baseURL=http://0.0.0.0:1313/theme/hugo-book/ baseURL: https://blog.poneding.com/ title: 秋河落叶 theme: hugo-book pluralizeListTitles: false defaultContentLanguage: cn # Book configuration disablePathToLower: true enableGitInfo: true # Needed for mermaid/katex shortcodes markup: tableOfContents: startLevel: 2 endLevel: 3 # ordered: true highlight: noClasses: false # style: monokai menu: after: - name: "🔗 GitHub" url: "https://github.'>Build Blog Site | 秋河落叶 - +git submodule add https://github.com/alex-shpak/hugo-book themes/hugo-book 2. 定制 # 2.1 配置 hugo.yaml # # hugo server --minify --themesDir ../.. --baseURL=http://0.0.0.0:1313/theme/hugo-book/ baseURL: https://blog.poneding.com/ title: 秋河落叶 theme: hugo-book pluralizeListTitles: false defaultContentLanguage: cn # Book configuration disablePathToLower: true enableGitInfo: true # Needed for mermaid/katex shortcodes markup: tableOfContents: startLevel: 2 endLevel: 3 # ordered: true highlight: noClasses: false # style: monokai menu: after: - name: "🔗 GitHub" url: "https://github.'>Build Blog Site | 秋河落叶 +

    🏠 首页 / 前端技术 / 搭建博客站点

    搭建博客站点 #

    1. Hugo 搭建博客 #

    Hugo 是一个用 Go 语言编写的静态网站生成器。Hugo 的速度非常快,因为它是一个独立的二进制文件,不需要任何运行时依赖。Hugo 的主要特点是速度快、易于安装、易于使用、易于定制。

    1.1 安装 Hugo @@ -88,8 +88,8 @@ --- - **🔗 外链** -

    2.2 配置 giscus 评论 -#

    拷贝 hugo-booklayouts/_default/baseof.html 文件到 layouts/_default/baseof.html,命令操作如下:

    mkdir -p layouts/_default
    +

    2.3 配置 giscus 评论 +#

    拷贝 hugo-booklayouts/_default/baseof.html 文件到 layouts/_default/baseof.html,命令操作如下:

    mkdir -p layouts/_default
     cp themes/hugo-book/layouts/_default/baseof.html layouts/_default/baseof.html
     

    通过配置 giscus 获取 js 脚本代码,参考: https://giscus.app

    获取到的 js 脚本代码,在 layouts/_default/baseof.html 文件找到 {{- partial "docs/comments" . -}} 所在行,在其下一行添加 js 脚本代码,最终代码如下:

    ...
    @@ -115,8 +115,8 @@
         <!-- end giscus -->
       </div>
     ...
    -

    2.3 代码主题自动切换 -#

    生成代码高亮样式文件,命令操作如下:

    mkdir -p static/css
    +

    2.4 代码主题自动切换 +#

    生成代码高亮样式文件,命令操作如下:

    mkdir -p static/css
     
     # light
     echo "@media (prefers-color-scheme: light) {"  > static/css/syntax.css
    @@ -132,8 +132,8 @@
     
     # 引入样式文件
     echo '<link rel="stylesheet" href="/css/syntax.css">' >> layouts/partials/docs/html-head.html
    -

    将 logo.png 图片放到 static 目录下。

    3. 部署 +

    将 logo.png 图片放到 static 目录下。

    3. 部署 #

    3.1 自定义域名 #

    我们已经在 hugo.yaml 中配置了 baseURL: https://blog.poneding.com/,我们还要创建一个 CNAME 文件,内容为 blog.poneding.com,然后将该文件放到 static 目录下。

    echo "blog.poneding.com" > static/CNAME
     

    3.2 使用 GitHub Actions 自动部署 @@ -185,6 +185,6 @@ git push -u origin master

    3.4 GitHub Pages 配置 #

    在 GitHub 仓库的 Settings -> Pages 中配置 SourceDeploy from a branch, Branchgh-pages 分支,root/

    4. SEO 配置 -#

    参考:


    » Pinia 入门

    参考:


    » Pinia 入门

    \ No newline at end of file +编辑本页
    \ No newline at end of file diff --git a/front-end/index.html b/front-end/index.html index 4681d670..72e51dfd 100644 --- a/front-end/index.html +++ b/front-end/index.html @@ -7,7 +7,7 @@ Pinia 入门 VitePress 认识Vue3">Front End | 秋河落叶 - +
    Front End

    🏠 首页 / 前端技术

    前端技术 diff --git a/front-end/pinia/index.html b/front-end/pinia/index.html index 053bda4e..9d3d4dea 100644 --- a/front-end/pinia/index.html +++ b/front-end/pinia/index.html @@ -7,7 +7,7 @@ 提供更加简单的API (去掉了 mutation ) 提供符合组合式API风格的API (和 Vue3 新语法统一) 去掉了modules的概念,每一个store都是一个独立的模块 搭配 TypeScript 一起使用提供可靠的类型推断 创建空Vue项目并安装Pinia # 1. 创建空Vue项目 # npm init vue@latest 2. 安装Pinia并注册 # npm i pinia import { createPinia } from 'pinia' const app = createApp(App) // 以插件的形式注册 app.use(createPinia()) app.use(router) app.mount('#app') 实现counter # 核心步骤: 定义store 组件使用store 1- 定义store import { defineStore } from 'pinia' import { ref } from 'vue' export const useCounterStore = defineStore('counter', ()=>{ // 数据 (state) const count = ref(0) // 修改数据的方法 (action) const increment = ()=>{ count.">Pinia | 秋河落叶 - +
    Pinia

    🏠 首页 / diff --git a/front-end/vitepress/index.html b/front-end/vitepress/index.html index 631c5813..0902b957 100644 --- a/front-end/vitepress/index.html +++ b/front-end/vitepress/index.html @@ -3,7 +3,7 @@ # 构建 VitePress 站点并将其部署到 GitHub Pages 的示例工作流程 # name: Deploy VitePress site to Pages on: # 在针对 `main` 分支的推送上运行。如果你 # 使用 `master` 分支作为默认分支,请将其更改为 `master` push: branches: [master] # 允许你从 Actions 选项卡手动运行此工作流程 workflow_dispatch: # 设置 GITHUB_TOKEN 的权限,以允许部署到 GitHub Pages permissions: contents: read pages: write id-token: write # 只允许同时进行一次部署,跳过正在运行和最新队列之间的运行队列 # 但是,不要取消正在进行的运行,因为我们希望允许这些生产部署完成 concurrency: group: pages cancel-in-progress: false jobs: # 构建工作 build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 # 如果未启用 lastUpdated,则不需要 # - uses: pnpm/action-setup@v3 # 如果使用 pnpm,请取消注释 # - uses: oven-sh/setup-bun@v1 # 如果使用 Bun,请取消注释 - name: Setup Node uses: actions/setup-node@v4 with: node-version: 20 cache: npm # 或 pnpm / yarn - name: Setup Pages uses: actions/configure-pages@v4 - name: Install dependencies run: npm ci # 或 pnpm install / yarn install / bun install - name: Build with VitePress run: npm run docs:build # 或 pnpm docs:build / yarn docs:build / bun run docs:build - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: path: .">Vitepress | 秋河落叶 - +

    Vitepress

    🏠 首页 / diff --git a/front-end/vue3/index.html b/front-end/vue3/index.html index 62f29678..6634d9d2 100644 --- a/front-end/vue3/index.html +++ b/front-end/vue3/index.html @@ -7,7 +7,7 @@ 特点: 代码量变少 分散式维护变成集中式维护 2. Vue3更多的优势 # 使用create-vue搭建Vue3项目 # 1. 认识create-vue # create-vue是Vue官方新的脚手架工具,底层切换到了 vite (下一代前端工具链),为开发提供极速响应 2. 使用create-vue创建项目 # 前置条件 - 已安装16.">Vue3 | 秋河落叶 - +

    Vue3

    🏠 首页 / diff --git a/git/common-usage/index.html b/git/common-usage/index.html index a62d6878..e8fe484f 100644 --- a/git/common-usage/index.html +++ b/git/common-usage/index.html @@ -9,7 +9,7 @@ # 生成 SSH 密钥 ssh-keygen -t rsa -C poneding@gmail.com # 查看 SSH 密钥,复制到 GitHub/GitLab 等 SSH Keys 中 cat ~/.ssh/id_rsa.pub 添加 .ssh/config 文件,配置 SSH 密钥的别名,方便管理多个 SSH 密钥。 vim ~/.ssh/config 以 GitHub 为例,配置如下: # GitHub Host github.com HostName github.com IdentityFile ~/.ssh/id_rsa 配置用户名和邮箱 # 提交代码时,需要配置用户名和邮箱。">Common Usage | 秋河落叶 - +

    Common Usage

    🏠 首页 / diff --git a/git/git-secret/index.html b/git/git-secret/index.html index 4fd2998d..7eebe5f2 100644 --- a/git/git-secret/index.html +++ b/git/git-secret/index.html @@ -19,7 +19,7 @@ git-secret 使用 # 假设我现在有一个仓库 git-secret-demo,仓库下有一个包含敏感信息的文件 secret.json: 我现在想做的是使用 git-secret 将 secret.json 文件加密。 首先得安装 gpg 工具 # Debian & Ubuntu sudo apt install gnupg -y ​ # Macos brew install gnupg 本地创建 gpg RSA 密钥对 gpg --gen-key 在创建时需要输入自己的用户名和邮箱,并且需要输入你的加密密码。">Git Secret | 秋河落叶 - +

    Git Secret

    🏠 首页 / diff --git a/git/github-action-best-practice/index.html b/git/github-action-best-practice/index.html index 08193b74..29d587f6 100644 --- a/git/github-action-best-practice/index.html +++ b/git/github-action-best-practice/index.html @@ -5,7 +5,7 @@ Github Action 使用最佳实践 # Commit 构建 beta 版本镜像 # 仓库根目录下创建 .github/workflows/commit-cicd.yml 文件,用于提交代码触发 github action。 beta 版本的镜像 tag 命名规则:{vx.x.x}-beta-{COMMIT_ID},例如:v1.0.0-beta-f37cfa2 name: commit-cicd ​ env: BASE_VERSION: v1.0.0 ​ on: push: branches: [main] workflow_dispatch: ​ jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 ​ - name: Set ENV run: | echo "VERSION=${BASE_VERSION}-beta-${GITHUB_SHA::7}" >> $GITHUB_ENV ​ - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 ​ - name: Login to docker hub uses: docker/login-action@v2 with: username: poneding password: ${{ secrets.'>Github Action Best Practice | 秋河落叶 - +

    Github Action Best Practice

    🏠 首页 / diff --git a/git/github-host-helm-chart/index.html b/git/github-host-helm-chart/index.html index 38977862..1e6bdd39 100644 --- a/git/github-host-helm-chart/index.html +++ b/git/github-host-helm-chart/index.html @@ -3,7 +3,7 @@ Helm | Chart Releaser Action to Automate GitHub Page Charts 创建 GitHub 仓库,例如:helm-charts,克隆到本地。 git clone git@github.com:[gh_id]/helm-charts.git cd helm-charts 创建干净的 gh-pages 分支。 git checkout --orphan gh-pages git rm -rf . vim README.md # helm-charts ## Usage [Helm](https://helm.sh) must be installed to use the charts. Please refer to Helm's [documentation](https://helm.sh/docs) to get started. Once Helm has been set up correctly, add the repo as follows: ```bash helm repo add mycharts https://[gh_id].">Github Host Helm Chart | 秋河落叶 - +

    Github Host Helm Chart

    🏠 首页 / diff --git a/git/github-hosting-helm-reop/index.html b/git/github-hosting-helm-reop/index.html index fa97a409..c0d2b229 100644 --- a/git/github-hosting-helm-reop/index.html +++ b/git/github-hosting-helm-reop/index.html @@ -5,7 +5,7 @@ GitHub 托管 helm-chart 仓库 # 创建 GitHub 仓库 # 创建 GitHub helm charts 仓库,例如:helm-charts,克隆到本地。 git clone git@github.com:poneding/helm-charts.git cd helm-charts 创建 gh-pages 孤立分支 # git checkout --orphan gh-pages git rm -rf . vim README.md 编写 README.md 文件,例如: # helm-charts ## Usage [Helm](https://helm.sh) must be installed to use the charts. Please refer to Helm's [documentation](https://helm.sh/docs) to get started. Once Helm has been set up correctly, add the repo as follows: ```bash helm repo add poneding https://poneding.">Github Hosting Helm Reop | 秋河落叶 - +

    Github Hosting Helm Reop

    🏠 首页 / diff --git a/git/github/index.html b/git/github/index.html index a642f8a4..2f02e4a6 100644 --- a/git/github/index.html +++ b/git/github/index.html @@ -9,7 +9,7 @@ curl -s https://api.github.com/repos/ketches/registry-proxy/releases/latest | jq -r .tag_name 方法二: basename $(curl -s -w %{redirect_url} https://github.com/ketches/registry-proxy/releases/latest) « GitHub 托管 helm-chart 仓库 » Gitlab 添加 K8s 集群">Github | 秋河落叶 - +

    Github

    🏠 首页 / diff --git a/git/gitlab-intergrate-k8s/index.html b/git/gitlab-intergrate-k8s/index.html index 4a451587..3655d3b0 100644 --- a/git/gitlab-intergrate-k8s/index.html +++ b/git/gitlab-intergrate-k8s/index.html @@ -17,7 +17,7 @@ 运行以下命令得到输出值: kubectl cluster-info | grep 'Kubernetes master' | awk '/http/ {print $NF}' 获取 CA Certificate: 运行以下命令得到输出值:">Gitlab Intergrate K8s | 秋河落叶 - +

    Gitlab Intergrate K8s

    🏠 首页 / diff --git a/git/gitlab-upgrade-cross-version/index.html b/git/gitlab-upgrade-cross-version/index.html index e510c712..59b3e215 100644 --- a/git/gitlab-upgrade-cross-version/index.html +++ b/git/gitlab-upgrade-cross-version/index.html @@ -13,7 +13,7 @@ 目的 # 实现 gitlab 版本:11.2.3 到 13.0.0 版本的升级,我选择的升级路线是:11.2.3 => 11.11.8 => 12.0.12 => 12.10.6 => 13.0.0 => 13.1.2 我当前创建 gitlab 容器的脚本如下: sudo docker run --detach \ --hostname gitlab.example.com \ --publish 8443:443 --publish 8080:80 --publish 8022:22 \ --name gitlab \ --restart always \ --volume /home/ubuntu/Apps/gitlab/etc/gitlab:/etc/gitlab \ --volume /home/ubuntu/Apps/gitlab/var/log/gitlab/logs:/var/log/gitlab \ --volume /home/ubuntu/Apps/gitlab/var/opt/gitlab:/var/opt/gitlab \ gitlab/gitlab-ce:11.">Gitlab Upgrade Cross Version | 秋河落叶 - +

    Gitlab Upgrade Cross Version

    🏠 首页 / diff --git a/git/index.html b/git/index.html index 2bfdd559..8cd72125 100644 --- a/git/index.html +++ b/git/index.html @@ -19,7 +19,7 @@ Gitlab 跨版本升级 多 GitHub 账号管理 搭建最简单的 git 仓库服务">Git | 秋河落叶 - +

    Git

    🏠 首页 / Git

    Git diff --git a/git/multi-github-account-management/index.html b/git/multi-github-account-management/index.html index 5cac5845..ee0f2367 100644 --- a/git/multi-github-account-management/index.html +++ b/git/multi-github-account-management/index.html @@ -11,7 +11,7 @@ ~/.gitconfig [user] name = poneding email = poneding@gmail.com [includeIf "gitdir:~/src/workspace/"] path = ~/src/workspace/.gitconfig [url "git@github-workspace"] insteadOf = git@github.com [pull] rebase = false [init] defaultBranch = master [core] excludesfile = ~/.gitignore_global ~/src/workspace/.gitconfig [user] name = dingpeng24001 email = dingpeng24001@talkweb.com.cn [url "git@github-workspace"] insteadOf = git@github.com [pull] rebase = false [init] defaultBranch = master [core] excludesfile = ~/.'>Multi Github Account Management | 秋河落叶 - +
    Multi Github Account Management

    🏠 首页 / diff --git a/git/simplest-git-server/index.html b/git/simplest-git-server/index.html index 12b6a130..06aabd58 100644 --- a/git/simplest-git-server/index.html +++ b/git/simplest-git-server/index.html @@ -7,7 +7,7 @@ git init --bare git-server-demo.git 其实也可以直接在终端创建,但是你首先要可以能够通过 ssh 的方式连接远端,例如远端 IP 是 192.168.10.24 ssh root@192.168.10.24 git init --bare git-server-demo.git 执行完命令之后,将在远端目标目录下生成 git-server-demo 目录,子目录结构如下: tree git-server-demo.git git-server-demo.git ├── branches ├── config ├── description ├── HEAD ├── hooks │ ├── applypatch-msg.sample │ ├── commit-msg.sample │ ├── fsmonitor-watchman.sample │ ├── post-update.sample │ ├── pre-applypatch.sample │ ├── pre-commit.sample │ ├── pre-merge-commit.sample │ ├── prepare-commit-msg.">Simplest Git Server | 秋河落叶 - +

    Simplest Git Server

    🏠 首页 / diff --git a/go/dev-env-config/index.html b/go/dev-env-config/index.html index 581bb2f5..47518e71 100644 --- a/go/dev-env-config/index.html +++ b/go/dev-env-config/index.html @@ -7,7 +7,7 @@ go install github.com/spf13/cobra-cli@latest 自动补全: cobra-cli completion zsh > .zfunc/_cobra-cli 在 .zshrc 文件中添加内容(如果已添加,则忽略): fpath+=~/.zfunc autoload -Uz compinit && compinit » Golang 函数可选参数模式">Dev Env Config | 秋河落叶 - +

    Dev Env Config

    🏠 首页 / diff --git a/go/function-optional-pattern/index.html b/go/function-optional-pattern/index.html index 84a9eb9d..184e7bfa 100644 --- a/go/function-optional-pattern/index.html +++ b/go/function-optional-pattern/index.html @@ -1,7 +1,7 @@ Function Optional Pattern | 秋河落叶 - +

    Function Optional Pattern

    🏠 首页 / diff --git a/go/go-cert-management/index.html b/go/go-cert-management/index.html index 87c3eeb2..f46cc098 100644 --- a/go/go-cert-management/index.html +++ b/go/go-cert-management/index.html @@ -5,7 +5,7 @@ Golang 密钥对、数字签名和证书管理 # Golang 实现密钥对生成 相当于使用 openssl 生成私钥和公钥: openssl genrsa -out pri.key 2048 openssl rsa -in pri.key -pubout -out pub.key package main import ( "crypto/rand" "crypto/rsa" ) func GenerateKeyPair() (*rsa.PrivateKey, *rsa.PublicKey, error) { prikey, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return nil, nil, err } return prikey, &prikey.PublicKey, nil } 实现加密和解密 加密解密:公钥加密,私钥解密 package main import ( "crypto/rand" "crypto/rsa" ) func Encrypt(data []byte, publicKey *rsa.'>Go Cert Management | 秋河落叶 - +

    Go Cert Management

    🏠 首页 / diff --git a/go/go-cross-complie/index.html b/go/go-cross-complie/index.html index 6cc2dc13..3f65cd64 100644 --- a/go/go-cross-complie/index.html +++ b/go/go-cross-complie/index.html @@ -5,7 +5,7 @@ Golang 不同平台架构编译 # 在 MacOS 平台编译成 Windows、Linux 可执行文件: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build main.go CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build main.go 在 Windows 平台编译成 Linux、MacOS 可执行文件: $env:GOOS = "linux";$env:CGO_ENABLED = "0";$env:GOARCH = "amd64";go build carbon/carbon.go $env:GOOS = "linux";$env:CGO_ENABLED = "0";$env:GOARCH = "arm64";go build carbon/carbon.go $env:GOOS = "darwin";$env:CGO_ENABLED = "0";$env:GOARCH = "amd64";go build carbon/carbon.'>Go Cross Complie | 秋河落叶 - +

    Go Cross Complie

    🏠 首页 / diff --git a/go/go-gen-cert/index.html b/go/go-gen-cert/index.html index 9751cca7..5876890d 100644 --- a/go/go-gen-cert/index.html +++ b/go/go-gen-cert/index.html @@ -1,7 +1,7 @@ Go Gen Cert | 秋河落叶 - +

    Go Gen Cert

    🏠 首页 / diff --git a/go/go-linkname/index.html b/go/go-linkname/index.html index c4987b09..f87bc42f 100644 --- a/go/go-linkname/index.html +++ b/go/go-linkname/index.html @@ -5,7 +5,7 @@ go:linkname 指令 # 背景 # 阅读 Golang 源码时,发现在标准库 time.Sleep 方法没有没有方法体。如下: // Sleep pauses the current goroutine for at least the duration d. // A negative or zero duration causes Sleep to return immediately. func Sleep(d Duration) 当我们直接在代码中写一个空方法 func Foo(),编译时会报错:missing function body。所以标准库使用了什么魔法来实现空方法的呢? 进一步研究,得知 time.Sleep 运行时实际调用了 runtime.timeSleep方法,如下: // timeSleep puts the current goroutine to sleep for at least ns nanoseconds. // //go:linkname timeSleep time.">Go Linkname | 秋河落叶 - +

    Go Linkname

    🏠 首页 / diff --git a/go/go-list-to-tree/index.html b/go/go-list-to-tree/index.html index 65021a69..5de1efe4 100644 --- a/go/go-list-to-tree/index.html +++ b/go/go-list-to-tree/index.html @@ -5,7 +5,7 @@ Golang 列表转树 # 场景介绍 # 从数据库获取到了菜单列表数据,这些菜单数据通过字段 ParentID 表示父子层级关系,现在需要将菜单列表数据转成树状的实例对象。 数据库取出的初始数据: raw := []Menu{ {Name: "一级菜单 1", ID: 1, PID: 0}, {Name: "一级菜单 2", ID: 2, PID: 0}, {Name: "一级菜单 3", ID: 3, PID: 0}, {Name: "二级菜单 1-1", ID: 11, PID: 1}, {Name: "二级菜单 1-2", ID: 12, PID: 1}, {Name: "二级菜单 1-3", ID: 13, PID: 1}, {Name: "二级菜单 2-1", ID: 21, PID: 2}, {Name: "二级菜单 2-2", ID: 22, PID: 2}, {Name: "二级菜单 2-3", ID: 23, PID: 2}, } 需要得到的目标数据:'>Go List to Tree | 秋河落叶 - +

    Go List to Tree

    🏠 首页 / diff --git a/go/go-mtls/index.html b/go/go-mtls/index.html index ad96c576..1efb4805 100644 --- a/go/go-mtls/index.html +++ b/go/go-mtls/index.html @@ -11,7 +11,7 @@ 为了简化 mTLS 握手的过程,我们这样简单梳理: 客户端发送访问服务器上受保护信息的请求; 服务器向客户端提供公钥证书; 客户端通过使用 CA 的公钥来验证服务器公钥证书的数字签名,以验证服务器的证书; 如果步骤 3 成功,客户机将其客户端公钥证书发送到服务器; 服务器使用步骤 3 中相同的方法验证客户机的证书; 如果成功,服务器将对受保护信息的访问权授予客户机。 代码实现 # 需要实现客户端验证服务端的公钥证书,服务端验证客户端的公钥证书。 生成证书 # echo '清理并生成目录' OUT=./certs DAYS=365 RSALEN=2048 CN=poneding rm -rf ${OUT}/* mkdir ${OUT} >> /dev/null 2>&1 cd ${OUT} echo '生成CA的私钥' openssl genrsa -out ca.key ${RSALEN} >> /dev/null 2>&1 echo '生成CA的签名证书' openssl req -new \ -x509 \ -key ca.">Go Mtls | 秋河落叶 - +

    Go Mtls

    🏠 首页 / diff --git a/go/go-publish-package-01/index.html b/go/go-publish-package-01/index.html index aa918a92..aa7b7506 100644 --- a/go/go-publish-package-01/index.html +++ b/go/go-publish-package-01/index.html @@ -19,7 +19,7 @@ 4、编写 go 类库代码,例如:: hell/hello.go: package hello import "fmt" func Say(name string) { fmt.'>Go Publish Package 01 | 秋河落叶 - +

    Go Publish Package 01

    🏠 首页 / diff --git a/go/go-publish-package-02/index.html b/go/go-publish-package-02/index.html index 35a760f9..ea61551f 100644 --- a/go/go-publish-package-02/index.html +++ b/go/go-publish-package-02/index.html @@ -19,7 +19,7 @@ git checkout v1 2、修改类库代码: hello/hello.go: package hello import "fmt" func Say(name string) { fmt.Printf("Hello, %s\n", name) fmt.Println("common-go version: v1.0.1") } 3、提交代码并发布:'>Go Publish Package 02 | 秋河落叶 - +

    Go Publish Package 02

    🏠 首页 / diff --git a/go/go-solid/index.html b/go/go-solid/index.html index 3f6c9a1b..335461c4 100644 --- a/go/go-solid/index.html +++ b/go/go-solid/index.html @@ -13,7 +13,7 @@ 为什么一段代码只有一个改变的原因很重要?嗯,就像你自己的代码可能会改变一样令人沮丧,发现您的代码所依赖的代码在您脚下发生变化更痛苦。当你的代码必须改变时,它应该响应直接刺激作出改变,而不应该成为附带损害的受害者。 因此,具有单一责任的代码修改的原因最少。 耦合和内聚 # 描述改变一个软件是多么容易或困难的两个词是:耦合和内聚。">Go Solid | 秋河落叶 - +

    Go Solid

    🏠 首页 / diff --git a/go/go-stdlib/index.html b/go/go-stdlib/index.html index 27b7ec9f..0dd2fdeb 100644 --- a/go/go-stdlib/index.html +++ b/go/go-stdlib/index.html @@ -5,7 +5,7 @@ Golang 标准库 # fmt # 格式化打印 %v 原样输出 %T 打印类型 %t bool %s string %f float %d 10进制整数 %b 2进制整数 %o 8进制整数 %x 16进制整数 0-9,a-f %X 16进制整数 0-9,A-F %c char %p pointer %.2f float 保留两位 path # file := "./logs/2021-01-25/error.log" fileName := path.Base(file) # 返回文件名:error.log fileExt := path.Ext(file) # 返回文件后缀:.log fileDir := path.Dir(file) # 返回文件路径: ./logs/2021-01-25 os/exec # Golang语言有一个包叫做 os/exec,使用该包可以直接在程序中调用主机的命令,使用示例如下: func OsExecUsage() error { fmt.'>Go Stdlib | 秋河落叶 - +

    Go Stdlib

    🏠 首页 / diff --git a/go/go-testing/index.html b/go/go-testing/index.html index 1c6f0510..d4a3fc8b 100644 --- a/go/go-testing/index.html +++ b/go/go-testing/index.html @@ -15,7 +15,7 @@ 包外测试:测试文件的包名称与被测包不一致,一般在被测包名称后面添加 _test 后缀,只能访问被测包内公开成员,相当于黑盒测试。 « Golang 标准库 » Golang">Go Testing | 秋河落叶 - +

    Go Testing

    🏠 首页 / diff --git a/go/go/index.html b/go/go/index.html index ecac8243..ec0e4956 100644 --- a/go/go/index.html +++ b/go/go/index.html @@ -21,7 +21,7 @@ close(ch) 当你的程序不再需要往 channel 中发送数据时,可以关闭 channel。 如果往已经关闭的 channal 发送数据,程序发生异常。 无缓冲 channel # 如果当前没有一个 goroutine 对无缓冲 channel 接收数据,那么无缓冲 channel 会阻止发送数据。">Go | 秋河落叶 - +

    Go

    🏠 首页 / diff --git a/go/gopkg-errors/index.html b/go/gopkg-errors/index.html index 0b31dc9c..f1395522 100644 --- a/go/gopkg-errors/index.html +++ b/go/gopkg-errors/index.html @@ -13,7 +13,7 @@ errors 包以不破坏错误的原始值的方式向错误中的添加调用上下文信息。 获取包 # go get github.com/pkg/errors 错误添加上下文 # The errors.Wrap function returns a new error that adds context to the original error. For example _, err := ioutil.ReadAll(r) if err !">Gopkg Errors | 秋河落叶 - +

    Gopkg Errors

    🏠 首页 / diff --git a/go/goreleaser/index.html b/go/goreleaser/index.html index 4c57ecea..0f082bd2 100644 --- a/go/goreleaser/index.html +++ b/go/goreleaser/index.html @@ -19,7 +19,7 @@ 从github生成token,写入文件: mkdir ~/.config/goreleaser vim ~/.config/goreleaser/github_token 或者直接在终端导入环境配置: export GITHUB_TOKEN="YOUR_GITHUB_TOKEN" 为项目打上 tag # git tag v0.'>Goreleaser | 秋河落叶 - +

    Goreleaser

    🏠 首页 / diff --git a/go/index.html b/go/index.html index c4c72a9e..a0f4b112 100644 --- a/go/index.html +++ b/go/index.html @@ -37,7 +37,7 @@ Mac M1 交叉编译 CGO pprof 使用 Go 生成 OpenSSH 兼容的 RSA 密钥对">Go | 秋河落叶 - +

    Go

    🏠 首页 / Golang 编程

    Golang 编程 diff --git a/go/mac-appl-silicon-cross-compile-cgo/index.html b/go/mac-appl-silicon-cross-compile-cgo/index.html index dcdfdaf5..f8fb0c33 100644 --- a/go/mac-appl-silicon-cross-compile-cgo/index.html +++ b/go/mac-appl-silicon-cross-compile-cgo/index.html @@ -13,7 +13,7 @@ brew install FiloSottile/musl-cross/musl-cross 2、添加到 PATH export PATH=$PATH:/opt/homebrew/Cellar/musl-cross/0.9.9_1/bin 3、编译 CGO 程序 # -tags=musl 不能省略不然会出现其他错误 CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=x86_64-linux-musl-gcc CXX=x86_64-linux-musl-g++ go build -tags=musl # 如果linux不想安装musl支持 CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=x86_64-linux-musl-gcc CXX=x86_64-linux-musl-g++ CGO_LDFLAGS="-static" go build -tags=musl CGO_ENABLED=1 GOOS=linux GOARCH=amd64 CC=x86_64-linux-musl-gcc CGO_LDFLAGS="-static" go build CGO_ENABLED=1 GOOS=linux GOARCH=arm64 CC=x86_64-linux-musl-gcc CGO_LDFLAGS="-static" go build 参考 # https://blog.'>MAC Appl Silicon Cross Compile Cgo | 秋河落叶 - +
    MAC Appl Silicon Cross Compile Cgo

    🏠 首页 / diff --git a/go/pprof/index.html b/go/pprof/index.html index e1d8f485..264de09f 100644 --- a/go/pprof/index.html +++ b/go/pprof/index.html @@ -3,7 +3,7 @@ package main import ( "fmt" "math/rand" "net/http" "time" _ "net/http/pprof" ) // 吃内存 type Eater struct { Name string Buffer [][]int } var e Eater func main() { e = Eater{Name: "eater"} http.HandleFunc("/go", goHandler) http.ListenAndServe(":8080", nil) // 如果不使用默认的 mux(http.DefaultServeMux),可以使用如下方式集成 pprof // mux := http.NewServeMux() // mux.HandleFunc("/go", goHandler) // mux.HandleFunc("/debug/pprof/", pprof.Index) // mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) // mux.HandleFunc("/debug/pprof/profile", pprof.Profile) // mux.HandleFunc("/debug/pprof/symbol", pprof.'>Pprof | 秋河落叶 - +

    Pprof

    🏠 首页 / diff --git a/go/ssh-keygen-with-go/index.html b/go/ssh-keygen-with-go/index.html index ee01fb88..d5eef282 100644 --- a/go/ssh-keygen-with-go/index.html +++ b/go/ssh-keygen-with-go/index.html @@ -5,7 +5,7 @@ 使用 Go 生成 OpenSSH 兼容的 RSA 密钥对 # 我们可以使用 ssh-keygen 命令生成一对用于 SSH 访问的私钥和公钥。本文将介绍如何使用 Go 生成一对 OpenSSH 兼容的 RSA 密钥对。 以下代码中 GenOpenSSHKeyPair 方法用于生成一对用于 SSH 访问的私钥和公钥。生成的私钥以 PEM 编码,公钥以 OpenSSH authorized_keys 文件中包含的格式进行编码。 package util import ( "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/pem" "golang.org/x/crypto/ssh" ) // GenOpenSSHKeyPair make a pair of private and public keys for SSH access. // Private Key generated is PEM encoded // Public key is encoded in the format for inclusion in an OpenSSH authorized_keys file.'>SSH Keygen With Go | 秋河落叶 - +

    SSH Keygen With Go

    🏠 首页 / diff --git a/graphql/graphql/index.html b/graphql/graphql/index.html index 46fffaa3..5058cb1c 100644 --- a/graphql/graphql/index.html +++ b/graphql/graphql/index.html @@ -33,7 +33,7 @@ 资源定义,请求和响应 # 数据描述: type User { name: String phone: String Friends: [User] } 数据请求: { user(name: "dp") { phone } } 请求结果:'>Graphql | 秋河落叶 - +

    Graphql

    🏠 首页 / diff --git a/graphql/index.html b/graphql/index.html index 25239e8b..c04d3b22 100644 --- a/graphql/index.html +++ b/graphql/index.html @@ -1,7 +1,7 @@ Graphql | 秋河落叶 - +

    Graphql

    🏠 首页 / Graphql

    Graphql diff --git a/grpc/gRPC/index.html b/grpc/gRPC/index.html index fc5abeb1..af3d2d4c 100644 --- a/grpc/gRPC/index.html +++ b/grpc/gRPC/index.html @@ -1,7 +1,7 @@ G Rpc | 秋河落叶 - +
    G Rpc

    🏠 首页 / diff --git a/grpc/index.html b/grpc/index.html index 2b4d24ae..adc7b816 100644 --- a/grpc/index.html +++ b/grpc/index.html @@ -1,7 +1,7 @@ Grpc | 秋河落叶 - +

    Grpc

    🏠 首页 / Grpc

    Grpc diff --git a/index.html b/index.html index b0375882..b2fa0b85 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,5 @@ | 秋河落叶 - +

    秋河落叶 diff --git a/istio/Istio/index.html b/istio/Istio/index.html index 201c7a39..28c2aa1f 100644 --- a/istio/Istio/index.html +++ b/istio/Istio/index.html @@ -15,7 +15,7 @@ 服务发现 负载均衡 故障恢复 指标和监控 A/B 测试 金丝雀发布 速率控制 访问控制 端到端认证 istioctl # 管理 istio 的命令行工具。 安装 # curl -L https://istio.io/downloadIstio | sh - cp istio-x.x.x /usr/local cd istio-x.x.x export PATH=$PWD/bin:$PATH Istio 的绝大多数治理能力都是在 Sidecar 而非应用程序中实现,因此是非侵入的; Istio 的调用链埋点逻辑也是在 Sidecar 代理中完成,对应用程序非侵入,但应用程序需做适当的修改,即配合在请求头上传递生成的 Trace 相关信息。 关键功能: 流量管理 可观察性 策略执行 服务身份和安全 平台支持 集成和定制 架构 # 数据面板 # Envoy # Envoy 是用 C++ 开发的高性能代理,用于协调服务网格中所有服务的入站和出站流量。Envoy 代理是唯一与数据平面流量交互的 Istio 组件。">Istio | 秋河落叶 - +
    Istio

    🏠 首页 / diff --git a/istio/aws-acm-tls-management/index.html b/istio/aws-acm-tls-management/index.html index d6bbd77d..5c777963 100644 --- a/istio/aws-acm-tls-management/index.html +++ b/istio/aws-acm-tls-management/index.html @@ -7,7 +7,7 @@ 在Ingressgateway service的annotation中添加: kind: Service apiVersion: v1 metadata: name: my-ingressgateway namespace: istio-system labels: app: my-ingressgateway annotations: # external-dns.alpha.kubernetes.io/hostname: example.com service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout: '3600' service.beta.kubernetes.io/aws-load-balancer-ssl-cert: >- arn:aws:acm:ap-southeast-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx service.beta.kubernetes.io/aws-load-balancer-ssl-ports: https .... service.yaml: apiVersion: v1 kind: Service metadata: name: demo-api labels: app: demop-api spec: ports: - name: http port: 80 targetPort: 80 selector: app: demo-api pod 所在的 namespace 需要开启 istio-injection,例如:kubectl label namespace default istio-injection=enabled">Aws Acm Tls Management | 秋河落叶 - +

    Aws Acm Tls Management

    🏠 首页 / diff --git a/istio/index.html b/istio/index.html index cac5c239..ede3b194 100644 --- a/istio/index.html +++ b/istio/index.html @@ -19,7 +19,7 @@ 应用层级设置访问白名单 实现 Https 协议的转发 Istio 0-1 流量管理方案">Istio | 秋河落叶 - +

    Istio

    🏠 首页 / Istio

    Istio diff --git a/istio/installation/index.html b/istio/installation/index.html index f5708023..0a6a2f21 100644 --- a/istio/installation/index.html +++ b/istio/installation/index.html @@ -15,7 +15,7 @@ cd istio-{ISTIO_VERSION} 拷贝bin目录下的istioctl二进制文件到PATH目录下: cp bin/istio /usr/local/bin « 使用 aws-acm 管理 tls 密钥和证书 » 授权策略 Authorization Policy">Installation | 秋河落叶 - +
    Installation

    🏠 首页 / diff --git a/istio/istio-auth-policy/index.html b/istio/istio-auth-policy/index.html index 34a8acc0..d6c7e398 100644 --- a/istio/istio-auth-policy/index.html +++ b/istio/istio-auth-policy/index.html @@ -19,7 +19,7 @@ 访问控制的请求条件: from:请求来源 to:请求目标 when:应用规则所需的提交 示例 # 以下授权策略允许两个源(服务帐号 cluster.local/ns/default/sa/sleep 和命名空间 dev),在使用有效的 JWT 令牌发送请求时,可以访问命名空间 foo 中的带有标签 app: httpbin 和 version: v1 的工作负载。 apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: httpbin namespace: foo spec: selector: matchLabels: app: httpbin version: v1 action: ALLOW rules: - from: - source: principals: ["cluster.'>Istio Auth Policy | 秋河落叶 - +

    Istio Auth Policy

    🏠 首页 / diff --git a/istio/istio-canary-deploy/index.html b/istio/istio-canary-deploy/index.html index 1c7c5ed5..11d4017a 100644 --- a/istio/istio-canary-deploy/index.html +++ b/istio/istio-canary-deploy/index.html @@ -17,7 +17,7 @@ 应用处于发布状态,已经至少发布一次,当前存在稳定的运行版本; 应用已经开启 Istio Gateway,涉及到 VirtualService 的流量转发; 应用当前不是金丝雀状态,要不然乱套了。 发布细节 # CI 发布除了创建或更新 Deployment,Service 之外,默认创建或更新 istio 的 DestinationRule 资源; deployment: apiVersion: apps/v1 kind: Deployment metadata: name: myapp-pbd3n69-v{iteration} # 出于兼容历史发布,当iteration为0时,name不附带iteration,iteration大于0时,name将附带iteration labels: app: myapp-pbd3n69 version: v{iteration} spec: selector: matchLabels: app: myapp-pbd3n69 version: v{iteration} template: metadata: labels: app: myapp-pbd3n69 version: v{iteration} .">Istio Canary Deploy | 秋河落叶 - +

    Istio Canary Deploy

    🏠 首页 / diff --git a/istio/istio-cors/index.html b/istio/istio-cors/index.html index f5ac00c7..b706e241 100644 --- a/istio/istio-cors/index.html +++ b/istio/istio-cors/index.html @@ -15,7 +15,7 @@ 官方文档: Istio / Virtual Service#CorsPolicy 在目标服务上设置允许的请求域 Hello: apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: b spec: http: - route: - destination: host: b.'>Istio Cors | 秋河落叶 - +

    Istio Cors

    🏠 首页 / diff --git a/istio/istio-timeout/index.html b/istio/istio-timeout/index.html index 3c331200..85fc68c2 100644 --- a/istio/istio-timeout/index.html +++ b/istio/istio-timeout/index.html @@ -15,7 +15,7 @@ apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: myapp spec: hosts: - myapp http: - route: - destination: host: myapp subset: v1 timeout: 1s timeout:请求超过设定的超时时间,响应返回 504 请求超时。 « Istio 0-1 使用Istio实现Cors » 应用层级设置访问白名单">Istio Timeout | 秋河落叶 - +

    Istio Timeout

    🏠 首页 / diff --git a/istio/istio-white-manifest/index.html b/istio/istio-white-manifest/index.html index 2f71d31a..117c34cd 100644 --- a/istio/istio-white-manifest/index.html +++ b/istio/istio-white-manifest/index.html @@ -13,7 +13,7 @@ 通过: https://www.example.com/bar 访问 bar; 插曲 # 按照 istio 官方文档,使用 AuthorizationPolicy 即可实现基于应用层级的访问白名单设置: apiVersion: security.istio.io/v1beta1 kind: AuthorizationPolicy metadata: name: foo spec: selector: matchLabels: app: foo action: ALLOW rules: - from: - source: ipBlocks: ["1.'>Istio White Manifest | 秋河落叶 - +

    Istio White Manifest

    🏠 首页 / diff --git a/istio/tls-transform/index.html b/istio/tls-transform/index.html index 982c5900..e9d5c038 100644 --- a/istio/tls-transform/index.html +++ b/istio/tls-transform/index.html @@ -3,7 +3,7 @@ » Istio 0-1 流量管理方案">Tls Transform | 秋河落叶 - +

    Tls Transform

    🏠 首页 / diff --git a/istio/traffic-management/index.html b/istio/traffic-management/index.html index 7adfac3e..309dd130 100644 --- a/istio/traffic-management/index.html +++ b/istio/traffic-management/index.html @@ -5,7 +5,7 @@ Istio 0-1 流量管理方案 # 设置 istio-system 命名空间下 istiod Deployment 的环境变量: PILOT_ENABLE_VIRTUAL_SERVICE_DELEGATE =true: « 实现 Https 协议的转发">Traffic Management | 秋河落叶 - +

    Traffic Management

    🏠 首页 / diff --git a/kubernetes/anti-affinity-improves-service-availability/index.html b/kubernetes/anti-affinity-improves-service-availability/index.html index 5a10d2aa..f5bc4cbd 100644 --- a/kubernetes/anti-affinity-improves-service-availability/index.html +++ b/kubernetes/anti-affinity-improves-service-availability/index.html @@ -5,7 +5,7 @@ 反亲和性提高服务可用性 # 在 Kubernetes 中部署服务时,我们通常会部署多副本来提高服务的可用性。但是当这些副本集中部署在一个节点,而且很不幸,该节点出现故障,那么服务很容易陷入不可用状态。 下面介绍一种方法,将服务副本分散部署在不同的节点(把鸡蛋放在不同的篮子里),避免单个节点故障导致服务多副本毁坏,提高服务可用性。 反亲和 # apiVersion: apps/v1 kind: Deployment metadata: name: nginx labels: app: nginx spec: selector: matchLabels: app: nginx replicas: 5 template: metadata: labels: app: nginx spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - nginx topologyKey: kubernetes.io/hostname containers: - name: nginx image: nginx ports: - name: tcp containerPort: 80 使用 kubernetes.io/hostname 作为拓扑域,查看匹配规则,即同一打有同样标签 app=nginx 的 pod 会调度到不同的节点。">Anti Affinity Improves Service Availability | 秋河落叶 - +

    Anti Affinity Improves Service Availability

    🏠 首页 / diff --git a/kubernetes/apiserver-builder/index.html b/kubernetes/apiserver-builder/index.html index 3d2075dc..3876473f 100644 --- a/kubernetes/apiserver-builder/index.html +++ b/kubernetes/apiserver-builder/index.html @@ -9,7 +9,7 @@ mkdir -p $(go env GOPATH)/src/github.com/poneding/apiserver-demo && cd $(go env GOPATH)/src/github.com/poneding/apiserver-demo apiserver-boot init repo --domain k8sdev.poneding.com 创建 API: # apiserver-boot create apiserver-boot create demo v1alpha1 User apiserver-boot create group version resource --group demo --version v1alpha1 --kind User 参考 # https://github.com/kubernetes-sigs/apiserver-builder-alpha/blob/master/docs/tools_user_guide.md https://github.com/kubernetes-sigs/apiserver-builder-alpha/blob/master/README.md « 反亲和性提高服务可用性 » apiserver">Apiserver Builder | 秋河落叶 - +

    Apiserver Builder

    🏠 首页 / diff --git a/kubernetes/apiserver/index.html b/kubernetes/apiserver/index.html index 902bce54..64031d1d 100644 --- a/kubernetes/apiserver/index.html +++ b/kubernetes/apiserver/index.html @@ -5,7 +5,7 @@ apiserver # 每一个 api 版本均有一个 apiservice 与之对应 k api-versions | wc -l 30 k get apiservices.apiregistration.k8s.io| wc -l 30 « apiserver-builder » 二进制搭建 K8s - 1 机器准备">Apiserver | 秋河落叶 - +

    Apiserver

    🏠 首页 / diff --git a/kubernetes/binary-build-k8s-01-prepare-nodes/index.html b/kubernetes/binary-build-k8s-01-prepare-nodes/index.html index 262aa5f3..c985653f 100644 --- a/kubernetes/binary-build-k8s-01-prepare-nodes/index.html +++ b/kubernetes/binary-build-k8s-01-prepare-nodes/index.html @@ -17,7 +17,7 @@ 当前虚拟机: k8s-master01: 192.168.115.131 k8s-node01: 192.168.115.132 k8s-node02: 192.168.115.133 虚拟机初始化 # 不做特殊说明的话: 以下操作需要在 Master 和 Node 的所有机器上执行">Binary Build K8s 01 Prepare Nodes | 秋河落叶 - +

    Binary Build K8s 01 Prepare Nodes

    🏠 首页 / diff --git a/kubernetes/binary-build-k8s-02-deploy-etcd/index.html b/kubernetes/binary-build-k8s-02-deploy-etcd/index.html index 0410a9c2..57d2b9de 100644 --- a/kubernetes/binary-build-k8s-02-deploy-etcd/index.html +++ b/kubernetes/binary-build-k8s-02-deploy-etcd/index.html @@ -9,7 +9,7 @@ etcd 是一个分布式的数据库系统,为了模拟 etcd 的高可用,我们将 etcd 部署在三台虚拟机上,正好就部署在 K8s 集群所使用的三台机器上吧。 etcd 集群,K8s 组件之间通信,为了安全可靠,我们最好启用 HTTPS 安全机制。K8s 提供了基于 CA 签名的双向数字证书认证方式和简单的基于 HTTP Base 或 Token 的认证方式,其中 CA 证书方式的安全性最高。我们使用 cfssl 为我们的 K8s 集群配置 CA 证书,此外也可以使用 openssl。 安装 cfssl # 在 Master 机器执行:">Binary Build K8s 02 Deploy Etcd | 秋河落叶 - +

    Binary Build K8s 02 Deploy Etcd

    🏠 首页 / diff --git a/kubernetes/binary-build-k8s-03-deploy-master/index.html b/kubernetes/binary-build-k8s-03-deploy-master/index.html index fcd4583d..a4cdf65a 100644 --- a/kubernetes/binary-build-k8s-03-deploy-master/index.html +++ b/kubernetes/binary-build-k8s-03-deploy-master/index.html @@ -7,7 +7,7 @@ 机器准备: 部署 etcd 集群: 部署 Master: 部署 Node: 我们已经知道在 K8s 的 Master 上存在着 kube-apiserver、kube-controller-manager、kube-scheduler 三大组件。本篇介绍在 Master 机器安装这些组件,除此之外,如果想在 Master 机器上操作集群,还需要安装 kubectl 工具。 安装 kubectl # kubernetes 的安装包里已经将 kubectl 包含进去了,部署很简单: cd /root/kubernetes/resources/ tar -zxvf ./kubernetes-server-linux-amd64.tar.gz cp kubernetes/server/bin/kubectl /usr/bin kubectl api-versions 制作 kubernetes 证书 # mkdir /root/kubernetes/resources/cert/kubernetes /etc/kubernetes/{ssl,bin} -p cp kubernetes/server/bin/kube-apiserver kubernetes/server/bin/kube-controller-manager kubernetes/server/bin/kube-scheduler /etc/kubernetes/bin cd /root/kubernetes/resources/cert/kubernetes 接下来都在 Master 机器上执行,编辑 ca-config.">Binary Build K8s 03 Deploy Master | 秋河落叶 - +

    Binary Build K8s 03 Deploy Master

    🏠 首页 / diff --git a/kubernetes/binary-build-k8s-04-deploy-worker/index.html b/kubernetes/binary-build-k8s-04-deploy-worker/index.html index 29637bf4..1a051d99 100644 --- a/kubernetes/binary-build-k8s-04-deploy-worker/index.html +++ b/kubernetes/binary-build-k8s-04-deploy-worker/index.html @@ -9,7 +9,7 @@ 本篇的执行命令需要在准备的两台Node机器上执行。 安装 docker # 可以参照官网: https://docs.docker.com/engine/install/ # 卸载老版本或重装 docker 时执行第一行 yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine -y # 安装 docker yum install -y yum-utils yum-config-manager \ --add-repo \ https://download.">Binary Build K8s 04 Deploy Worker | 秋河落叶 - +

    Binary Build K8s 04 Deploy Worker

    🏠 首页 / diff --git a/kubernetes/cloud-native-understood/index.html b/kubernetes/cloud-native-understood/index.html index 56f28993..f233915a 100644 --- a/kubernetes/cloud-native-understood/index.html +++ b/kubernetes/cloud-native-understood/index.html @@ -13,7 +13,7 @@ 云原生本身不能称为是一种架构,它首先是一种基础设施,运行在其上的应用称作云原生应用,只有符合云原生设计哲学的应用架构才叫云原生应用架构。 « 二进制搭建 K8s - 4 部署 Node » 集群联邦">Cloud Native Understood | 秋河落叶 - +

    Cloud Native Understood

    🏠 首页 / diff --git a/kubernetes/cluster-federation/index.html b/kubernetes/cluster-federation/index.html index dc456eaa..a23b95a6 100644 --- a/kubernetes/cluster-federation/index.html +++ b/kubernetes/cluster-federation/index.html @@ -17,7 +17,7 @@ Template:定义了联邦资源的模板,用于指定联邦资源的属性 Placement:定义了联邦集群资源的部署位置,用于指定联邦资源的部署位置。 Overrides:定义了联邦集群资源的覆盖规则,用于覆盖联邦资源的属性。 kubefed 为所有的 Kubernetes 原生资源提供了对应的联邦资源,例如 FederatedService、FederatedDeployment 等。 联邦资源中定义了原生资源的 Template、又通过 Overrides 定义了资源同步到不同的工作集群时需要做的变更,例如: kind: FederatedDeployment ... spec: ... overrides: # Apply overrides to cluster1 - clusterName: cluster1 clusterOverrides: # Set the replicas field to 5 - path: "/spec/replicas" value: 5 # Set the image of the first container - path: "/spec/template/spec/containers/0/image" value: "nginx:1.'>Cluster Federation | 秋河落叶 - +

    Cluster Federation

    🏠 首页 / diff --git a/kubernetes/configmap-understood/index.html b/kubernetes/configmap-understood/index.html index 102158a9..b83fb714 100644 --- a/kubernetes/configmap-understood/index.html +++ b/kubernetes/configmap-understood/index.html @@ -15,7 +15,7 @@ 可以使用多组 --from-literal== 参数,在 configmap 中定义多组键值对。 创建一个文件内容的 ConfigMap 假如我当前有一个配置文件 app.json,文件内容如下: { "App": "MyApp", "Version": "v1.0" } 使用以下命令创建 ConfigMap:'>Configmap Understood | 秋河落叶 - +

    Configmap Understood

    🏠 首页 / diff --git a/kubernetes/delete-es-log-index-scheduler/index.html b/kubernetes/delete-es-log-index-scheduler/index.html index 689117b8..4e59702d 100644 --- a/kubernetes/delete-es-log-index-scheduler/index.html +++ b/kubernetes/delete-es-log-index-scheduler/index.html @@ -9,7 +9,7 @@ apiVersion: v1 kind: ConfigMap metadata: name: es-log-indices-clear-configmap namespace: efk data: clean-indices.sh: | #/bin/bash LAST_MONTH_DATE=`date -d "1 month ago" +"%Y.%m.%d"` echo Start clear es indices *-${LAST_MONTH_DATE} curl -XDELETE http://elasticsearch:9200/*-${LAST_MONTH_DATE} --- 说明:这里我配置的configmap所在命名空间和efk部署的命名空间一致,并且es的Service的名称是elasticsearch,所以可以使用 http://elasticsearch:9200访问到es服务,否则的话需要是无法访问到的,所以这里需要根据具体情况配置es的服务地址; CronJob # CronJob使用了 poneding/sparrow apiVersion: batch/v1beta1 kind: CronJob metadata: name: clean-indices namespace: efk spec: schedule: "0 0 1/1 * *" jobTemplate: spec: template: spec: containers: - name: auto-recycle-job image: poneding/sparrow args: ["/bin/sh", "/job/clean-indices.'>Delete Es Log Index Scheduler | 秋河落叶 - +

    Delete Es Log Index Scheduler

    🏠 首页 / diff --git a/kubernetes/delete-k8s-resource-force/index.html b/kubernetes/delete-k8s-resource-force/index.html index 99695219..e171483e 100644 --- a/kubernetes/delete-k8s-resource-force/index.html +++ b/kubernetes/delete-k8s-resource-force/index.html @@ -5,7 +5,7 @@ 强制删除 K8s 资源 # 强制删除 Pod # kubectl delete po -n --force --grace-period=0 强制删除 PVC # kubectl patch pv -n -p '{"metadata":{"finalizers":null}}' 强制删除 PV # kubectl patch pvc -n -p '{"metadata":{"finalizers":null}}' 强制删除命名空间 # 在删除 kubesphere 的命名空间时遇到无法删除成功的现象,命名空间一直处于 Terminating 状态。 $ kubectl get ns |grep kubesphere NAME STATUS AGE kubesphere-controls-system Terminating 22d kubesphere-monitoring-system Terminating 21d 在网上找到了一种解决方案。 首先获取命名空间的 json 文件,'>Delete K8s Resource Force | 秋河落叶 - +

    Delete K8s Resource Force

    🏠 首页 / diff --git a/kubernetes/gateway-api-practice/index.html b/kubernetes/gateway-api-practice/index.html index 08a963de..f420d077 100644 --- a/kubernetes/gateway-api-practice/index.html +++ b/kubernetes/gateway-api-practice/index.html @@ -7,7 +7,7 @@ Gateway API 概述 # Gateway API 是一个 Kubernetes 的扩展 API,它定义了一套 API 来管理网关、路由、TLS 等资源对象,可以用来替代传统的 Ingress。 和 Ingress 一样,Gateway API 也是一个抽象层,它定义了一套 API 接口,这些接口由社区中的不同厂商来实现,比如 nginx、envoy、traefik 等。 API 清单 # GatewayClass Gateway HTTPRoute GRPCRoute BackendTLSPolicy ReferenceGrant 安装 Gateway API # # 安装最新版 gateway-api CRDs export LATEST=$(curl -s https://api.github.com/repos/kubernetes-sigs/gateway-api/releases/latest | jq -r .tag_name) kubectl apply -f https://github.">Gateway API Practice | 秋河落叶 - +

    Gateway API Practice

    🏠 首页 / diff --git a/kubernetes/helm-k8s-package-management-tool/index.html b/kubernetes/helm-k8s-package-management-tool/index.html index f8352d2d..a6ad956d 100644 --- a/kubernetes/helm-k8s-package-management-tool/index.html +++ b/kubernetes/helm-k8s-package-management-tool/index.html @@ -31,7 +31,7 @@ 下载地址: https://github.com/helm/helm/releases wget https://get.helm.sh/helm-v3.5.2-linux-amd64.tar.gz tar -zxvf ./helm-v3.5.2-linux-amd64.tar.gz sudo mv linux-amd64/helm /usr/local/bin/helm 第一个helm命令 helm help 更多安装方式可查看: https://helm.">Helm K8s Package Management Tool | 秋河落叶 - +

    Helm K8s Package Management Tool

    🏠 首页 / diff --git a/kubernetes/hpa-usage/index.html b/kubernetes/hpa-usage/index.html index 33ee1d9a..5e437dcf 100644 --- a/kubernetes/hpa-usage/index.html +++ b/kubernetes/hpa-usage/index.html @@ -11,7 +11,7 @@ 示例 # 以部署redis为例,现使用redis « Kubernetes 0-1 Helm Kubernetes 的包管理工具 » HTTP 客户端调用 Kubernetes APIServer">Hpa Usage | 秋河落叶 - +

    Hpa Usage

    🏠 首页 / diff --git a/kubernetes/http-call-k8s-apiserver/index.html b/kubernetes/http-call-k8s-apiserver/index.html index 7dafda7a..d84897c9 100644 --- a/kubernetes/http-call-k8s-apiserver/index.html +++ b/kubernetes/http-call-k8s-apiserver/index.html @@ -5,7 +5,7 @@ HTTP 客户端调用 Kubernetes APIServer # 本篇介绍几种如何通过 HTTP 客户端调用 Kubernetes APIServer 的姿势。 如何获取 Kubernetes api-server 地址 # 查看 api-server 的几种方式: # 1. 直接查看 kubeconfig 文件 $ cat ~/.kube/config apiVersion: v1 clusters: - cluster: server: https://192.168.58.2:8443 ... # 2. kubectl 查看集群信息 $ kubectl cluster-info Kubernetes control plane is running at https://192.168.58.2:8443 ... # 3. kubectl 查看集群配置 $ kubectl config view clusters: - cluster: .">HTTP Call K8s Apiserver | 秋河落叶 - +

    HTTP Call K8s Apiserver

    🏠 首页 / diff --git a/kubernetes/index.html b/kubernetes/index.html index c8755858..9133bf88 100644 --- a/kubernetes/index.html +++ b/kubernetes/index.html @@ -41,7 +41,7 @@ 安装 Kubernetes K3s Kubernetes 0-1 K8s部署coredns">Kubernetes | 秋河落叶 - +

    Kubernetes

    🏠 首页 / Kubernetes

    Kubernetes diff --git a/kubernetes/informer/index.html b/kubernetes/informer/index.html index 33c84c10..3dda6536 100644 --- a/kubernetes/informer/index.html +++ b/kubernetes/informer/index.html @@ -43,7 +43,7 @@ 一个自带索引功能的本地存储,用于存储资源对象。Informer从DeltaFIFO中Pop出资源,存储到Indexer。Indexer中资源与k8s etcd数据保持一致。本地读取时直接查询本地存储,从而减少k8s apiserver和etcd的压力。 使用示例 # 自定义控制器 clientset, err := kubernetes.NewForConfig(config) stopCh := make(chan struct{}) defer close(stopch) sharedInformers := informers.NewSharedInformerFactory(clientset, time.Minute) informer := sharedInformer.Core().V1().Pods().Informer() informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{} { // ... }, UpdateFunc: func(obj interface{} { // .">Informer | 秋河落叶 - +
    Informer

    🏠 首页 / diff --git a/kubernetes/ingress-gray-deploy/index.html b/kubernetes/ingress-gray-deploy/index.html index a97ec61c..4ac82f09 100644 --- a/kubernetes/ingress-gray-deploy/index.html +++ b/kubernetes/ingress-gray-deploy/index.html @@ -13,7 +13,7 @@ 通过 Ingress 按权重进行灰度发布 通过 Ingress 按 Header 进行灰度发布 容器服务 Kubernetes 版(简称 ACK) 本节课使用的 Kubernetes(k8s) 集群就是由 ACK 提供的,本实验涵盖的都是一些基本操作。更多高级用法,可以去 ACK 的产品页面了解哦。 Step 2 :部署 Deployment V1 应用 # 创建如下 YAML 文件(app-v1.yaml) apiVersion: v1 kind: Service metadata: name: my-app-v1 labels: app: my-app spec: ports: - name: http port: 80 targetPort: http selector: app: my-app version: v1.">Ingress Gray Deploy | 秋河落叶 - +

    Ingress Gray Deploy

    🏠 首页 / diff --git a/kubernetes/installation/index.html b/kubernetes/installation/index.html index 31f5a598..fdabc6cf 100644 --- a/kubernetes/installation/index.html +++ b/kubernetes/installation/index.html @@ -3,7 +3,7 @@ » K3s">Installation | 秋河落叶 - +

    Installation

    🏠 首页 / diff --git a/kubernetes/k3s/index.html b/kubernetes/k3s/index.html index 94d7c6af..87e5417c 100644 --- a/kubernetes/k3s/index.html +++ b/kubernetes/k3s/index.html @@ -11,7 +11,7 @@ CPU: 1 核 Memory:512M 端口要求: K3s Server 节点的入站规则如下: 协议 端口 源 描述 TCP 6443 K3s agent 节点 Kubernetes API Server UDP 8472 K3s server 和 agent 节点 仅对 Flannel VXLAN 需要 UDP 51820 K3s server 和 agent 节点 只有 Flannel Wireguard 后端需要 UDP 51821 K3s server 和 agent 节点 只有使用 IPv6 的 Flannel Wireguard 后端才需要 TCP 10250 K3s server 和 agent 节点 Kubelet metrics TCP 2379-2380 K3s server 节点 只有嵌入式 etcd 高可用才需要 启动 # curl -sfL https://rancher-mirror.">K3s | 秋河落叶 - +

    K3s

    🏠 首页 / diff --git a/kubernetes/k8s-deploy-coredns/index.html b/kubernetes/k8s-deploy-coredns/index.html index 2596e61d..41d9e1e9 100644 --- a/kubernetes/k8s-deploy-coredns/index.html +++ b/kubernetes/k8s-deploy-coredns/index.html @@ -9,7 +9,7 @@ [root@k8s-master01 ~]# kubectl run -it --rm busybox --image=busybox sh If you don't see a command prompt, try pressing enter. / # ping www.baidu.com ping: bad address 'www.baidu.com' 我们可以通过在K8s中部署coredns解决这一问题。 准备coredns.yaml文件,写入文件内容: apiVersion: v1 kind: ServiceAccount metadata: name: coredns namespace: kube-system labels: kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: labels: kubernetes.io/bootstrapping: rbac-defaults addonmanager.kubernetes.io/mode: Reconcile name: system:coredns rules: - apiGroups: - "" resources: - endpoints - services - pods - namespaces verbs: - list - watch - apiGroups: - "" resources: - nodes verbs: - get --- apiVersion: rbac.'>K8s Deploy Coredns | 秋河落叶 - +

    K8s Deploy Coredns

    🏠 首页 / diff --git a/kubernetes/k8s-deploy-dashboard/index.html b/kubernetes/k8s-deploy-dashboard/index.html index 96f83058..275e471d 100644 --- a/kubernetes/k8s-deploy-dashboard/index.html +++ b/kubernetes/k8s-deploy-dashboard/index.html @@ -11,7 +11,7 @@ kind: Service apiVersion: v1 metadata: labels: k8s-app: kubernetes-dashboard name: kubernetes-dashboard namespace: kubernetes-dashboard spec: type: LoadBalancer ports: - port: 443 targetPort: 8443 selector: k8s-app: kubernetes-dashboard 创建kube-dash-admin-user.yaml文件: vim kube-dash-admin-user.yaml 写入如下内容: apiVersion: v1 kind: ServiceAccount metadata: name: admin-user namespace: kubernetes-dashboard --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: admin-user roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: cluster-admin subjects: - kind: ServiceAccount name: admin-user namespace: kubernetes-dashboard 执行命令:">K8s Deploy Dashboard | 秋河落叶 - +

    K8s Deploy Dashboard

    🏠 首页 / diff --git a/kubernetes/k8s-deploy-efk/index.html b/kubernetes/k8s-deploy-efk/index.html index 7130fe3e..238ff023 100644 --- a/kubernetes/k8s-deploy-efk/index.html +++ b/kubernetes/k8s-deploy-efk/index.html @@ -13,7 +13,7 @@ 采用StatefulSet部署ES。 编写es-statefulSet.yaml文件如下: apiVersion: apps/v1 kind: StatefulSet metadata: name: es-cluster namespace: dev spec: serviceName: elasticsearch replicas: 3 selector: matchLabels: app: elasticsearch template: metadata: labels: app: elasticsearch spec: containers: - name: elasticsearch image: docker.elastic.co/elasticsearch/elasticsearch:7.7.0 resources: limits: cpu: 1000m requests: cpu: 100m ports: - containerPort: 9200 name: rest protocol: TCP - containerPort: 9300 name: inter-node protocol: TCP volumeMounts: - name: data mountPath: /usr/share/elasticsearch/data env: - name: cluster.">K8s Deploy Efk | 秋河落叶 - +

    K8s Deploy Efk

    🏠 首页 / diff --git a/kubernetes/k8s-deploy-prometheus-grafana/index.html b/kubernetes/k8s-deploy-prometheus-grafana/index.html index 363e1814..11b99f2a 100644 --- a/kubernetes/k8s-deploy-prometheus-grafana/index.html +++ b/kubernetes/k8s-deploy-prometheus-grafana/index.html @@ -9,7 +9,7 @@ externalUrl: prometheus routePrefix: prometheus 修改文件kube-prometheus/manifests/grafana-deployment.yaml,做这一步的目的是为grafana的访问分配子路径,访问方式为:http(s)://xxx/grafana 在deployment.spec.template.spec.container[0]下添加 env: - name: GF_SERVER_ROOT_URL value: "http://localhost:3000/grafana" - name: GF_SERVER_SERVE_FROM_SUB_PATH value: "true" Apply k8s资源 # 可能需要运行多次以下命令,确保k8s资源都创建 kubectl create -f manifests/setup -f manifests # !如果要删除以上创建的k8s资源,运行以下命令 kubectl delete --ignore-not-found=true -f manifests/ -f manifests/setup Ingress转发 apiVersion: extensions/v1beta1 kind: Ingress metadata: name: prometheus namespace: monitoring spec: rules: - host: dp.example.tech http: paths: - path: /prometheus backend: serviceName: prometheus-k8s servicePort: 9090 - path: /grafana backend: serviceName: grafana servicePort: 3000 - path: /alertmanager backend: serviceName: alertmanager-main servicePort: 9093 « Kubernetes 0-1 K8s部署EFK'>K8s Deploy Prometheus Grafana | 秋河落叶 - +

    K8s Deploy Prometheus Grafana

    🏠 首页 / diff --git a/kubernetes/k8s-deploy-zookeeper-kafka/index.html b/kubernetes/k8s-deploy-zookeeper-kafka/index.html index bf878c99..be967465 100644 --- a/kubernetes/k8s-deploy-zookeeper-kafka/index.html +++ b/kubernetes/k8s-deploy-zookeeper-kafka/index.html @@ -13,7 +13,7 @@ 部署Zookeeper # 编写zookeeper-statefulSet.yaml文件: vim zookeeper-statefulSet.yaml 写入内容: kind: StatefulSet apiVersion: apps/v1beta1 metadata: name: zookeeper-1 namespace: dev spec: serviceName: zookeeper-1 replicas: 1 selector: matchLabels: app: zookeeper-1 template: metadata: labels: app: zookeeper-1 spec: containers: - name: zookeeper image: digitalwonderland/zookeeper ports: - containerPort: 2181 env: - name: ZOOKEEPER_ID value: "1" - name: ZOOKEEPER_SERVER_1 value: zookeeper-1 - name: ZOOKEEPER_SERVER_2 value: zookeeper-2 - name: ZOOKEEPER_SERVER_3 value: zookeeper-3 volumeMounts: - name: zookeeper-data mountPath: "/var/lib/zookeeper/data" subPath: zookeeper volumeClaimTemplates: - metadata: name: zookeeper-data labels: app: zookeeper-1 spec: accessModes: ["ReadWriteOnce"] storageClassName: gp2 resources: requests: storage: 30Gi --- kind: StatefulSet apiVersion: apps/v1beta1 metadata: name: zookeeper-2 namespace: dev spec: serviceName: zookeeper-2 replicas: 1 selector: matchLabels: app: zookeeper-2 template: metadata: labels: app: zookeeper-2 spec: containers: - name: zookeeper image: digitalwonderland/zookeeper ports: - containerPort: 2181 env: - name: ZOOKEEPER_ID value: "2" - name: ZOOKEEPER_SERVER_1 value: zookeeper-1 - name: ZOOKEEPER_SERVER_2 value: zookeeper-2 - name: ZOOKEEPER_SERVER_3 value: zookeeper-3 volumeMounts: - name: zookeeper-data mountPath: "/var/lib/zookeeper/data" subPath: zookeeper-data volumeClaimTemplates: - metadata: name: zookeeper-data labels: app: zookeeper-2 spec: accessModes: ["ReadWriteOnce"] storageClassName: gp2 resources: requests: storage: 30Gi --- kind: StatefulSet apiVersion: apps/v1beta1 metadata: name: zookeeper-3 namespace: dev spec: serviceName: zookeeper-3 replicas: 1 selector: matchLabels: app: zookeeper-3 template: metadata: labels: app: zookeeper-3 spec: containers: - name: zookeeper image: digitalwonderland/zookeeper ports: - containerPort: 2181 env: - name: ZOOKEEPER_ID value: "3" - name: ZOOKEEPER_SERVER_1 value: zookeeper-1 - name: ZOOKEEPER_SERVER_2 value: zookeeper-2 - name: ZOOKEEPER_SERVER_3 value: zookeeper-3 volumeMounts: - name: zookeeper-data mountPath: "/var/lib/zookeeper/data" subPath: zookeeper volumeClaimTemplates: - metadata: name: zookeeper-data labels: app: zookeeper-3 spec: accessModes: ["ReadWriteOnce"] storageClassName: gp2 resources: requests: storage: 30Gi 编写zookeeper-service.'>K8s Deploy Zookeeper Kafka | 秋河落叶 - +

    K8s Deploy Zookeeper Kafka

    🏠 首页 / diff --git a/kubernetes/k8s-dev-01-api-concept/index.html b/kubernetes/k8s-dev-01-api-concept/index.html index f678fab6..525c1990 100644 --- a/kubernetes/k8s-dev-01-api-concept/index.html +++ b/kubernetes/k8s-dev-01-api-concept/index.html @@ -21,7 +21,7 @@ API 类型,例如:Deployment,Service 等 通过 kubectl api-versions 获取集群中所有 API 的版本列表: $ kubectl api-versions acme.cert-manager.io/v1 admissionregistration.k8s.io/v1 apiextensions.k8s.io/v1 apiregistration.k8s.io/v1 apps/v1 authentication.k8s.io/v1 通过 kubectl api-resources 命令获取集群所有 API 的资源列表,并且可以看到资源的简写名称,版本以及类型: $ kubectl api-resources NAME SHORTNAMES APIVERSION NAMESPACED KIND bindings v1 true Binding componentstatuses cs v1 false ComponentStatus configmaps cm v1 true ConfigMap endpoints ep v1 true Endpoints events ev v1 true Event limitranges limits v1 true LimitRange namespaces ns v1 false Namespace nodes no v1 false Node API 资源端点 # GVR 端点:">K8s Dev 01 API Concept | 秋河落叶 - +

    K8s Dev 01 API Concept

    🏠 首页 / diff --git a/kubernetes/k8s-dev-02-crd/index.html b/kubernetes/k8s-dev-02-crd/index.html index 4fbf7956..a62d857c 100644 --- a/kubernetes/k8s-dev-02-crd/index.html +++ b/kubernetes/k8s-dev-02-crd/index.html @@ -3,7 +3,7 @@ » Kubernetes 定制开发 50:扩展调度器">K8s Dev 02 Crd | 秋河落叶 - +

    K8s Dev 02 Crd

    🏠 首页 / diff --git a/kubernetes/k8s-dev-50-extend-kube-scheduler/index.html b/kubernetes/k8s-dev-50-extend-kube-scheduler/index.html index 53274e80..a0f2ad3f 100644 --- a/kubernetes/k8s-dev-50-extend-kube-scheduler/index.html +++ b/kubernetes/k8s-dev-50-extend-kube-scheduler/index.html @@ -11,7 +11,7 @@ 扩展调度器 # 有三种方式可以实现自定义调度器: 修改 kube-scheduler 源码调度逻辑,然后编译成定制的调度器镜像,然后使用这个镜像部署调度进程 自定义 Pod 控制器,监听 Pod 的 spec.schedulerName 字段,在 Pod 被创建时,为其绑定节点 使用 Scheduler Extender 的方式,这种方式不需要修改默认调度器的配置文件 编译定制调度器镜像 # 克隆 kubernetes 源码,然后修改 kube-scheduler 源码,然后编译成定制的调度器镜像。 git clone https://github.com/kubernetes/kubernetes.git cd kubernetes # 修改源码 make 编写 Dockerfile:">K8s Dev 50 Extend Kube Scheduler | 秋河落叶 - +

    K8s Dev 50 Extend Kube Scheduler

    🏠 首页 / diff --git a/kubernetes/k8s-get-started/index.html b/kubernetes/k8s-get-started/index.html index 56df5879..6ea22604 100644 --- a/kubernetes/k8s-get-started/index.html +++ b/kubernetes/k8s-get-started/index.html @@ -23,7 +23,7 @@ Master 节点 # 负责 K8s 资源的调度管理,由 Master 向 Node 下达控制命令,并且一般运维人员使用 Master 操作和执行命令。 Master 节点扮演的角色相当于 K8s 的大脑,其重要性可想而知,因此建议部署 3 台 Master 节点保证 K8s 的高可用性。 kube-api-server:http rest 接口服务,与 K8s 其他组件通信,负责 K8s 资源的 CURD 的操作入口;">K8s Get Started | 秋河落叶 - +

    K8s Get Started

    🏠 首页 / diff --git a/kubernetes/kubeadm-install-k8s-docker/index.html b/kubernetes/kubeadm-install-k8s-docker/index.html index 10e8878c..7d586616 100644 --- a/kubernetes/kubeadm-install-k8s-docker/index.html +++ b/kubernetes/kubeadm-install-k8s-docker/index.html @@ -9,7 +9,7 @@ 随着 kubeadm & k8s 版本的更新,安装过程可能会有所不同,截至目前,本文档使用的是 kubeadm v1.28.3 & k8s v1.28.3 版本; 本文档使用的操作系统是 Ubuntu 22.04,其他操作系统可能会有所不同。 要求 # 至少一台物理机或虚拟机(例如:Ubuntu 22.04)作为集群节点,最少 2 核 2G 内存; 多节点之前网络互通,且节点主机名不冲突; Master 节点需要开放以下端口:6443、2379-2380、10250、10251、10252; 准备工作 # 禁用交换分区: # 临时禁用交换分区 sudo swapoff -a vim /etc/fstab # 注释掉 swap 分区的配置 配置系统: cat <Kubeadm Install K8s Docker | 秋河落叶 - +

    Kubeadm Install K8s Docker

    🏠 首页 / diff --git a/kubernetes/kubeadm-install-k8s/index.html b/kubernetes/kubeadm-install-k8s/index.html index f87e8232..8e47e565 100644 --- a/kubernetes/kubeadm-install-k8s/index.html +++ b/kubernetes/kubeadm-install-k8s/index.html @@ -9,7 +9,7 @@ 随着 kubeadm & k8s 版本的更新,安装过程可能会有所不同,截至目前,本文档使用的是 kubeadm v1.28.3 & k8s v1.28.3 版本; 本文档使用的操作系统是 Ubuntu 22.04,其他操作系统可能会有所不同。 要求 # 至少一台物理机或虚拟机(例如:Ubuntu 22.04)作为集群节点,最少 2 核 2G 内存; 多节点之前网络互通,且节点主机名不冲突; Master 节点需要开放以下端口:6443、2379-2380、10250、10251、10252; 准备工作 # 禁用交换分区: # 临时禁用交换分区 sudo swapoff -a vim /etc/fstab # 注释掉 swap 分区的配置 配置系统: cat <Kubeadm Install K8s | 秋河落叶 - +

    Kubeadm Install K8s

    🏠 首页 / diff --git a/kubernetes/kubeadm-upgrade/index.html b/kubernetes/kubeadm-upgrade/index.html index 0b939744..85e22b22 100644 --- a/kubernetes/kubeadm-upgrade/index.html +++ b/kubernetes/kubeadm-upgrade/index.html @@ -7,7 +7,7 @@ 注意: 不支持跨主版本升级,如 1.27.x 升级到 1.29.x,中间必须先升级到 1.28.x 主版本更新必须先升级到最新的次版本,如 1.28.3 升级到 1.28.4,然后再升级到 1.29.x 升级步骤 # 控制节点(control plane node)升级 工作节点(worker node)升级 升级过程 # 1、升级至当前主版本的最新次版本 # sudo apt update sudo apt-cache madison kubeadm 以上命令后,将可以得到类似如下输出: $ sudo apt-cache madison kubeadm kubeadm | 1.28.4-1.1 | https://pkgs.k8s.io/core:/stable:/v1.28/deb Packages kubeadm | 1.28.3-1.1 | https://pkgs.k8s.io/core:/stable:/v1.28/deb Packages kubeadm | 1.">Kubeadm Upgrade | 秋河落叶 - +

    Kubeadm Upgrade

    🏠 首页 / diff --git a/kubernetes/kubebuilder-inaction/index.html b/kubernetes/kubebuilder-inaction/index.html index 5c409fa3..127906b6 100644 --- a/kubernetes/kubebuilder-inaction/index.html +++ b/kubernetes/kubebuilder-inaction/index.html @@ -7,7 +7,7 @@ 安装 # 条件 # kustomize curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash controller-gen go install sigs.k8s.io/controller-tools/cmd/controller-gen@latest ⚠️ 注意:以上命令都是直接下载了对应命令工具最新的版本,在使用 kubebuilder 创建项目之后,在 Makefile 文件中会指定 kustomize 和 controller-gen 的版本,为了避免不兼容,推荐下载对应指定的版本。 使用以下命令安装 kubebuilder: # download kubebuilder and install locally. GOOS=$(go env GOOS) GOARCH=$(go env GOARCH) curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/$GOOS/$GOARCH chmod +x kubebuilder && mv kubebuilder /usr/local/bin/ 代码自动补全:'>Kubebuilder Inaction | 秋河落叶 - +

    Kubebuilder Inaction

    🏠 首页 / diff --git a/kubernetes/kubectl/index.html b/kubernetes/kubectl/index.html index 957de485..31ad671c 100644 --- a/kubernetes/kubectl/index.html +++ b/kubernetes/kubectl/index.html @@ -9,7 +9,7 @@ $ vim ~/.bashrc ... source <(kubectl completion bash) 命令别名 # alias k=kubectl complete -F __start_kubectl k Troubleshooting # Q1. _get_comp_words_by_ref: command not found # 解决方法: apt install bash-completion -y source /usr/share/bash-completion/bash_completion source <(kubectl completion bash) « kubebuilder 实战 » Kubernetes 0-1 Kubernetes最佳实践">Kubectl | 秋河落叶 - +

    Kubectl

    🏠 首页 / diff --git a/kubernetes/kubernetes-best-practice/index.html b/kubernetes/kubernetes-best-practice/index.html index 58e04df8..ee859f5c 100644 --- a/kubernetes/kubernetes-best-practice/index.html +++ b/kubernetes/kubernetes-best-practice/index.html @@ -5,7 +5,7 @@ Kubernetes 0-1 Kubernetes最佳实践 # https://github.com/learnk8s/kubernetes-production-best-practices « kubectl » Kubernetes Dashboard">Kubernetes Best Practice | 秋河落叶 - +

    Kubernetes Best Practice

    🏠 首页 / diff --git a/kubernetes/kubernetes-dashboard/index.html b/kubernetes/kubernetes-dashboard/index.html index 2ccbeb1b..faa70fa5 100644 --- a/kubernetes/kubernetes-dashboard/index.html +++ b/kubernetes/kubernetes-dashboard/index.html @@ -9,7 +9,7 @@ ![image-20191223173827866]( C:\Users\dp\AppData\Roaming\Typora\typora-user-images\image-20191223173827866.png) # Copyright 2017 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.'>Kubernetes Dashboard | 秋河落叶 - +

    Kubernetes Dashboard

    🏠 首页 / diff --git a/kubernetes/kubernetes-naming-constraints/index.html b/kubernetes/kubernetes-naming-constraints/index.html index 8c4528ee..f7218ee1 100644 --- a/kubernetes/kubernetes-naming-constraints/index.html +++ b/kubernetes/kubernetes-naming-constraints/index.html @@ -17,7 +17,7 @@ 引入包: go get k8s.io/apimachinery/pkg/util/validation 示例代码: package main import ( "k8s.'>Kubernetes Naming Constraints | 秋河落叶 - +

    Kubernetes Naming Constraints

    🏠 首页 / diff --git a/kubernetes/kubernetes/index.html b/kubernetes/kubernetes/index.html index 8b2957bf..3f03bbab 100644 --- a/kubernetes/kubernetes/index.html +++ b/kubernetes/kubernetes/index.html @@ -3,7 +3,7 @@ » KubeVirt 创建 Windows 虚拟机">Kubernetes | 秋河落叶 - +

    Kubernetes

    🏠 首页 / diff --git a/kubernetes/kubevirt-create-windows-vm/index.html b/kubernetes/kubevirt-create-windows-vm/index.html index 2fda2484..9a93a61b 100644 --- a/kubernetes/kubevirt-create-windows-vm/index.html +++ b/kubernetes/kubevirt-create-windows-vm/index.html @@ -1,7 +1,7 @@ Kubevirt Create Windows Vm | 秋河落叶 - +

    Kubevirt Create Windows Vm

    🏠 首页 / diff --git a/kubernetes/kubevirt-practice/index.html b/kubernetes/kubevirt-practice/index.html index f63c7e11..2480b61e 100644 --- a/kubernetes/kubevirt-practice/index.html +++ b/kubernetes/kubevirt-practice/index.html @@ -3,7 +3,7 @@ 安装 # 部署 K8s 资源 # 最新版本 export KUBEVIRT_VERSION=$(curl -s https://api.github.com/repos/kubevirt/kubevirt/releases/latest | jq -r .">Kubevirt Practice | 秋河落叶 - +

    Kubevirt Practice

    🏠 首页 / diff --git a/kubernetes/kustomize/index.html b/kubernetes/kustomize/index.html index 6107ba3a..eef29f8a 100644 --- a/kubernetes/kustomize/index.html +++ b/kubernetes/kustomize/index.html @@ -9,7 +9,7 @@ kubectl kustomize 要应用这些资源,使用 --kustomize 或 -k 参数来执行 kubectl apply: kubectl apply -k 生成资源 # ConfigMap 和 Secret 包含其他 Kubernetes 对象(如 Pod)所需要的配置或敏感数据。 ConfigMap 或 Secret 中数据的来源往往是集群外部,例如某个 .properties 文件或者 SSH 密钥文件。 Kustomize 提供 secretGenerator 和 configMapGenerator,可以基于文件或字面值来生成 Secret 和 ConfigMap。 configMapGenerator # 要基于文件来生成 ConfigMap,可以在 configMapGenerator 的 files 列表中添加表项。 下面是一个根据 .">Kustomize | 秋河落叶 - +

    Kustomize

    🏠 首页 / diff --git a/kubernetes/liveness-readiness-probe/index.html b/kubernetes/liveness-readiness-probe/index.html index 32f8bc57..d06f91ae 100644 --- a/kubernetes/liveness-readiness-probe/index.html +++ b/kubernetes/liveness-readiness-probe/index.html @@ -39,7 +39,7 @@ 使用方式: livenessProbe: tcpSocket: port: 80 一般就绪探针会在启动容器一段时间后才开始第一次的就绪探测,之后做周期性探测。所以在定义就绪指针时,会给以下几个参数: initialDelaySeconds:在初始化容器多少秒后开始第一次就绪探测; timeoutSeconds:如果该次就绪探测超过多少秒后还未成功,判定为超时,该次探测失败,Pod不就绪。默认值1,最小值1; periodSeconds:如果Pod未就绪,则每隔多少秒周期性的做就绪探测。默认值10,最小值1; failureThreshold:如果容器之前探测成功,后续连续几次探测失败,则确定容器未就绪。默认值3,最小值1; successThreshold:如果容器之前探测失败,后续连续几次探测成功,则确定容器就绪。默认值1,最小值1。 使用示例 # 目前我在docker hub有一个测试镜像:poneding/helloweb:v1,容器启动后,有一个健康检查路由/healthz/return200,访问该路由状态码返回200;有一个检查路由/health/return404,访问该路由状态码返回404。">Liveness Readiness Probe | 秋河落叶 - +

    Liveness Readiness Probe

    🏠 首页 / diff --git a/kubernetes/local-storageclass/index.html b/kubernetes/local-storageclass/index.html index f22a8937..4dc0dcde 100644 --- a/kubernetes/local-storageclass/index.html +++ b/kubernetes/local-storageclass/index.html @@ -11,7 +11,7 @@ 与 hostPath 卷相比,local 卷能够以持久和可移植的方式使用,而无需手动将 Pod 调度到节点。系统通过查看 PersistentVolume 的节点亲和性配置,就能了解卷的节点约束。 然而,local 卷仍然取决于底层节点的可用性,并不适合所有应用程序。 如果节点变得不健康,那么 local 卷也将变得不可被 Pod 访问。使用它的 Pod 将不能运行。 使用 local 卷的应用程序必须能够容忍这种可用性的降低,以及因底层磁盘的耐用性特征而带来的潜在的数据丢失风险。 创建 local-storage 存储类 # apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: local-storage provisioner: kubernetes.io/no-provisioner volumeBindingMode: WaitForFirstConsumer 手动创建 PV/PVC # 1、使用 local 卷时,你需要设置 PersistentVolume 对象的 nodeAffinity 字段。 Kubernetes 调度器使用 PersistentVolume 的 nodeAffinity 信息来将使用 local 卷的 Pod 调度到正确的节点;">Local Storageclass | 秋河落叶 - +

    Local Storageclass

    🏠 首页 / diff --git a/kubernetes/metallb/index.html b/kubernetes/metallb/index.html index adc59c44..69aa2cbc 100644 --- a/kubernetes/metallb/index.html +++ b/kubernetes/metallb/index.html @@ -19,7 +19,7 @@ apiVersion: v1 kind: ConfigMap metadata: namespace: metallb-system name: config data: config: | address-pools: - name: default protocol: layer2 addresses: - 192.168.115.140-192.168.115.199 注意:IP池的网络需要和K8s集群的IP处于同一网段,我的K8s集群网络是192.168.115.13x,这里IP池则是给到192.168.115.140-192.168.115.199的范围。 执行命令: kubectl apply -f metallb-namespace.yaml kubectl create secret generic -n metallb-system memberlist --from-literal=secretkey="$(openssl rand -base64 128)" kubectl apply -f metallb.'>Metallb | 秋河落叶 - +

    Metallb

    🏠 首页 / diff --git a/kubernetes/nfs-as-pvc/index.html b/kubernetes/nfs-as-pvc/index.html index bd8a3fc7..b9463f99 100644 --- a/kubernetes/nfs-as-pvc/index.html +++ b/kubernetes/nfs-as-pvc/index.html @@ -9,7 +9,7 @@ 部署 nfs # 以下命令需要root权限,示例中机器IP为192.168.115.137。 安装 nfs: # Ubuntu & Debian apt install nfs-kernel-server -y # CentOS yum install nfs-util -y 创建共享目录: mkdir /nfs/data -p 修改 nfs 的默认配置,在文末添加配置: vim /etc/exports /nfs/data *(rw,sync,no_root_squash) 其中: /nfs/data:共享目录 *:对所有开放访问,可以配置成网段,IP,域名等 rw:读写权限 sync:文件同时写入磁盘和内存 no_root_squash:当登录 NFS 主机使用共享目录的使用者是 root 时,其权限将被转换成为匿名使用者,通常它的 UID 与 GID,都会变成 nobody 身份 重启 rpc,nfs 需要向 rpc 注册: systemctl restart rpcbind.">Nfs as Pvc | 秋河落叶 - +

    Nfs as Pvc

    🏠 首页 / diff --git a/kubernetes/pod-understood/index.html b/kubernetes/pod-understood/index.html index 04ff237e..df0cc9e3 100644 --- a/kubernetes/pod-understood/index.html +++ b/kubernetes/pod-understood/index.html @@ -7,7 +7,7 @@ Pod 的容器运行时由容器引擎提供,默认的容器引擎是 Docker;并且 K8s 管理的是 Pod,而不是容器。 一个 Pod 内部的容器共享: 存储:一个 Pod 可以指定一组共享存储卷。 网络:每个 Pod 分配一个唯一 IP(集群内 IP),共享网络命名空间,包括 IP 地址和网络端口。Pod 内的容器可以使用 localhost 互相通信,集群内 Pod 与 Pod通信可以使用 Pod 分配的 IP,但是由于 Pod 的 IP 是随机分配的,这种互通信的方式不太适合使用。 尽管一个 Pod 内可以包含多个 Pod,但我们在部署应用容器时的最佳实践是一个 Pod 里面只包含一个应用容器作为主容器,其他容器为主容器服务,称之为辅助容器。例如主容器崩溃了,会有一个辅助容器去重启主容器。辅助容器可以有也可以没有,因为 Pod 里面容器的生命周期可以被 Pod 的生命周期取代,而 Pod 的生命周期可以通过 Pod 管理器来管理维护。">Pod Understood | 秋河落叶 - +

    Pod Understood

    🏠 首页 / diff --git a/kubernetes/prgramming-kubernetes/index.html b/kubernetes/prgramming-kubernetes/index.html index 73de0ce6..1ea31a87 100644 --- a/kubernetes/prgramming-kubernetes/index.html +++ b/kubernetes/prgramming-kubernetes/index.html @@ -13,7 +13,7 @@ code-generator 使用 B站 code-generator 介绍 mkdir hack vim hack/boilerplate.go.txt /* Copyright 2022 programming-kubernetes. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.'>Prgramming Kubernetes | 秋河落叶 - +

    Prgramming Kubernetes

    🏠 首页 / diff --git a/kubernetes/prometheus-collect-kong-metrics/index.html b/kubernetes/prometheus-collect-kong-metrics/index.html index dd9f02f2..11b1b414 100644 --- a/kubernetes/prometheus-collect-kong-metrics/index.html +++ b/kubernetes/prometheus-collect-kong-metrics/index.html @@ -15,7 +15,7 @@ 可以看到一已经生成了很多kong的指标项,如http访问,nginx当前访问量等指标 « Kubernetes 编程 » Prometheus">Prometheus Collect Kong Metrics | 秋河落叶 - +

    Prometheus Collect Kong Metrics

    🏠 首页 / diff --git a/kubernetes/prometheus/index.html b/kubernetes/prometheus/index.html index fc007f57..9589f928 100644 --- a/kubernetes/prometheus/index.html +++ b/kubernetes/prometheus/index.html @@ -17,7 +17,7 @@ 处理流程: 配置资源目标或应用抓取; 抓取资源或应用指标数据; 制定报警规则,推送报警; 灵活查询语言,结合Grafana展示 Installation & Start Up # 1. 以服务进程运行Prometheus # ​ 在ubuntu系统上安装Prometheus,一般有两种方式 第一种,安装命令如下: wget https://github.com/prometheus/prometheus/releases/download/v2.13.1/prometheus-2.13.1.linux-amd64.tar.gz tar xvfz prometheus-2.13.1.linux-amd64.tar.gz # 启动Prometheus cd prometheus-2.">Prometheus | 秋河落叶 - +

    Prometheus

    🏠 首页 / diff --git a/kubernetes/pvc-expansion/index.html b/kubernetes/pvc-expansion/index.html index e16f003c..c7c033a3 100644 --- a/kubernetes/pvc-expansion/index.html +++ b/kubernetes/pvc-expansion/index.html @@ -15,7 +15,7 @@ allowVolumeExpansion: true # 允许卷扩充 之后再次执行 PVC 扩容的操作即可。 « Prometheus » 了解 Secret">Pvc Expansion | 秋河落叶 - +

    Pvc Expansion

    🏠 首页 / diff --git a/kubernetes/secret-understood/index.html b/kubernetes/secret-understood/index.html index 74277a36..c5c004d7 100644 --- a/kubernetes/secret-understood/index.html +++ b/kubernetes/secret-understood/index.html @@ -9,7 +9,7 @@ 作为环境变量传递给容器 作为文件挂载到容器的 Volume Secret 会存储在 Pod 所调度的节点的内存中,而不是写入磁盘。 Pod 默认生成的 Secret # 每个 Pod 都会被自动挂载一个 Secret 卷,只需要使用 kubectl desribe pod 命令就能看到一个名称类似 default-token-n4q6m 的 Secret,Secret 也是一种 K8s 资源,所以,可以使用 kubectl get secret 或 kubectl describe secret 获取查看。 从上面图例可以看出,Pod 默认生成的 Secret 会包含三个配置项:ca.crt、namespace、token。其实这三个配置项是 Pod 内部安全访问Kubernetes API 服务的所有信息,而在 kubectl describe pod 的时候,你可以看到 Secret 所挂载的具体目录在 /var/run/secrets/kubernetes.">Secret Understood | 秋河落叶 - +

    Secret Understood

    🏠 首页 / diff --git a/kubernetes/service-understood/index.html b/kubernetes/service-understood/index.html index d7f08313..19e968b4 100644 --- a/kubernetes/service-understood/index.html +++ b/kubernetes/service-understood/index.html @@ -9,7 +9,7 @@ 为什么有 Service # 集群中部署了 Pod,应用是成功的部署起来了,但是只是至此的话,Pod 提供服务访问存在以下一些问题。 Pod 是短暂的,可能会被销毁或重新调度,这使得 Pod 的 IP 是随时变动和更新的; 部署多个 Pod 的伸缩问题,流量分配问题; 集群外部客户端无法直接访问 Pod。 这时候就需要 Service,Pod 作为 Service 的后端提供服务。所以我们可以想象,Service 需要完成的事情: 服务发现,通过 Pod 的 lable 查找目标 Pod,将查找的 Pod 的注册到自己的后端列表,Pod 的 IP 信息发生更改,后端列表也同步更新; 负载均衡,请求到达 Service 之后,将请求均衡转发的后端列表; 服务暴露:对外提供统一的请求地址。 创建 Service # 在创建 Sercvice 之前我们首先创建 service 代理的 Pod,nginx-pod.">Service Understood | 秋河落叶 - +

    Service Understood

    🏠 首页 / diff --git a/kubernetes/telepresence/index.html b/kubernetes/telepresence/index.html index acc6969b..16a64e85 100644 --- a/kubernetes/telepresence/index.html +++ b/kubernetes/telepresence/index.html @@ -5,7 +5,7 @@ Telepresence # Telepresence是一款 « 了解 Service » Kubernetes 0-1 使用preStop优雅终止Pod">Telepresence | 秋河落叶 - +

    Telepresence

    🏠 首页 / diff --git a/kubernetes/terminate-pod-gracefully/index.html b/kubernetes/terminate-pod-gracefully/index.html index f880b43b..e74ff029 100644 --- a/kubernetes/terminate-pod-gracefully/index.html +++ b/kubernetes/terminate-pod-gracefully/index.html @@ -5,7 +5,7 @@ Kubernetes 0-1 使用preStop优雅终止Pod # Kubernetes允许Pod终止之前,执行自定义逻辑。 字段定义 # 字段定义:pod.spec.containers.lifecycle.preStop $ kubectl explain pod.spec.containers.lifecycle.preStop KIND: Pod VERSION: v1 RESOURCE: preStop DESCRIPTION: PreStop is called immediately before a container is terminated due to an API request or management event such as liveness/startup probe failure, preemption, resource contention, etc. The handler is not called if the container crashes or exits. The reason for termination is passed to the handler.">Terminate Pod Gracefully | 秋河落叶 - +
    Terminate Pod Gracefully

    🏠 首页 / diff --git a/kubernetes/terraform/index.html b/kubernetes/terraform/index.html index 55f6ca5b..8cb6c455 100644 --- a/kubernetes/terraform/index.html +++ b/kubernetes/terraform/index.html @@ -1,7 +1,7 @@ Terraform | 秋河落叶 - +

    Terraform

    🏠 首页 / diff --git a/kubernetes/velero-minio-backup-restore-volume/index.html b/kubernetes/velero-minio-backup-restore-volume/index.html index 2ca52d98..6edd0701 100644 --- a/kubernetes/velero-minio-backup-restore-volume/index.html +++ b/kubernetes/velero-minio-backup-restore-volume/index.html @@ -5,7 +5,7 @@ Velero + Minio 备份与恢复 # 安装 Minio # docker run -d --name minio \\ -p 9000:9000 \\ -p 9001:9001 \\ -e MINIO_ROOT_USER=minio \\ -e MINIO_ROOT_PASSWORD=minio \\ -v /minio-data:/data \\ quay.io/minio/minio:latest server /data --console-address ":9001" 创建 Bucket # 设置 Region # 点击保存后,会出现一个横幅,点击横幅上的 Restart 即可。 创建 AccessKey # 保存 AccessKey 和 SecretKey 到文件 credentials-velero: [default] aws_access_key_id = aws_secret_access_key = 安装 Velero CLI # # linux wget Velero Minio Backup Restore Volume | 秋河落叶 - +

    Velero Minio Backup Restore Volume

    🏠 首页 / diff --git a/kubernetes/volume-understood/index.html b/kubernetes/volume-understood/index.html index ca4041ad..7f8f114f 100644 --- a/kubernetes/volume-understood/index.html +++ b/kubernetes/volume-understood/index.html @@ -13,7 +13,7 @@ 容器与 Volume 的简单关系: Volume 定义 # 定义在 pod.spec.container 属性下: kind: Pod ... spec: container: ... volumeMounts: - mountPath: name: subPath: volumes: - name: : ... mountPath: 容器内的目录,如果不存在则创建该目录 subPath:默认会将 mountPath 直接映射到 volume 的根目录,使用 subpath 映射到 volume 特定的目录。 Volume 类型 # Volume 有多种类型,有的可以直接在集群中使用,有的则需要第三方服务或云平台的支持。简单罗列几种常见类型,更多了类型参考: https://kubernetes.">Volume Understood | 秋河落叶 - +

    Volume Understood

    🏠 首页 / diff --git a/kubernetes/vpa/index.html b/kubernetes/vpa/index.html index 4bf4574c..c98a0087 100644 --- a/kubernetes/vpa/index.html +++ b/kubernetes/vpa/index.html @@ -11,7 +11,7 @@ Initial:VPA只在pod创建时分配资源请求,以后不会更改它们。 Off:VPA不会自动更改pods的资源需求。计算并可以在VPA对象中检查建议。 示例 # « 了解 Volume">Vpa | 秋河落叶 - +

    Vpa

    🏠 首页 / diff --git a/linux/certbot-auto-gen-cert/index.html b/linux/certbot-auto-gen-cert/index.html index a159ce64..f427c727 100644 --- a/linux/certbot-auto-gen-cert/index.html +++ b/linux/certbot-auto-gen-cert/index.html @@ -7,7 +7,7 @@ 提前已经将域名解析到本服务器; 本服务器端口 80、443 处于未被占用的状态,如果 web 服务占用了 80 端口,需要临时关闭。 certbot-auto certonly --standalone --email poneding@gmail.com -d test.poneding.com 以上命令执行完成后,将会在 /etc/letsencrypt/live 目录下生成域名证书文件。默认证书有效期为 3 个月。 nginx 配置证书 # 参考示例: server { listen 80; server_name abc.com; rewrite ^(.*) https://test.poneding.com permanent; } server{ listen 443 ssl default_server; listen [::]:443 ssl default_server; ssl_certificate /etc/letsencrypt/live/test.poneding.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/test.">Certbot Auto Gen Cert | 秋河落叶 - +

    Certbot Auto Gen Cert

    🏠 首页 / diff --git a/linux/history-with-date/index.html b/linux/history-with-date/index.html index ed9d71c9..eee67631 100644 --- a/linux/history-with-date/index.html +++ b/linux/history-with-date/index.html @@ -9,7 +9,7 @@ 1 ls 2 top 4 docker ps 5 df 6 ls 那么,如果想知道历史执行的 command 的时间该怎么做呢。 按照如下步骤,一步一步来。 首先设置 HISTTIMEFORMAT 变量 $ HISTTIMEFORMAT="%d/%m/%y %T " # OR $ echo 'export HISTTIMEFORMAT="%d/%m/%y %T "' >> ~/.bash_profile 使用 source 命令加载 HISTTIMEFORMAT 变量到当前 shell 命令窗 $ . ~/.bash_profile # OR $ source ~/.bash_profile 再次运行 history 命令,已经可以输出附带执行时间的 history 了。 1 root 2020/02/18 11:28:19 ls 2 root 2020/02/18 11:28:21 top 4 root 2020/02/18 11:28:58 docker ps 5 root 2020/02/18 11:34:09 df 6 root 2020/02/18 11:34:15 ls « certbot-auto 生成证书'>History With Date | 秋河落叶 - +

    History With Date

    🏠 首页 / diff --git a/linux/index.html b/linux/index.html index c37d1713..ddcc1d30 100644 --- a/linux/index.html +++ b/linux/index.html @@ -21,7 +21,7 @@ 使用 SSH Tunnel 连接中间件 tee 保存 stderr 到文件 vim 使用">Linux | 秋河落叶 - +

    Linux

    🏠 首页 / Linux

    Linux diff --git a/linux/linux-commands/index.html b/linux/linux-commands/index.html index 592fe9ea..5ccde8c8 100644 --- a/linux/linux-commands/index.html +++ b/linux/linux-commands/index.html @@ -19,7 +19,7 @@ 实例 # 把 textfile1 的档案内容加上行号后输入 textfile2 这个档案里 cat -n textfile1 > textfile2 把 textfile1 和 textfile2 的档案内容加上行号(空白行不加)之后将内容附加到 textfile3 里。 cat -b textfile1 textfile2 >> textfile3 清空/etc/test.">Linux Commands | 秋河落叶 - +
    Linux Commands

    🏠 首页 / diff --git a/linux/linux-common-commands/index.html b/linux/linux-common-commands/index.html index 6349d534..cfe8b018 100644 --- a/linux/linux-common-commands/index.html +++ b/linux/linux-common-commands/index.html @@ -1,7 +1,7 @@ Linux Common Commands | 秋河落叶 - +

    Linux Common Commands

    🏠 首页 / diff --git a/linux/linux-enable-crontab-log/index.html b/linux/linux-enable-crontab-log/index.html index 11982d05..fec08d46 100644 --- a/linux/linux-enable-crontab-log/index.html +++ b/linux/linux-enable-crontab-log/index.html @@ -7,7 +7,7 @@ You need to edit the /etc/rsyslog.conf or /etc/rsyslog.d/50-default.conf (on Ubuntu) file and make sure you have the following line uncommented or add it if it is missing: cron.* /var/log/cron.log Then restart rsyslog and cron: sudo service rsyslog restart sudo service cron restart Cron jobs will log to /var/log/cron.">Linux Enable Crontab Log | 秋河落叶 - +

    Linux Enable Crontab Log

    🏠 首页 / diff --git a/linux/linux-secure-login/index.html b/linux/linux-secure-login/index.html index 512de224..15ea6c4b 100644 --- a/linux/linux-secure-login/index.html +++ b/linux/linux-secure-login/index.html @@ -13,7 +13,7 @@ 创建用户(以下都基于 ubuntu 系统操作) adduser dp 用户赋权 此时创建的用户不能使用 sudo 权限,考虑将用户加入 sudo 组 usermod -a -G sudo dp 并且,为了避免使用 sudo 权限需要时不时的输入密码的麻烦,进行免密设置。在 /etc/sudoers 文件中新增行。 dp ALL=(ALL) NOPASSWD: ALL 到了这一步,应该尝试使用新用户登录系统,如果成功登录再往下继续。 禁用 root 登录 打开 /etc/ssh/sshd_config 文件,找到 PermitRootLogin 项,修改该项成如下:">Linux Secure Login | 秋河落叶 - +

    Linux Secure Login

    🏠 首页 / diff --git a/linux/shell-command-interval-character/index.html b/linux/shell-command-interval-character/index.html index 523e206b..9a876449 100644 --- a/linux/shell-command-interval-character/index.html +++ b/linux/shell-command-interval-character/index.html @@ -31,7 +31,7 @@ # 不管有没有赚到钱,都要回家过年 sh earn_money.sh; sh go_home.sh 5. >: 输出到指定文件(文件不存在则创建文件,文件存在则会覆盖文件内容) echo "Hello World" > hello.'>Shell Command Interval Character | 秋河落叶 - +

    Shell Command Interval Character

    🏠 首页 / diff --git a/linux/shell/index.html b/linux/shell/index.html index 2977f46d..0779f693 100644 --- a/linux/shell/index.html +++ b/linux/shell/index.html @@ -21,7 +21,7 @@ unset my_name 变量类型: 局部变量:在脚本或命令中定义,仅在当前shell实例有效 环境变量:所有shell实例有效 shell变量:shell程序设置的特殊变量 shell 字符串 # 单引号与双引号的区别: 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的; 单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用; 双引号里可以有变量; 双引号里可以出现转义字符 my_name="Ding Peng" hello_string="Hllo,\"$my_name\"!" echo $hello_string 获取字符串长度:'>Shell | 秋河落叶 - +

    Shell

    🏠 首页 / diff --git a/linux/ssh-tunnel-connect-middleware/index.html b/linux/ssh-tunnel-connect-middleware/index.html index 52ff8b04..fa80d7a5 100644 --- a/linux/ssh-tunnel-connect-middleware/index.html +++ b/linux/ssh-tunnel-connect-middleware/index.html @@ -13,7 +13,7 @@ 在连接配置页面 Main,输入 Sql Server 连接的基本信息,这里 host 直接使用原本的数据库 host 即可。 切至 SSH,勾选 Use SSH Tunnel,输入跳板机的连接配置即可。 配置完成,Ok连接即可。">SSH Tunnel Connect Middleware | 秋河落叶 - +

    SSH Tunnel Connect Middleware

    🏠 首页 / diff --git a/linux/tee-keep-stderr/index.html b/linux/tee-keep-stderr/index.html index 419a7ca6..6ebfcf21 100644 --- a/linux/tee-keep-stderr/index.html +++ b/linux/tee-keep-stderr/index.html @@ -11,7 +11,7 @@ echo exec 1.sh start! && \ cat hello.txt && \ echo exec 1.sh end! 假如 hello.txt 文件不存在,执行 1.sh 文件中的 cat 命令将报错。如果我们想将执行 1.sh 文件的输出写入到一个 log 文件,例如: sh 1.sh | tee 1.log 执行以上命令,控制台的输出是: $ sh 1.sh | tee 1.log exec 1.sh start! cat: hello.txt: No such file or directory 但是写入到 1.">Tee Keep Stderr | 秋河落叶 - +

    Tee Keep Stderr

    🏠 首页 / diff --git a/linux/vim-common-commands/index.html b/linux/vim-common-commands/index.html index a52c7c14..6bc0419b 100644 --- a/linux/vim-common-commands/index.html +++ b/linux/vim-common-commands/index.html @@ -19,7 +19,7 @@ ecs+:set smartcase:如果查找字符中存在大写则自动大小写敏感 查找当前字符: 光标移动 # h:向左 j:向下 k:向上 l:向右 替换 # :s/jay/dp/g 替换当前行中所有匹配 jay => dp :1,$s/jay/dp/g 替换所有 :1,5s/jay/dp/g 替换 1 到 5 行 翻页 # ctrl+f:下一页 ctrl+d:下半页 ctrl+b:上一页 ctrl+u:上半页 行操作 # dG:删除当前行到尾行">Vim Common Commands | 秋河落叶 - +

    Vim Common Commands

    🏠 首页 / diff --git a/middleware/elasticsearch/index.html b/middleware/elasticsearch/index.html index 5fde3935..a75240c9 100644 --- a/middleware/elasticsearch/index.html +++ b/middleware/elasticsearch/index.html @@ -19,7 +19,7 @@ GET /_cat/indices?v 删除索引 # DELETE /users 创建文档 # 这个操作是在单个索引下的 POST /users/_doc # 一定需要body,否则报错 body: { "name": "dp", "age": 18 } 上面这个文档创建时会生成随机 ID(返回结果中的 _id),不便维护,使用下面的方法自定义文档 ID,此时由于 ID 自定义了,就要求幂等,所以可以使用 PUT 方法 POST | PUT /users/_doc/1002 PUT /users/_create/1003 # 一定需要body,否则报错 body: { "name": "dp", "age": 18 } 查询文档 # 获取单个文档'>Elasticsearch | 秋河落叶 - +

    Elasticsearch

    🏠 首页 / diff --git a/middleware/index.html b/middleware/index.html index e6a4d27a..ae965c5a 100644 --- a/middleware/index.html +++ b/middleware/index.html @@ -9,7 +9,7 @@ MySQL Postgres Redis">Middleware | 秋河落叶 - +

    Middleware

    🏠 首页 / 数据中间件

    数据中间件 diff --git a/middleware/mongodb/index.html b/middleware/mongodb/index.html index 33b29175..baf5352b 100644 --- a/middleware/mongodb/index.html +++ b/middleware/mongodb/index.html @@ -15,7 +15,7 @@ mongodb 没有开启权限验证之前,使用 mongo 命令可以直接连接本地 mongodb; sudo mongo 使用 db.createUser 命令创建 dba 用户,为 dba 用户添加所有 database 的管理员权限; > db.createUser({user:"dba",pwd:"[your pass]",roles:[ {role:"readWriteAnyDatabase",db:"admin"},{role:"dbAdminAnyDatabase",db:"admin"},{role:"userAdminAnyDatabase",db:"admin"},{role:"clusterAdmin",db:"admin"},{role:"restore",db:"admin"},{role:"backup",db:"admin"} ]}) Successfully added user: { "user" : "dba", "roles" : [ // .'>Mongodb | 秋河落叶 - +
    Mongodb

    🏠 首页 / diff --git a/middleware/mysql/index.html b/middleware/mysql/index.html index 242088c1..753975e1 100644 --- a/middleware/mysql/index.html +++ b/middleware/mysql/index.html @@ -7,7 +7,7 @@ 下载完成后,双击 msi 文件安装。 Ubuntu 安装 MySQL # sudo apt update sudo apt install mysql-server -y # 只安装 mysql 客户端 sudo apt install mysql-client -y Docker 安装 MySQL # docker run -d --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7 Troubleshooting # Q1. root 用户本地登录 # 使用命令 mysql -u root -p,输入密码后登录失败,提示如下: Access denied for user 'root'@'localhost' 解决方案::">Mysql | 秋河落叶 - +

    Mysql

    🏠 首页 / diff --git a/middleware/postgres/index.html b/middleware/postgres/index.html index 1f40b4b9..a294d061 100644 --- a/middleware/postgres/index.html +++ b/middleware/postgres/index.html @@ -7,7 +7,7 @@ GRANT USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public TO ; 修改用户密码 alter user postgres with password 'admin123'; « MySQL » Redis">Postgres | 秋河落叶 - +

    Postgres

    🏠 首页 / diff --git a/middleware/redis/index.html b/middleware/redis/index.html index 93235f6b..0deafb79 100644 --- a/middleware/redis/index.html +++ b/middleware/redis/index.html @@ -3,7 +3,7 @@ bind 127.0.0.1 # 注释掉这部分,使 redis 可以外部访问 daemonize no # 用守护线程的方式启动 requirepass your_pwd # 给 redis 设置密码 appendonly yes # redis 持久化 默认是 no tcp-keepalive 300 # 防止出现远程主机强迫关闭了一个现有的连接的错误 默认是 300 « Postgres">Redis | 秋河落叶 - +

    Redis

    🏠 首页 / diff --git a/os/index.html b/os/index.html index 2ae0d3ed..249e05c9 100644 --- a/os/index.html +++ b/os/index.html @@ -9,7 +9,7 @@ openssl Ubuntu Windows 使用姿势">Os | 秋河落叶 - +

    Os

    🏠 首页 / 操作系统

    操作系统 diff --git a/os/macos/index.html b/os/macos/index.html index 70dcb41c..3b26df97 100644 --- a/os/macos/index.html +++ b/os/macos/index.html @@ -7,7 +7,7 @@ sudo mkdir /etc/paths.d/mypath vim /etc/paths.d/mypath /your/path 查看端口占用并退出程序 # 有时候使用 VSCode 调试或运行程序后,无法成功推出程序,端口一直占用。 查看端口占用: # [port] 替换成你想查看的端口号,例如:sudo lsof -i tcp:8080 sudo lsof -i tcp:[port] 上述命令可以得到程序的进程 PID,退出进程:">Macos | 秋河落叶 - +
    Macos

    🏠 首页 / diff --git a/os/ohmyzsh/index.html b/os/ohmyzsh/index.html index f6e180ed..e8525f40 100644 --- a/os/ohmyzsh/index.html +++ b/os/ohmyzsh/index.html @@ -3,7 +3,7 @@ echo $SHELL /usr/bin/zsh 安装 ohmyzsh: sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" 安装 zsh-autosuggestions: git clone https://github.'>Ohmyzsh | 秋河落叶 - +

    Ohmyzsh

    🏠 首页 / diff --git a/os/openssl/index.html b/os/openssl/index.html index ac45c9aa..f4ea21a2 100644 --- a/os/openssl/index.html +++ b/os/openssl/index.html @@ -9,7 +9,7 @@ openssl:命令行工具 libcrypto:加密算法库 libssl:加密模块应用库,实现了 SSL 和 TLS 协议 对称加密 # echo test > test.txt # 加密 openssl enc -e -des3 -a -salt -in test.txt -out test.txt.enc # 解密 openssl enc -d -des3 -a -salt -in test.txt.enc -out test.txt.dec -salt:加盐,增加破解难度,使用 openssl 默认盐值 -S [salt]:指定盐值 非对称加密 # 生成密钥对 openssl genrsa -out rsa_private_key.pem 2048 openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.">Openssl | 秋河落叶 - +

    Openssl

    🏠 首页 / diff --git a/os/ubuntu/index.html b/os/ubuntu/index.html index d99133f5..bae450e0 100644 --- a/os/ubuntu/index.html +++ b/os/ubuntu/index.html @@ -5,7 +5,7 @@ Ubuntu # 终端默认使用英文 # LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=en_US.UTF-8 下载 arm64 桌面镜像 # https://cdimage.ubuntu.com/jammy/daily-live/current/jammy-desktop-arm64.iso 关闭防火墙 # sudo ufw disable sudo ufw status 修改时区 # sudo cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime sudo timedatectl set-timezone Asia/Shanghai date # 同步时间 sudo apt install ntpdate -y sudo ntpdate cn.pool.ntp.org 解决英文系统下中文显示问题 # 修改字体优先级 sudo vim /etc/fonts/conf.avail/64-language-selector-prefer.conf 注意:在 Ubuntu 23.10 或更新版本的系统上修改文件: sudo vim /etc/fonts/conf.d/64-language-selector-cjk-prefer.conf 将 `JP` 和 `KR` 所在行往下调整即可,调整成如下所示: ```xml Ubuntu | 秋河落叶 - +

    Ubuntu

    🏠 首页 / diff --git a/os/windows/index.html b/os/windows/index.html index ab1d384f..8152f970 100644 --- a/os/windows/index.html +++ b/os/windows/index.html @@ -11,7 +11,7 @@ 打开配置文件 %USERPROFILE%/.ssh/config,在该配置文件中添加配置行: ServerAliveInterval 60 2. VSCode 搭配 Remote-SSH # 配置远程访问文件 %USERPROFILE%/.ssh/config: 密钥文件进行SSH连接 # Host aliyun HostName 11.11.11.11 User root IdentityFile ~/.ssh/aliyun_key 用户密码进行SSH连接 # Host ubuntu HostName 192.">Windows | 秋河落叶 - +

    Windows

    🏠 首页 / diff --git a/reading/index.html b/reading/index.html index 96003e4e..798822e8 100644 --- a/reading/index.html +++ b/reading/index.html @@ -5,7 +5,7 @@ 阅读 # 云原生应用开发:Operator原理与实践 我的第一本算法书 深入理解计算机网络.md">Reading | 秋河落叶 - +

    Reading

    🏠 首页 / 阅读

    阅读 diff --git "a/reading/\344\272\221\345\216\237\347\224\237\345\272\224\347\224\250\345\274\200\345\217\221Operator\345\216\237\347\220\206\344\270\216\345\256\236\350\267\265/index.html" "b/reading/\344\272\221\345\216\237\347\224\237\345\272\224\347\224\250\345\274\200\345\217\221Operator\345\216\237\347\220\206\344\270\216\345\256\236\350\267\265/index.html" index 57335a05..c66792ff 100644 --- "a/reading/\344\272\221\345\216\237\347\224\237\345\272\224\347\224\250\345\274\200\345\217\221Operator\345\216\237\347\220\206\344\270\216\345\256\236\350\267\265/index.html" +++ "b/reading/\344\272\221\345\216\237\347\224\237\345\272\224\347\224\250\345\274\200\345\217\221Operator\345\216\237\347\220\206\344\270\216\345\256\236\350\267\265/index.html" @@ -17,7 +17,7 @@ client-go库抽象封装了与k8s reset api的交互,便于开发者基于k8s做二次开发。利用client-go操作k8s资源的流程基本如下: 通过kubeconfig信息构造Config实例,该实例记录了集群证书,k8s apiserver地址等信息; 根据Config实例携带的信息构建特定的客户端(clientset,dynamicset等); 利用客户端向k8s apiserver发起请求,操作k8s资源。 以下是使用 client-go 获取 pod 的代码清单: func main() { var kubernetes *string if home := homeDir(); home != "" { kubeconfig := flag.String( "kubeconfig", filePath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file", ) } else { kubeconfig := flag.'>云原生应用开发: Operator原理与实践 | 秋河落叶 - +
    云原生应用开发: Operator原理与实践

    🏠 首页 / diff --git "a/reading/\346\210\221\347\232\204\347\254\254\344\270\200\346\234\254\347\256\227\346\263\225\344\271\246/index.html" "b/reading/\346\210\221\347\232\204\347\254\254\344\270\200\346\234\254\347\256\227\346\263\225\344\271\246/index.html" index 0c694509..581b1572 100644 --- "a/reading/\346\210\221\347\232\204\347\254\254\344\270\200\346\234\254\347\256\227\346\263\225\344\271\246/index.html" +++ "b/reading/\346\210\221\347\232\204\347\254\254\344\270\200\346\234\254\347\256\227\346\263\225\344\271\246/index.html" @@ -9,7 +9,7 @@ 选择排序: func selectionSort(nums []int) []int { for i := 0; i < len(nums); i++ { min := i for j := i + 1; j < len(nums); j++ { if nums[j] < nums[min] { min = j } } nums[i], nums[min] = nums[min], nums[i] } return nums } « 云原生应用开发:Operator原理与实践 » 深入理解计算机网络.md">我的第一本算法书 | 秋河落叶 - +

    我的第一本算法书

    🏠 首页 / diff --git "a/reading/\346\267\261\345\205\245\347\220\206\350\247\243\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/index.html" "b/reading/\346\267\261\345\205\245\347\220\206\350\247\243\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/index.html" index 5baf261c..0a3aa51b 100644 --- "a/reading/\346\267\261\345\205\245\347\220\206\350\247\243\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/index.html" +++ "b/reading/\346\267\261\345\205\245\347\220\206\350\247\243\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/index.html" @@ -5,7 +5,7 @@ 二进制数的四种表示方法 # 原码 # 二进制数第一位用来表示正负符号,0 表示 +,1 表示 -。 原码就是带正负符号的二进制数,例如,+3 原码为 00000011,-3 原码为 10000011。 原码表示方法中,0 有 +0 和 -0 表现形式。 反码 # 补码 # 原码在加减法运算中的不便, 移码 # « 我的第一本算法书">深入理解计算机网络 | 秋河落叶 - +

    深入理解计算机网络

    🏠 首页 / diff --git a/rust/cargo/index.html b/rust/cargo/index.html index 2f6cef87..09d494cf 100644 --- a/rust/cargo/index.html +++ b/rust/cargo/index.html @@ -11,7 +11,7 @@ release 模式构建花费的时间较长,但是构建出来的二进制文件则要精简很多。 cargo build --release 运行项目 # cargo run 追踪 panic 位置运行: RUST_BACKTRACE=1 cargo run 创建类包 # cargo new --lib mylib 检测项目是否可以编译 # cargo check 安装可执行文件(更新) # cargo install --path .">Cargo | 秋河落叶 - +

    Cargo

    🏠 首页 / diff --git a/rust/dev-env-config/index.html b/rust/dev-env-config/index.html index 6f7b209b..590093d2 100644 --- a/rust/dev-env-config/index.html +++ b/rust/dev-env-config/index.html @@ -11,7 +11,7 @@ sudo apt install build-essential 更新 # rustup update 卸载 # rustup self uninstall 配置命令补全 # 第一种方式,zsh 添加 rust 插件: vim ~/.zshrc 找到 plugins 配置位置,追加 rust: plugins=(... rust) 第二种方式:">Dev Env Config | 秋河落叶 - +

    Dev Env Config

    🏠 首页 / diff --git a/rust/getting-started/index.html b/rust/getting-started/index.html index d44f06c2..4fd2a175 100644 --- a/rust/getting-started/index.html +++ b/rust/getting-started/index.html @@ -15,7 +15,7 @@ rustc main.rs && ./main 执行后,你应该看到输出 Hello, World!。 « Rust 开发环境配置 » 查看根目录">Getting Started | 秋河落叶 - +

    Getting Started

    🏠 首页 / diff --git a/rust/index.html b/rust/index.html index 5b588c67..9371e40c 100644 --- a/rust/index.html +++ b/rust/index.html @@ -11,7 +11,7 @@ 查看根目录 Rust VSCode 调试 Rust WASM 编程">Rust | 秋河落叶 - +

    Rust

    🏠 首页 / Rust 编程

    Rust 编程 diff --git a/rust/rust-programming/index.html b/rust/rust-programming/index.html index c4f62816..f124fe16 100644 --- a/rust/rust-programming/index.html +++ b/rust/rust-programming/index.html @@ -11,7 +11,7 @@ let s: &str = "Hello World!"; let s1 = s.to_string(); let s1 = String::from(s); let s2 = &s1[..]; let s2 = s1.as_ref(); Panic # 设置 RUST_BACKTRACE=1 环境变量值,可以追踪到 panic 位置,例如: « Rust 入门 » Rust VSCode 调试'>Rust Programming | 秋河落叶 - +
    Rust Programming

    🏠 首页 / diff --git a/rust/vscode-debugging/index.html b/rust/vscode-debugging/index.html index e2c2a8cb..7195760a 100644 --- a/rust/vscode-debugging/index.html +++ b/rust/vscode-debugging/index.html @@ -3,7 +3,7 @@ { "version": "0.2.0", "configurations": [ { "type": "lldb", "request": "launch", "name": "Debug Rust Project", "cargo": { "args": [ "build", "--target-dir=${fileDirname}/../target", "--manifest-path=${fileDirname}/../Cargo.toml" ] }, "args": [], "cwd": "${workspaceFolder}" }, { "type": "lldb", "request": "launch", "name": "Debug Rust Unit Tests", "cargo": { "args": [ "test", "--no-run", "--target-dir=${fileDirname}/.'>Vscode Debugging | 秋河落叶 - +

    Vscode Debugging

    🏠 首页 / diff --git a/rust/wasm-programming/index.html b/rust/wasm-programming/index.html index 5d6e7cc8..c6324aab 100644 --- a/rust/wasm-programming/index.html +++ b/rust/wasm-programming/index.html @@ -5,7 +5,7 @@ Rust WASM 编程 # 1. 初始化项目 # cargo new hello-wasm cd hello-wasm 2. 安装 wasm-pack # cargo install wasm-pack 3. 编写代码 # 编辑 src/main.rs 文件: // 使用 wasm-bindgen 在 Rust 与 JavaScript 之间通信 extern crate wasm_bindgen; use wasm_bindgen::prelude::*; #[wasm_bindgen] extern { pub fn alert(s: &str); } #[wasm_bindgen] pub fn greet(name: &str){ alert(&format!("Hello, {}!",name)); } 编辑 Cargo.toml 文件: [package] name = "hello-wasm" version = "0.'>Wasm Programming | 秋河落叶 - +

    Wasm Programming

    🏠 首页 / diff --git a/sitemap.xml b/sitemap.xml index bf4a3c69..850001c4 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -1 +1 @@ -https://blog.poneding.com/2024-06-13T17:01:18+08:00https://blog.poneding.com/algo/2024-06-13T15:37:18+08:00https://blog.poneding.com/algo/%E5%A0%86%E6%8E%92%E5%BA%8F/https://blog.poneding.com/algo/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F/https://blog.poneding.com/aws/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/build-eks-cluster/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/cluster-autoscaler/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/create-eks-cluster/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/eks-config-alb-ingress/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/eks-details/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/eks-intergrate-gitlab-auto-release-01/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/eks-intergrate-gitlab-auto-release-02/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/eks-use-efs/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/gitlab-eks/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/k8s-deploy-kong/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/k8s-deploy-konga/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/k8s-deploy-postgres/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/terraform-remanage-resource/2024-06-13T15:37:18+08:00https://blog.poneding.com/cka/001/2024-06-13T15:37:18+08:00https://blog.poneding.com/cka/2024-06-13T15:37:18+08:00https://blog.poneding.com/cka/prepare-cka/2024-06-13T15:37:18+08:00https://blog.poneding.com/cka/tasks/2024-06-13T15:37:18+08:00https://blog.poneding.com/cs/2024-06-13T15:37:18+08:00https://blog.poneding.com/cs/internet/2024-06-13T15:37:18+08:00https://blog.poneding.com/cs/networking/2024-06-13T15:37:18+08:00https://blog.poneding.com/cs/virtual-memory/2024-06-13T15:37:18+08:00https://blog.poneding.com/dapr/2024-06-13T15:37:18+08:00https://blog.poneding.com/dapr/dapr/2024-06-13T15:37:18+08:00https://blog.poneding.com/design-pattern/2024-06-13T15:37:18+08:00https://blog.poneding.com/design-pattern/cicd/2024-06-13T15:37:18+08:00https://blog.poneding.com/devops/2024-06-13T15:37:18+08:00https://blog.poneding.com/devops/agile/2024-06-13T15:37:18+08:00https://blog.poneding.com/devops/ansible/2024-06-13T15:37:18+08:00https://blog.poneding.com/devops/bule-green-rollback-gray/2024-06-13T15:37:18+08:00https://blog.poneding.com/devops/chaos-engineering/2024-06-13T15:37:18+08:00https://blog.poneding.com/devops/commercial-canvas/2024-06-13T15:37:18+08:00https://blog.poneding.com/devops/grafana-monite-service-with-5xx/2024-06-13T15:37:18+08:00https://blog.poneding.com/devops/grafana-monite-service/2024-06-13T15:37:18+08:00https://blog.poneding.com/devops/grafana/2024-06-13T15:37:18+08:00https://blog.poneding.com/devops/jeager/2024-06-13T15:37:18+08:00https://blog.poneding.com/devops/nginx/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/container-diff/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/dind/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/docker-buildx/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/docker-commands/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/docker-compose-practice/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/docker-container-install-pfx-cert/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/docker-copy-between-host-container/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/docker-manifest-build-cross-arch-image/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/docker-run-link/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/docker-visiable-tool-kitematic/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/dockerfile/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/linux-container/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/non-root-account-get-docker-permission/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/some-apps/2024-06-13T15:37:18+08:00https://blog.poneding.com/ebpf/2024-06-13T15:37:18+08:00https://blog.poneding.com/ebpf/ebpf/2024-06-13T15:37:18+08:00https://blog.poneding.com/front-end/2024-06-13T15:37:18+08:00https://blog.poneding.com/front-end/build-blog-site/2024-06-13T15:37:18+08:00https://blog.poneding.com/front-end/pinia/2024-06-13T15:37:18+08:00https://blog.poneding.com/front-end/vitepress/2024-06-13T15:37:18+08:00https://blog.poneding.com/front-end/vue3/2024-06-13T15:37:18+08:00https://blog.poneding.com/git/2024-06-13T15:37:18+08:00https://blog.poneding.com/git/common-usage/2024-06-13T15:37:18+08:00https://blog.poneding.com/git/git-secret/2024-06-13T15:37:18+08:00https://blog.poneding.com/git/github-action-best-practice/2024-06-13T15:37:18+08:00https://blog.poneding.com/git/github-host-helm-chart/2024-06-13T15:37:18+08:00https://blog.poneding.com/git/github-hosting-helm-reop/2024-06-13T15:37:18+08:00https://blog.poneding.com/git/github/2024-06-13T15:37:18+08:00https://blog.poneding.com/git/gitlab-intergrate-k8s/2024-06-13T15:37:18+08:00https://blog.poneding.com/git/gitlab-upgrade-cross-version/2024-06-13T15:37:18+08:00https://blog.poneding.com/git/multi-github-account-management/2024-06-13T15:37:18+08:00https://blog.poneding.com/git/simplest-git-server/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/dev-env-config/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/function-optional-pattern/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go-cert-management/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go-cross-complie/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go-gen-cert/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go-linkname/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go-list-to-tree/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go-mtls/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go-publish-package-01/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go-publish-package-02/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go-solid/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go-stdlib/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go-testing/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/gopkg-errors/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/goreleaser/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/mac-appl-silicon-cross-compile-cgo/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/pprof/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/ssh-keygen-with-go/2024-06-13T15:37:18+08:00https://blog.poneding.com/graphql/2024-06-13T15:37:18+08:00https://blog.poneding.com/graphql/graphql/2024-06-13T15:37:18+08:00https://blog.poneding.com/grpc/2024-06-13T15:37:18+08:00https://blog.poneding.com/grpc/gRPC/2024-06-13T15:37:18+08:00https://blog.poneding.com/istio/2024-06-13T15:37:18+08:00https://blog.poneding.com/istio/aws-acm-tls-management/2024-06-13T15:37:18+08:00https://blog.poneding.com/istio/installation/2024-06-13T15:37:18+08:00https://blog.poneding.com/istio/istio-auth-policy/2024-06-13T15:37:18+08:00https://blog.poneding.com/istio/istio-canary-deploy/2024-06-13T15:37:18+08:00https://blog.poneding.com/istio/istio-cors/2024-06-13T15:37:18+08:00https://blog.poneding.com/istio/istio-timeout/2024-06-13T15:37:18+08:00https://blog.poneding.com/istio/istio-white-manifest/2024-06-13T15:37:18+08:00https://blog.poneding.com/istio/Istio/2024-06-13T15:37:18+08:00https://blog.poneding.com/istio/tls-transform/2024-06-13T15:37:18+08:00https://blog.poneding.com/istio/traffic-management/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/anti-affinity-improves-service-availability/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/apiserver-builder/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/apiserver/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/binary-build-k8s-01-prepare-nodes/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/binary-build-k8s-02-deploy-etcd/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/binary-build-k8s-03-deploy-master/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/binary-build-k8s-04-deploy-worker/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/cloud-native-understood/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/cluster-federation/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/configmap-understood/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/delete-es-log-index-scheduler/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/delete-k8s-resource-force/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/gateway-api-practice/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/helm-k8s-package-management-tool/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/hpa-usage/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/http-call-k8s-apiserver/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/informer/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/ingress-gray-deploy/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/installation/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/k3s/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/k8s-deploy-coredns/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/k8s-deploy-dashboard/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/k8s-deploy-efk/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/k8s-deploy-prometheus-grafana/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/k8s-deploy-zookeeper-kafka/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/k8s-dev-01-api-concept/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/k8s-dev-02-crd/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/k8s-dev-50-extend-kube-scheduler/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/k8s-get-started/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kubeadm-install-k8s-docker/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kubeadm-install-k8s/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kubeadm-upgrade/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kubebuilder-inaction/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kubectl/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kubernetes-best-practice/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kubernetes-dashboard/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kubernetes-naming-constraints/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kubernetes/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kubevirt-create-windows-vm/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kubevirt-practice/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kustomize/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/liveness-readiness-probe/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/local-storageclass/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/metallb/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/nfs-as-pvc/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/pod-understood/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/prgramming-kubernetes/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/prometheus-collect-kong-metrics/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/prometheus/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/pvc-expansion/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/secret-understood/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/service-understood/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/telepresence/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/terminate-pod-gracefully/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/terraform/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/velero-minio-backup-restore-volume/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/volume-understood/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/vpa/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/certbot-auto-gen-cert/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/history-with-date/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/linux-commands/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/linux-common-commands/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/linux-enable-crontab-log/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/linux-secure-login/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/shell-command-interval-character/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/shell/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/ssh-tunnel-connect-middleware/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/tee-keep-stderr/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/vim-common-commands/2024-06-13T15:37:18+08:00https://blog.poneding.com/middleware/2024-06-13T15:37:18+08:00https://blog.poneding.com/middleware/elasticsearch/2024-06-13T15:37:18+08:00https://blog.poneding.com/middleware/mongodb/2024-06-13T15:37:18+08:00https://blog.poneding.com/middleware/mysql/2024-06-13T15:37:18+08:00https://blog.poneding.com/middleware/postgres/2024-06-13T15:37:18+08:00https://blog.poneding.com/middleware/redis/2024-06-13T15:37:18+08:00https://blog.poneding.com/os/2024-06-13T15:37:18+08:00https://blog.poneding.com/os/macos/2024-06-13T15:37:18+08:00https://blog.poneding.com/os/ohmyzsh/2024-06-13T15:37:18+08:00https://blog.poneding.com/os/openssl/2024-06-13T15:37:18+08:00https://blog.poneding.com/os/ubuntu/2024-06-13T15:37:18+08:00https://blog.poneding.com/os/windows/2024-06-13T15:37:18+08:00https://blog.poneding.com/reading/2024-06-13T15:37:18+08:00https://blog.poneding.com/reading/%E4%BA%91%E5%8E%9F%E7%94%9F%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91Operator%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E8%B7%B5/https://blog.poneding.com/reading/%E6%88%91%E7%9A%84%E7%AC%AC%E4%B8%80%E6%9C%AC%E7%AE%97%E6%B3%95%E4%B9%A6/https://blog.poneding.com/reading/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/https://blog.poneding.com/rust/2024-06-13T15:37:18+08:00https://blog.poneding.com/rust/cargo/2024-06-13T15:37:18+08:00https://blog.poneding.com/rust/dev-env-config/2024-06-13T15:37:18+08:00https://blog.poneding.com/rust/getting-started/2024-06-13T15:37:18+08:00https://blog.poneding.com/rust/rust-programming/2024-06-13T15:37:18+08:00https://blog.poneding.com/rust/vscode-debugging/2024-06-13T15:37:18+08:00https://blog.poneding.com/rust/wasm-programming/2024-06-13T15:37:18+08:00https://blog.poneding.com/categories/https://blog.poneding.com/tags/ \ No newline at end of file +https://blog.poneding.com/2024-06-13T17:01:18+08:00https://blog.poneding.com/algo/2024-06-13T15:37:18+08:00https://blog.poneding.com/algo/%E5%A0%86%E6%8E%92%E5%BA%8F/https://blog.poneding.com/algo/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F/https://blog.poneding.com/aws/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/build-eks-cluster/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/cluster-autoscaler/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/create-eks-cluster/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/eks-config-alb-ingress/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/eks-details/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/eks-intergrate-gitlab-auto-release-01/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/eks-intergrate-gitlab-auto-release-02/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/eks-use-efs/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/gitlab-eks/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/k8s-deploy-kong/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/k8s-deploy-konga/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/k8s-deploy-postgres/2024-06-13T15:37:18+08:00https://blog.poneding.com/aws/terraform-remanage-resource/2024-06-13T15:37:18+08:00https://blog.poneding.com/cka/001/2024-06-13T15:37:18+08:00https://blog.poneding.com/cka/2024-06-13T15:37:18+08:00https://blog.poneding.com/cka/prepare-cka/2024-06-13T15:37:18+08:00https://blog.poneding.com/cka/tasks/2024-06-13T15:37:18+08:00https://blog.poneding.com/cs/2024-06-13T15:37:18+08:00https://blog.poneding.com/cs/internet/2024-06-13T15:37:18+08:00https://blog.poneding.com/cs/networking/2024-06-13T15:37:18+08:00https://blog.poneding.com/cs/virtual-memory/2024-06-13T15:37:18+08:00https://blog.poneding.com/dapr/2024-06-13T15:37:18+08:00https://blog.poneding.com/dapr/dapr/2024-06-13T15:37:18+08:00https://blog.poneding.com/design-pattern/2024-06-13T15:37:18+08:00https://blog.poneding.com/design-pattern/cicd/2024-06-13T15:37:18+08:00https://blog.poneding.com/devops/2024-06-13T15:37:18+08:00https://blog.poneding.com/devops/agile/2024-06-13T15:37:18+08:00https://blog.poneding.com/devops/ansible/2024-06-13T15:37:18+08:00https://blog.poneding.com/devops/bule-green-rollback-gray/2024-06-13T15:37:18+08:00https://blog.poneding.com/devops/chaos-engineering/2024-06-13T15:37:18+08:00https://blog.poneding.com/devops/commercial-canvas/2024-06-13T15:37:18+08:00https://blog.poneding.com/devops/grafana-monite-service-with-5xx/2024-06-13T15:37:18+08:00https://blog.poneding.com/devops/grafana-monite-service/2024-06-13T15:37:18+08:00https://blog.poneding.com/devops/grafana/2024-06-13T15:37:18+08:00https://blog.poneding.com/devops/jeager/2024-06-13T15:37:18+08:00https://blog.poneding.com/devops/nginx/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/container-diff/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/dind/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/docker-buildx/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/docker-commands/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/docker-compose-practice/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/docker-container-install-pfx-cert/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/docker-copy-between-host-container/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/docker-manifest-build-cross-arch-image/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/docker-run-link/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/docker-visiable-tool-kitematic/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/dockerfile/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/linux-container/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/non-root-account-get-docker-permission/2024-06-13T15:37:18+08:00https://blog.poneding.com/docker/some-apps/2024-06-13T15:37:18+08:00https://blog.poneding.com/ebpf/2024-06-13T15:37:18+08:00https://blog.poneding.com/ebpf/ebpf/2024-06-13T15:37:18+08:00https://blog.poneding.com/front-end/2024-06-13T15:37:18+08:00https://blog.poneding.com/front-end/build-blog-site/2024-06-13T17:04:07+08:00https://blog.poneding.com/front-end/pinia/2024-06-13T15:37:18+08:00https://blog.poneding.com/front-end/vitepress/2024-06-13T15:37:18+08:00https://blog.poneding.com/front-end/vue3/2024-06-13T15:37:18+08:00https://blog.poneding.com/git/2024-06-13T15:37:18+08:00https://blog.poneding.com/git/common-usage/2024-06-13T15:37:18+08:00https://blog.poneding.com/git/git-secret/2024-06-13T15:37:18+08:00https://blog.poneding.com/git/github-action-best-practice/2024-06-13T15:37:18+08:00https://blog.poneding.com/git/github-host-helm-chart/2024-06-13T15:37:18+08:00https://blog.poneding.com/git/github-hosting-helm-reop/2024-06-13T15:37:18+08:00https://blog.poneding.com/git/github/2024-06-13T15:37:18+08:00https://blog.poneding.com/git/gitlab-intergrate-k8s/2024-06-13T15:37:18+08:00https://blog.poneding.com/git/gitlab-upgrade-cross-version/2024-06-13T15:37:18+08:00https://blog.poneding.com/git/multi-github-account-management/2024-06-13T15:37:18+08:00https://blog.poneding.com/git/simplest-git-server/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/dev-env-config/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/function-optional-pattern/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go-cert-management/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go-cross-complie/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go-gen-cert/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go-linkname/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go-list-to-tree/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go-mtls/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go-publish-package-01/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go-publish-package-02/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go-solid/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go-stdlib/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go-testing/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/go/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/gopkg-errors/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/goreleaser/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/mac-appl-silicon-cross-compile-cgo/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/pprof/2024-06-13T15:37:18+08:00https://blog.poneding.com/go/ssh-keygen-with-go/2024-06-13T15:37:18+08:00https://blog.poneding.com/graphql/2024-06-13T15:37:18+08:00https://blog.poneding.com/graphql/graphql/2024-06-13T15:37:18+08:00https://blog.poneding.com/grpc/2024-06-13T15:37:18+08:00https://blog.poneding.com/grpc/gRPC/2024-06-13T15:37:18+08:00https://blog.poneding.com/istio/2024-06-13T15:37:18+08:00https://blog.poneding.com/istio/aws-acm-tls-management/2024-06-13T15:37:18+08:00https://blog.poneding.com/istio/installation/2024-06-13T15:37:18+08:00https://blog.poneding.com/istio/istio-auth-policy/2024-06-13T15:37:18+08:00https://blog.poneding.com/istio/istio-canary-deploy/2024-06-13T15:37:18+08:00https://blog.poneding.com/istio/istio-cors/2024-06-13T15:37:18+08:00https://blog.poneding.com/istio/istio-timeout/2024-06-13T15:37:18+08:00https://blog.poneding.com/istio/istio-white-manifest/2024-06-13T15:37:18+08:00https://blog.poneding.com/istio/Istio/2024-06-13T15:37:18+08:00https://blog.poneding.com/istio/tls-transform/2024-06-13T15:37:18+08:00https://blog.poneding.com/istio/traffic-management/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/anti-affinity-improves-service-availability/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/apiserver-builder/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/apiserver/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/binary-build-k8s-01-prepare-nodes/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/binary-build-k8s-02-deploy-etcd/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/binary-build-k8s-03-deploy-master/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/binary-build-k8s-04-deploy-worker/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/cloud-native-understood/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/cluster-federation/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/configmap-understood/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/delete-es-log-index-scheduler/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/delete-k8s-resource-force/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/gateway-api-practice/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/helm-k8s-package-management-tool/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/hpa-usage/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/http-call-k8s-apiserver/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/informer/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/ingress-gray-deploy/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/installation/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/k3s/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/k8s-deploy-coredns/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/k8s-deploy-dashboard/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/k8s-deploy-efk/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/k8s-deploy-prometheus-grafana/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/k8s-deploy-zookeeper-kafka/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/k8s-dev-01-api-concept/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/k8s-dev-02-crd/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/k8s-dev-50-extend-kube-scheduler/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/k8s-get-started/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kubeadm-install-k8s-docker/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kubeadm-install-k8s/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kubeadm-upgrade/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kubebuilder-inaction/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kubectl/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kubernetes-best-practice/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kubernetes-dashboard/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kubernetes-naming-constraints/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kubernetes/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kubevirt-create-windows-vm/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kubevirt-practice/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/kustomize/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/liveness-readiness-probe/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/local-storageclass/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/metallb/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/nfs-as-pvc/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/pod-understood/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/prgramming-kubernetes/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/prometheus-collect-kong-metrics/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/prometheus/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/pvc-expansion/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/secret-understood/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/service-understood/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/telepresence/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/terminate-pod-gracefully/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/terraform/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/velero-minio-backup-restore-volume/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/volume-understood/2024-06-13T15:37:18+08:00https://blog.poneding.com/kubernetes/vpa/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/certbot-auto-gen-cert/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/history-with-date/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/linux-commands/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/linux-common-commands/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/linux-enable-crontab-log/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/linux-secure-login/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/shell-command-interval-character/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/shell/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/ssh-tunnel-connect-middleware/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/tee-keep-stderr/2024-06-13T15:37:18+08:00https://blog.poneding.com/linux/vim-common-commands/2024-06-13T15:37:18+08:00https://blog.poneding.com/middleware/2024-06-13T15:37:18+08:00https://blog.poneding.com/middleware/elasticsearch/2024-06-13T15:37:18+08:00https://blog.poneding.com/middleware/mongodb/2024-06-13T15:37:18+08:00https://blog.poneding.com/middleware/mysql/2024-06-13T15:37:18+08:00https://blog.poneding.com/middleware/postgres/2024-06-13T15:37:18+08:00https://blog.poneding.com/middleware/redis/2024-06-13T15:37:18+08:00https://blog.poneding.com/os/2024-06-13T15:37:18+08:00https://blog.poneding.com/os/macos/2024-06-13T15:37:18+08:00https://blog.poneding.com/os/ohmyzsh/2024-06-13T15:37:18+08:00https://blog.poneding.com/os/openssl/2024-06-13T15:37:18+08:00https://blog.poneding.com/os/ubuntu/2024-06-13T15:37:18+08:00https://blog.poneding.com/os/windows/2024-06-13T15:37:18+08:00https://blog.poneding.com/reading/2024-06-13T15:37:18+08:00https://blog.poneding.com/reading/%E4%BA%91%E5%8E%9F%E7%94%9F%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91Operator%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E8%B7%B5/https://blog.poneding.com/reading/%E6%88%91%E7%9A%84%E7%AC%AC%E4%B8%80%E6%9C%AC%E7%AE%97%E6%B3%95%E4%B9%A6/https://blog.poneding.com/reading/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C/https://blog.poneding.com/rust/2024-06-13T15:37:18+08:00https://blog.poneding.com/rust/cargo/2024-06-13T15:37:18+08:00https://blog.poneding.com/rust/dev-env-config/2024-06-13T15:37:18+08:00https://blog.poneding.com/rust/getting-started/2024-06-13T15:37:18+08:00https://blog.poneding.com/rust/rust-programming/2024-06-13T15:37:18+08:00https://blog.poneding.com/rust/vscode-debugging/2024-06-13T15:37:18+08:00https://blog.poneding.com/rust/wasm-programming/2024-06-13T15:37:18+08:00https://blog.poneding.com/categories/https://blog.poneding.com/tags/ \ No newline at end of file diff --git a/tags/index.html b/tags/index.html index c2a3a39f..869cd310 100644 --- a/tags/index.html +++ b/tags/index.html @@ -1,5 +1,5 @@ Tags | 秋河落叶 - +

    Tags
    \ No newline at end of file