作者归档:往事随风

关于往事随风

1111

Vue3.0 全局拦截器


2021年11月21日 11:13:02   1,724 次浏览

全局过滤器
如果在应用中全局注册了过滤器,那么在每个组件中用计算属性或方法调用来替换它可能就没那么方便了。取而代之的是,你可以通过全局属性以让它能够被所有组件使用到:

 // 在 main.js 中添加
const app = createApp(App) 
app.config.globalProperties.$filters = { currencyUSD(value) { return '$' + value } }

然后,可以通过这个 $filters 对象修正所有的模板,就像这样:

<template> <h1>Bank Account Balance</h1> <p>{{ $filters.currencyUSD(accountBalance) }}</p> </template>

"firstTimestamp": "2021-09-28T07:00:01Z",
"lastTimestamp": "2021-10-09T06:28:19Z",


将时间转换年月日 时分秒格式: 2021-10-09 15:58:18

Golang操作Gitlab实现MR自动合并


2021年3月26日 17:26:22   3,914 次浏览

通过gitlab api实现merge requests的自动合并,在使用api前我们需要先安装依赖及需要在gitlab配置帐户的token, 因为接下来需要用到token

go get -u github.com/xanzy/go-gitlab

package main

import (
	"fmt"
	"github.com/xanzy/go-gitlab"
	"log"
	"time"
)
var (
	// Your gitlab token
	token = ""
	// Your gitlab url
	url = "http://"
	// Name of your GITLAB project team
	groupName = "ops"
)
func main() {

	for {
		git, err := gitlab.NewClient(token, gitlab.WithBaseURL(url))

		if err != nil {
			log.Fatalf("Failed to create client: %v", err)
		}

		log.Println("开始获取所有组信息")

		u := &gitlab.ListGroupsOptions{
			Search: gitlab.String(groupName),
		}
		group, _, err := git.Groups.ListGroups(u)

		if err != nil {
			fmt.Printf("获取组信息异常:%v", err)
			panic(err)
		}

		for _, group := range group {
			log.Println("组名:",group.Name, "创建时间:", group.CreatedAt.Format("2006-01-02"))
			if group.Name == groupName {
				// Get Merge Requests List
				log.Printf("获取:%v项目Merge Requests开始...", groupName)
				ListMergeOps := &gitlab.ListGroupMergeRequestsOptions{
					OrderBy: gitlab.String("updated_at"),
					State: gitlab.String("opened"),
					Scope: gitlab.String("all"),
				}
				// List Group Merge
				mr, _, err := git.MergeRequests.ListGroupMergeRequests(group.ID, ListMergeOps)
				// List All Merge Requests
				//mr, _, err := git.MergeRequests.ListMergeRequests(ListMergeOps)
				if err != nil {
					panic(err)
				}
				for _, mr := range mr {
					log.Printf("用户:%v发起了一个MR请求,描述信息:%v, MR_ID: %v, 项目ID: %v", mr.Author.Name, mr.Title, mr.IID, mr.ProjectID)
					// Auto merge request
					MergeSave := &gitlab.AcceptMergeRequestOptions{}
					merge, code, err := git.MergeRequests.AcceptMergeRequest(mr.ProjectID, mr.IID, MergeSave)
					if err != nil {
						fmt.Println(err)
					}
					switch code.StatusCode{
					case 405:
						log.Println("当前Merge Requests存在草稿,暂时不能合并")
					case 406:
						log.Println("Merge Requests合并失败,当前代码存在冲突请解决后再试!")
					case 200:
						log.Println("恭喜你,Merge Requests合并成功.")
					default:
						log.Println("等待下一次检查中.")
					}
					log.Println(merge)
				}
			}
		}
		time.Sleep(time.Duration(30)*time.Second)
	}
}

接下来我们向gitlab发起一个merge requests,等待30s秒将会自动合并。

2021/03/26 16:34:12 开始获取所有组信息
2021/03/26 16:34:12 组名: ops 创建时间: 2021-03-08
2021/03/26 16:34:12 获取:ops项目Merge Requests开始...
2021/03/26 16:34:12 用户:张三发起了一个MR请求,描述信息:新增ibe配置, MR_ID: 6, 项目ID: 77
2021/03/26 16:34:14 恭喜你,Merge Requests合并成功.

基于Python 版本的gitlab requests 自动合并脚本

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @File  : GitLab-API.py
# @Author: 风哥
# @Email: gujiwork@outlook.com
# @Date  : 2021/3/25
# @Desc  : gitlab自动合并请求
import gitlab
from gitlab.exceptions import GitlabMRClosedError
import time


url = 'http://'  # gitlab安装地址
private_token = ''  # gitlab 登录密钥 需自己设置


while True:
    # 登录 获取gitlab操作对象gl
    gl = gitlab.Gitlab(url, private_token)
    group = gl.groups.get('ops')
    mrs = group.mergerequests.list(state='opened', order_by='updated_at')
    for i in mrs:
        msg = "用户:{0}发起了一个MR请求, 描述信息:{1}, MR_ID: {2}, 项目ID:{3}".format(i.author['name'], i.title, str(i.iid), str(i.project_id))
        project = gl.projects.get(i.project_id, lazy=True)
        mr = project.mergerequests.get(i.iid, lazy=True)
        try:
            print("开始自动合并代码")
            mr.merge()
            print("自动合并MR成功!")
        except GitlabMRClosedError as e:
            if e.response_code == 405:
                print("MR存在Draft草稿, Closed关闭, Pipline状态, 返回状态码405不允许操作")
            elif e.response_code == 406:
                print("自动合并失败, 当前存在冲突,请手动解决后再试!")
            else:
                print("出现异常,暂时无法自动合并!")

        # print(mr)
    time.sleep(30)

节点异常Pod驱逐时效设置


2020年12月01日 10:02:02   3,332 次浏览

判断节点Not Ready时效设置

k8s默认节点not ready需要经过40s时间,可以通过以下参数进行修改

vim /etc/kubernetes/manifest/kuber-controller-manager.yaml     

--node-monitor-grace-period=20s  

vim /var/lib/kubelet/kubeadm-flags.env     
--node-status-update-frequency=4s 

vim /etc/kubernetes/manifest/kuber-controller-manager.yaml     
--node-monitor-grace-period=20s 

vim /var/lib/kubelet/kubeadm-flags.env     
--node-status-update-frequency=4s

备注:
--node-status-update-frequency 指定kubelet的上报频率

--node-monitor-grace-period 失联指定时间后,将节点状态readey变成为not ready

--node-monitor-grace-period需要是--node-status-update-frequency整数倍

社区默认的配置
--node-status-update-frequency  10s 
--node-monitor-period           5s 
--node-monitor-grace-period     40s
快速更新和快速响应

--node-status-update-frequency  4s 
--node-monitor-period   2s 
--node-monitor-grace-period  20s

节点Not Ready之后 pod驱逐并重启时效设置

节点Not Ready 之后, k8s默认需要300s时间,在新的节点上启动重新启动Pod, 可以通过以下参数修改。

设置以下参数

vim /etc/kubernetes/manifest/kube-apiserver.yaml

   - --default-not-ready-toleration-seconds=10
   - --default-unreachable-toleration-seconds=10

设置之后pod会被自动加上以下容忍污点

 tolerations:

 - effect: NoExecute
   key: node.kubernetes.io/not-ready
   operator: Exists
   tolerationSeconds: 10

 - effect: NoExecute
   key: node.kubernetes.io/unreachable
   operator: Exists
   tolerationSeconds: 10

故障隔离时效

节点故障隔离由node-agent和节点隔离controller控制器组成,检测kubelet,docker,containerd,calico等组件是否异常,隔离机制如下图所示:

Node-Agent上报的心跳时间间隔1s,连续5次为失败,即5s触发隔离, 为节点设置NoExecute污点,立即驱逐故障节点上的pod,k8s自动调度到其他正常的节点运行pod。

Kubernetes 最佳安全实践指南


2020年11月27日 23:09:50   2,010 次浏览

对于大部分 Kubernetes 用户来说,安全是无关紧要的,或者说没那么紧要,就算考虑到了,也只是敷衍一下,草草了事。实际上 Kubernetes 提供了非常多的选项可以大大提高应用的安全性,只要用好了这些选项,就可以将绝大部分的攻击抵挡在门外。为了更容易上手,我将它们总结成了几个最佳实践配置,大家看完了就可以开干了。当然,本文所述的最佳安全实践仅限于 Pod 层面,也就是容器层面,于容器的生命周期相关,至于容器之外的安全配置(比如操作系统啦、k8s 组件啦),以后有机会再唠。

1. 为容器配置 Security Context

大部分情况下容器不需要太多的权限,我们可以通过 Security Context 限定容器的权限和访问控制,只需加上 SecurityContext 字段:

apiVersion: v1
kind: Pod
metadata:
  name: <Pod name>
spec:
  containers:
  - name: <container name>
  image: <image>
+   securityContext:

2. 禁用 allowPrivilegeEscalation

allowPrivilegeEscalation=true 表示容器的任何子进程都可以获得比父进程更多的权限。最好将其设置为 false,以确保 RunAsUser 命令不能绕过其现有的权限集。

apiVersion: v1
kind: Pod
metadata:
  name: <Pod name>
spec:
  containers:
  - name: <container name>
  image: <image>
    securityContext:
  +   allowPrivilegeEscalation: false

3. 不要使用 root 用户

为了防止来自容器内的提权攻击,最好不要使用 root 用户运行容器内的应用。UID 设置大一点,尽量大于 3000

apiVersion: v1
kind: Pod
metadata:
  name: <name>
spec:
  securityContext:
+   runAsUser: <UID higher than 1000>
+   runAsGroup: <UID higher than 3000>

4. 限制 CPU 和内存资源

这个就不用多说了吧,requests 和 limits 都加上。

5. 不必挂载 Service Account Token

ServiceAccount 为 Pod 中运行的进程提供身份标识,怎么标识呢?当然是通过 Token 啦,有了 Token,就防止假冒伪劣进程。如果你的应用不需要这个身份标识,可以不必挂载:

apiVersion: v1
kind: Pod
metadata:
  name: <name>
spec:
+ automountServiceAccountToken: false

6. 确保 seccomp 设置正确

对于 Linux 来说,用户层一切资源相关操作都需要通过系统调用来完成,那么只要对系统调用进行某种操作,用户层的程序就翻不起什么风浪,即使是恶意程序也就只能在自己进程内存空间那一分田地晃悠,进程一终止它也如风消散了。seccomp(secure computing mode)就是一种限制系统调用的安全机制,可以可以指定允许那些系统调用。

对于 Kubernetes 来说,大多数容器运行时都提供一组允许或不允许的默认系统调用。通过使用 runtime/default 注释或将 Pod 或容器的安全上下文中的 seccomp 类型设置为 RuntimeDefault,可以轻松地在 Kubernetes 中应用默认值。

apiVersion: v1
kind: Pod
metadata:
  name: <name>
  annotations:
  + seccomp.security.alpha.kubernetes.io/pod: "runtime/default"

7. 限制容器的 capabilities

容器依赖于传统的 Unix 安全模型,通过控制资源所属用户和组的权限,来达到对资源的权限控制。以 root 身份运行的容器拥有的权限远远超过其工作负载的要求,一旦发生泄露,攻击者可以利用这些权限进一步对网络进行攻击。

默认情况下,使用 Docker 作为容器运行时,会启用 NET_RAW capability,这可能会被恶意攻击者进行滥用。因此,建议至少定义一个PodSecurityPolicy(PSP),以防止具有 NET_RAW 功能的容器启动。

通过限制容器的 capabilities,可以确保受攻击的容器无法为攻击者提供横向攻击的有效路径,从而缩小攻击范围。

apiVersion: v1
kind: Pod
metadata:
  name: <name>
spec:
  securityContext:
  + runAsNonRoot: true
  + runAsUser: <specific user>
  capabilities:
  drop:
  + -NET_RAW
  + -ALL

8. 只读

如果容器不需要对根文件系统进行写入操作,最好以只读方式加载容器的根文件系统,可以进一步限制攻击者的手脚。

apiVersion: v1
kind: Pod
metadata:
  name: <Pod name>
spec:
  containers:
  - name: <container name>
  image: <image>
  securityContext:
  + readOnlyRootFilesystem: true

9 总结

总之,Kubernetes 提供了非常多的选项来增强集群的安全性,没有一个放之四海而皆准的解决方案,所以需要对这些选项非常熟悉,以及了解它们是如何增强应用程序的安全性,才能使集群更加稳定安全。

最后,请记住:你需要万分小心你的 YAML 文件内容缩进,如果你的 YAML 文件非常多,眼睛看不过来,希望下面的神器可以助你一臂之力:


Zabbix使用Python检查Haproxy状态页面


2020年11月26日 13:01:59   1,963 次浏览

概述

网上有很多使用zabbix监控haproxy的脚本,但大多数都使用的socket方式,而haproxy的stats页面页面我们经常需要访问的,所以我们这次使用python来抓取haproxy的stats页面。

haproxy的stats页面分析

<tr class="active0"><td class=ac><a name="app_push/push496">

当backend正常时,会显示绿色.

<tr class="active4"><td class=ac><a name="app_push/push096">

中间会有黄色的情况,backend反复故障恢复时会产生。 Python脚本抓取

#!/usr/bin/env python
#coding=utf-8
#Debug in Python2.7
import urllib2
import sys
import re
url = sys.argv[1]
#url = 'http://10.100.18.78:8888/status'
try:
    response = urllib2.urlopen(url,timeout=5).read();
except:
    print 'error to connect haproxy.'
    sys.exit(0)
pattern = re.compile('<tr class="active0"><td class=ac><a name="(.*?)"></a>')
items = re.findall(pattern, response)
data = []
for item in items:
    #print item
    data.append(item)
if len(data):
    print data
else:
    print 'ok'

当haproxy有backend故障时,会打印故障服务器,没有故障时显示OK,服务器无法连接,显示’error to connect haproxy.’ 故障显示如下

['app_push/push496', 'app_push/push092']

Zabbix中添加监控项 需要zabbix客户端自定义一个key来关联检查脚本。然后zabbix服务器端设置模板。这里我是用的字符串匹配。

#!/usr/bin/env python
from dingding import message
import requests
import sys
import re
import time
while True:
    time.sleep(30)
    url = 'http://10.28.xx.xx:65010/haproxy'
    try:
        response = requests.get(url=url, timeout=5).text

    except:
        print('error to connect haproxy.')
        sys.exit(0)
    pattern = re.compile('<tr class="active0"><td class=ac><a name="(.*?)"></a>')
    items = re.findall(pattern, response)
    data = []

    for item in items:
        #print item

        data.append(item)
    if len(data):
        print(data)
        message(text=data)
    else:
        print('ok')
dingding.py

#!/usr/bin/env python
#-*- coding: utf-8 -*-

'''
@Author: 风哥
@Email:  gujiwork@outlook.com
@Create Time: 2019/9/4
'''

import json
import requests


def message(text):
    # 告警通知
    headers = {
        'Content-Type': 'application/json;charset=utf-8',
    }

    alarm_user = 'phone1,phone2'
    notice_url = 'https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

    at_user = (alarm_user).split(',')
    json_text = {
        "msgtype": "text",
        "at": {
            "atMobiles":
                at_user,
            "isAtAll": False  # 为True表示@所有人
        },

        "text": {
            "content": (text)

        }
    }
    try:

        notice = requests.post(notice_url, json.dumps(json_text), headers=headers).content
        print(json.loads(notice))

    except BaseException as e:
        at_user = []
        print(e)

 

vue返回上一页


2020年11月26日 13:01:21   1,437 次浏览
如何使用点击方式控制当前页返回到上一个路由页面:
查阅相关资料,返回上一目录用到的是 this.$router.go(-1); 将该方法些到返回按钮上,点击触发该方法;
具体代码如下:
1.在当前页面添加返回按钮
<!--返回按钮--> 
<div class="backTo" v-show="isShow"> 
    <span v-on:click="back">返回</span> 
</div>
2.在方法体内现价back方法
methods:{ 
  back(){ 
    this.$router.go(-1);
    //返回上一层 
  }, 
},
问题2:如何控制”返回键” 的显示和隐藏: 由于这里需要频繁的改变”返回键“的显示和隐藏,所以这里考虑用v-show
data() { 
  return { 
    isShow:false 
  } 
}
触发isShow 的值改变的事件应该是当前页面路由地址的改变,并且这里需要使用watch完成监控:
watch:{ 
  $route(now,old){ 
  //监控路由变换,控制返回按钮的显示 
  if(now.path=="/home/home"){ 
    this.isShow=false; 
  } else{ 
    this.isShow=true; 
    } 
  } 
}
这样,当页面处在主页下的时候,返回键自动隐藏掉,如果不是当前主页,就显示返回键
第二种方式:vue2.0返回上一页
@click="$router.back(-1)"

 

页面:

使用python操作Jenkins权限


2020年9月03日 16:19:09   2,783 次浏览

对于 jenkins 的权限管理,一般来说都会使用 Role-based Authorization Strategy 进行管理

在人员较少或项目不多的时候,使用上没什么问题,但随着人员的增多或项目的增多,手动添加权限会很麻烦,需要一个权限一个权限的去勾选,非常容易出错,此时就需要通过该插件提供的 API 去操作权限,但是 官方文档 中并没有介绍 API 的使用,最终在 gitter 中发现有大佬指出了其 REST API 源代码位置,即 https://github.com/jenkinsci/role-strategy-plugin/blob/master/src/main/java/com/michelin/cio/hudson/plugins/rolestrategy/RoleBasedAuthorizationStrategy.java#L391-L395

其中注释很清楚,并且都会有 curl 示例,使用起来很方便,当然注释中也有一些小问题,该踩的坑我已经踩完了,下面主要演示一下使用 python 对权限的修改

权限内容

首先了解下该权限组成以及其对应的权限ID

全局权限
权限范围 权限名称 权限ID
全部 Administer hudson.model.Hudson.Administer
Read hudson.model.Hudson.Read
凭据 Create com.cloudbees.plugins.credentials.CredentialsProvider.Create
Delete com.cloudbees.plugins.credentials.CredentialsProvider.Delete
ManageDomains com.cloudbees.plugins.credentials.CredentialsProvider.ManageDomains
Update com.cloudbees.plugins.credentials.CredentialsProvider.Update
View com.cloudbees.plugins.credentials.CredentialsProvider.View
代理 Build hudson.model.Computer.Build
Configure hudson.model.Computer.Configure
Connect hudson.model.Computer.Connect
Create hudson.model.Computer.Create
Delete hudson.model.Computer.Delete
Disconnect hudson.model.Computer.Disconnect
Provision hudson.model.Computer.Provision
任务(Job) Build hudson.model.Item.Build
Cancel hudson.model.Item.Cancel
Configure hudson.model.Item.Configure
Create hudson.model.Item.Create
Delete hudson.model.Item.Delete
Discover hudson.model.Item.Discover
Move hudson.model.Item.Move
Read hudson.model.Item.Read
Workspace hudson.model.Item.Workspace
运行(构建历史操作) Delete hudson.model.Run.Delete
Replay hudson.model.Run.Replay
Update hudson.model.Run.Update
视图 Configure hudson.model.View.Configure
Create hudson.model.View.Create
Delete hudson.model.View.Delete
Read hudson.model.View.Read
SCM Tag hudson.scm.SCM.Tag
Lockable Resources Reserve org.jenkins.plugins.lockableresources.LockableResourcesManager.Reserve
Unlock org.jenkins.plugins.lockableresources.LockableResourcesManager.Unlock
View org.jenkins.plugins.lockableresources.LockableResourcesManager.View

使用代码添加角色指定权限的时候,指定的权限ID必须是上表中权限ID列中的值

项目权限

包括上面的 凭据、任务、运行、SCM、Lockable Resources 相关所有权限

节点权限

包括上面的 凭据、代理、Lockable Resources 相关所有权限

封装API

实现很简单,使用 requests 库去构建 GET 和 POST 请求即可,下面是封装好的示例

import requests


class JenkinsRole:
    def __init__(self, host, username, port=80, password=None, token=None, ssl=False):
        """
        password和token使用其中一个即可
        :param host: Jenkins主机
        :param username: 管理员用户
        :param port: Jenkins端口,默认为80
        :param password: 管理员密码
        :param token: 管理员的Token
        :param ssl: Jenkins地址是否是https协议
        """
        self.host = host
        self.username = username
        self.port = port
        self.password = password
        self.token = token
        self.ssl = ssl

    @property
    def pwd_or_token(self):
        if self.password and self.token:
            raise ConnectionError("password与token填写一个即可")
        return self.password if self.password else self.token

    @property
    def proto(self):
        return 'https' if self.ssl else 'http'

    def get_crumb(self) -> dict:
        res = requests.get(
            f'{self.proto}://{self.username}:{self.pwd_or_token}@{self.host}:{self.port}/crumbIssuer/api/xml?'
            f'xpath=concat(//crumbRequestField,":",//crumb)')

        return {res.text.split(':')[0]: res.text.split(':')[1]}

    def add_role(self, role_type, role_name, permissions: str, role_pattern=None, overwrite=True):
        """
        添加角色
        如果添加的权限不属于对应的角色类型,两种情况:
        1、添加的权限都不属于对应的角色类型,则会添加一个空权限的角色
        比如向projectRoles中添加视图权限hudson.model.View.Create命名为p1,
        则在projectRoles列表中依然会添加p1角色,但是该角色没有任何权限

        2、添加的权限部分不属于对应的角色类型,则会将属于该角色类型的权限添加上

        :param role_type: 只能是globalRoles或projectRoles或slaveRoles
        :param role_name: 角色名称
        :param permissions: 角色ID,多个角色ID使用 , 号隔开,比如:'hudson.model.Hudson.Read,hudson.model.Computer.Build'
        :param role_pattern: 角色模式,支持正则表达式,当添加的是项目角色时需要指定
        :param overwrite: 如果新增的权限已经存在是否覆盖,如果选择不覆盖,即使权限已经存在,也不会返回任何报错
        :return:
        """
        if role_type not in ('globalRoles', 'projectRoles', 'slaveRoles'):
            raise AttributeError("role_type必须是'globalRoles', 'projectRoles', 'slaveRoles' 其中一个")

        if role_type in ('projectRoles', 'slaveRoles') and not role_pattern:
            raise AttributeError("如果增加项目权限或节点权限,必须指定role_pattern,否则将匹配 .* ")

        role_data = {
            "type": role_type,
            "roleName": role_name,
            "permissionIds": permissions,
            "overwrite": overwrite,
            "pattern": role_pattern
        }

        headers = self.get_crumb()

        res = requests.post(f'{self.proto}://{self.host}:{self.port}/role-strategy/strategy/addRole', data=role_data,
                            headers=headers, auth=(self.username, self.pwd_or_token))
        return res.status_code

    def get_role(self, role_type, role_name):
        """
        获取指定角色的详细,返回结果示例:
        {'permissionIds': {'hudson.model.Computer.Build': True}, 'sids': ['admin']}
        :param role_type:
        :param role_name:
        :return:
        """
        if role_type not in ('globalRoles', 'projectRoles', 'slaveRoles'):
            raise AttributeError("role_type必须是'globalRoles', 'projectRoles', 'slaveRoles' 其中一个")

        params = {
            "type": role_type,
            "roleName": role_name
        }

        res = requests.get(f'{self.proto}://{self.host}:{self.port}/role-strategy/strategy/getRole',
                           params=params, auth=(self.username, self.pwd_or_token))
        return res.json()

    def remove_roles(self, role_type, role_names: str):
        """
        删除权限
        :param role_type:
        :param role_names: 多个角色用 , 号隔开
        :return:
        """
        if role_type not in ('globalRoles', 'projectRoles', 'slaveRoles'):
            raise AttributeError("role_type必须是'globalRoles', 'projectRoles', 'slaveRoles' 其中一个")

        data = {
            'type': role_type,
            'roleNames': role_names
        }

        headers = self.get_crumb()

        res = requests.post(f'{self.proto}://{self.host}:{self.port}/role-strategy/strategy/removeRoles', data=data,
                            headers=headers, auth=(self.username, self.pwd_or_token))
        return res.status_code

    def assign_role(self, role_type, role_name, sid):
        """
        将某个角色赋予某个用户
        注意:如果赋予用户某个不存在的权限也不会报错
        :param role_type:
        :param role_name: (单个角色)
        :param sid: 用户名称(单个用户)
        :return:
        """
        if role_type not in ('globalRoles', 'projectRoles', 'slaveRoles'):
            raise AttributeError("role_type必须是'globalRoles', 'projectRoles', 'slaveRoles' 其中一个")

        data = {
            'type': role_type,
            'roleName': role_name,
            'sid': sid
        }

        headers = self.get_crumb()

        res = requests.post(f'{self.proto}://{self.host}:{self.port}/role-strategy/strategy/assignRole', data=data,
                            headers=headers, auth=(self.username, self.pwd_or_token))
        return res.status_code

    def delete_roles_from_sid(self, role_type, sid):
        """
        删除指定用户所有的相关权限
        注意:如果指定了一个不存在的用户,也不会报错
        :param role_type:
        :param sid: 单个用户
        :return:
        """
        if role_type not in ('globalRoles', 'projectRoles', 'slaveRoles'):
            raise AttributeError("role_type必须是'globalRoles', 'projectRoles', 'slaveRoles' 其中一个")

        data = {
            'type': role_type,
            'sid': sid
        }

        headers = self.get_crumb()

        res = requests.post(f'{self.proto}://{self.host}:{self.port}/role-strategy/strategy/deleteSid', data=data,
                            headers=headers, auth=(self.username, self.pwd_or_token))
        return res.status_code

    def unassign_role(self, role_type, role_name, sid):
        """
        删除指定用户的某个权限
        注意:即使指定一个不存在的用户或不存在的role,也不会返回错误
        :param role_type:
        :param role_name:
        :param sid:
        :return:
        """
        if role_type not in ('globalRoles', 'projectRoles', 'slaveRoles'):
            raise AttributeError("role_type必须是'globalRoles', 'projectRoles', 'slaveRoles' 其中一个")

        data = {
            'type': role_type,
            'roleName': role_name,
            'sid': sid
        }

        headers = self.get_crumb()

        res = requests.post(f'{self.proto}://{self.host}:{self.port}/role-strategy/strategy/unassignRole', data=data,
                            headers=headers, auth=(self.username, self.pwd_or_token))
        return res.status_code

    def get_all_roles(self, role_type):
        """
        获取指定类型角色下的所有角色以及角色下的用户
        返回结果示例:{"p1":[],"p2":["zm"],"test":["zm"]}
        :param role_type:
        :return:
        """
        if role_type not in ('globalRoles', 'projectRoles', 'slaveRoles'):
            raise AttributeError("role_type必须是'globalRoles', 'projectRoles', 'slaveRoles' 其中一个")

        params = {
            "type": role_type
        }

        res = requests.get(f'{self.proto}://{self.host}:{self.port}/role-strategy/strategy/getAllRoles',
                           params=params, auth=(self.username, self.pwd_or_token))
        return res.json()

 

MySql Lock wait timeout exceeded该如何处理?


2020年8月28日 17:22:02   1,789 次浏览

这个问题我相信大家对它并不陌生,但是有很多人对它产生的原因以及处理吃的不是特别透,很多情况都是交给DBA去定位和处理问题,接下来我们就针对这个问题来展开讨论。

Mysql造成锁的情况有很多,下面我们就列举一些情况:

  1. 执行DML操作没有commit,再执行删除操作就会锁表。
  2. 在同一事务内先后对同一条数据进行插入和更新操作。
  3. 表索引设计不当,导致数据库出现死锁。
  4. 长事物,阻塞DDL,继而阻塞所有同表的后续操作。

但是要区分的是Lock wait timeout exceededDead Lock是不一样。

  • Lock wait timeout exceeded:后提交的事务等待前面处理的事务释放锁,但是在等待的时候超过了mysql的锁等待时间,就会引发这个异常。
  • Dead Lock:两个事务互相等待对方释放相同资源的锁,从而造成的死循环,就会引发这个异常。

还有一个要注意的是innodb_lock_wait_timeoutlock_wait_timeout也是不一样的。

  • innodb_lock_wait_timeout:innodb的dml操作的行级锁的等待时间
  • lock_wait_timeout:数据结构ddl操作的锁的等待时间

如何查看innodb_lock_wait_timeout的具体值?

SHOW VARIABLES LIKE 'innodb_lock_wait_timeout'

如何修改innode lock wait timeout的值?

参数修改的范围有Session和Global,并且支持动态修改,可以有两种方法修改:

方法一:

通过下面语句修改

set innodb_lock_wait_timeout=100;
set global innodb_lock_wait_timeout=100;

ps. 注意global的修改对当前线程是不生效的,只有建立新的连接才生效。

方法二:

修改参数文件/etc/my.cnf innodb_lock_wait_timeout = 50

ps. innodb_lock_wait_timeout指的是事务等待获取资源等待的最长时间,超过这个时间还未分配到资源则会返回应用失败; 当锁等待超过设置时间的时候,就会报如下的错误;ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction。其参数的时间单位是秒,最小可设置为1s(一般不会设置得这么小),最大可设置1073741824秒,默认安装时这个值是50s(默认参数设置)。

下面介绍在遇到这类问题该如何处理

问题现象

  • 数据更新或新增后数据经常自动回滚。
  • 表操作总报 Lock wait timeout exceeded 并长时间无反应

解决方法

  • 应急方法:show full processlist; kill掉出现问题的进程。 ps.有的时候通过processlist是看不出哪里有锁等待的,当两个事务都在commit阶段是无法体现在processlist上
  • 根治方法:select * from innodb_trx;查看有是哪些事务占据了表资源。 ps.通过这个办法就需要对innodb有一些了解才好处理

说起来很简单找到它杀掉它就搞定了,但是实际上并没有想象的这么简单,当问题出现要分析问题的原因,通过原因定位业务代码可能某些地方实现的有问题,从而来避免今后遇到同样的问题。

innodb_*表的解释

MysqlInnoDB存储引擎是支持事务的,事务开启后没有被主动Commit。导致该资源被长期占用,其他事务在抢占该资源时,因上一个事务的锁而导致抢占失败!因此出现 Lock wait timeout exceeded

下面几张表是innodb的事务和锁的信息表,理解这些表就能很好的定位问题。

innodb_trx ## 当前运行的所有事务 innodb_locks ## 当前出现的锁 innodb_lock_waits ## 锁等待的对应关系

下面对 innodb_trx 表的每个字段进行解释:

trx_id:事务ID。
trx_state:事务状态,有以下几种状态:RUNNING、LOCK WAITROLLING BACK 和 COMMITTING。
trx_started:事务开始时间。
trx_requested_lock_id:事务当前正在等待锁的标识,可以和 INNODB_LOCKS 表 JOIN 以得到更多详细信息。
trx_wait_started:事务开始等待的时间。
trx_weight:事务的权重。
trx_mysql_thread_id:事务线程 ID,可以和 PROCESSLISTJOIN。
trx_query:事务正在执行的 SQL 语句。
trx_operation_state:事务当前操作状态。
trx_tables_in_use:当前事务执行的 SQL 中使用的表的个数。
trx_tables_locked:当前执行 SQL 的行锁数量。
trx_lock_structs:事务保留的锁数量。
trx_lock_memory_bytes:事务锁住的内存大小,单位为 BYTES。
trx_rows_locked:事务锁住的记录数。包含标记为 DELETED,并且已经保存到磁盘但对事务不可见的行。
trx_rows_modified:事务更改的行数。
trx_concurrency_tickets:事务并发票数。
trx_isolation_level:当前事务的隔离级别。
trx_unique_checks:是否打开唯一性检查的标识。
trx_foreign_key_checks:是否打开外键检查的标识。
trx_last_foreign_key_error:最后一次的外键错误信息。
trx_adaptive_hash_latched:自适应散列索引是否被当前事务锁住的标识。
trx_adaptive_hash_timeout:是否立刻放弃为自适应散列索引搜索 LATCH 的标识。

下面对 innodb_locks 表的每个字段进行解释:

lock_id:锁 ID。
lock_trx_id:拥有锁的事务 ID。可以和 INNODB_TRX 表 JOIN 得到事务的详细信息。
lock_mode:锁的模式。有如下锁类型:行级锁包括:S、X、IS、IX,分别代表:共享锁、排它锁、意向共享锁、意向排它锁。表级锁包括:S_GAP、X_GAP、IS_GAP、IX_GAP 和 AUTO_INC,分别代表共享间隙锁、排它间隙锁、意向共享间隙锁、意向排它间隙锁和自动递增锁。
lock_type:锁的类型。RECORD 代表行级锁,TABLE 代表表级锁。
lock_table:被锁定的或者包含锁定记录的表的名称。
lock_index:当 LOCK_TYPE=’RECORD’ 时,表示索引的名称;否则为 NULL。
lock_space:当 LOCK_TYPE=’RECORD’ 时,表示锁定行的表空间 ID;否则为 NULL。
lock_page:当 LOCK_TYPE=’RECORD’ 时,表示锁定行的页号;否则为 NULL。
lock_rec:当 LOCK_TYPE=’RECORD’ 时,表示一堆页面中锁定行的数量,亦即被锁定的记录号;否则为 NULL。
lock_data:当 LOCK_TYPE=’RECORD’ 时,表示锁定行的主键;否则为NULL

下面对 innodb_lock_waits 表的每个字段进行解释:

requesting_trx_id:请求事务的 ID。
requested_lock_id:事务所等待的锁定的 ID。可以和 INNODB_LOCKS 表 JOIN。
blocking_trx_id:阻塞事务的 ID。
blocking_lock_id:某一事务的锁的 ID,该事务阻塞了另一事务的运行。可以和 INNODB_LOCKS 表 JOIN。

锁等待的处理步骤

  • 直接查看 innodb_lock_waits 表
SELECT * FROM innodb_lock_waits;
  • innodb_locks 表和 innodb_lock_waits 表结合:
SELECT * FROM innodb_locks WHERE lock_trx_id IN (SELECT blocking_trx_id FROM innodb_lock_waits);
  • innodb_locks 表 JOIN innodb_lock_waits 表:
SELECT innodb_locks.* FROM innodb_locks JOIN innodb_lock_waits ON (innodb_locks.lock_trx_id = innodb_lock_waits.blocking_trx_id);
  • 查询 innodb_trx 表:
SELECT trx_id, trx_requested_lock_id, trx_mysql_thread_id, trx_query FROM innodb_trx WHERE trx_state = 'LOCK WAIT';
  • trx_mysql_thread_id 即kill掉事务线程 ID
SHOW ENGINE INNODB STATUS ;
SHOW PROCESSLIST ;

从上述方法中得到了相关信息,我们可以得到发生锁等待的线程 ID,然后将其 KILL 掉。 KILL 掉发生锁等待的线程。

kill ID;

Python调用STS临时授权访问OSS


2020年8月12日 20:33:45   3,279 次浏览

OSS可以通过阿里云STS(Security Token Service)进行临时授权访问。通过STS,您可以为第三方应用或子用户(即用户身份由您自己管理的用户)颁发一个自定义时效和权限的访问凭证。

实现逻辑:前端用户点击上传图片, 前端向后端发起请求,后端返回一个签名后的url ,前端拿到签名后url再进行用Put方法

调用OSS的SDK时报“SignatureDoesNotMatch”错误, 由于前端直接向签名url put 存在跨域问题,需要把oss的跨域给打开, 使用:set_oss_cors 方法

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @File  : sts_token.py
# @Author: 往事随风
# @Email: gujiwork@outlook.com
# @Date  : 2020/12/24
# @Desc  :
# pip install aliyun-python-sdk-sts
# pip install oss2
from aliyunsdkcore import client
from aliyunsdksts.request.v20150401 import AssumeRoleRequest
import json
import oss2
import requests
from oss2.models import BucketCors, CorsRule


class AliStsGenerateToKey:
    def __init__(self, endpoint, access_key_id, access_key_secret, bucket_name):
        """
        :param endpoint: 地域
        :param access_key_id: ram子用户key
        :param access_key_secret: ram子用户 secret
        :param bucket_name: oss bucket名称
        """
        self.endpoint = endpoint
        self.access_key_id = access_key_id
        self.access_key_secret = access_key_secret
        self.bucket_name = bucket_name

    @staticmethod
    def generate_sts_key(self, role_arn):
        object_name = ''
        # 设置sts token获取的权限策略, action及resource
        policy_text = '{"Statement": [{"Action": ["*"],"Effect": "Allow","Resource": ["*"]}],"Version":"1"}'
        # policy_text = '{"Statement": [{"Action": ["oss:GetObject"],"Effect": "Allow","Resource": ["acs:oss:*:*:oss-test/*"]}],"Version":"1"}'
        clt = client.AcsClient(self.access_key_id, self.access_key_secret, 'cn-hangzhou')
        req = AssumeRoleRequest.AssumeRoleRequest()
        req.set_accept_format('json')
        req.set_RoleArn(role_arn)
        # 设置会话名称,审计服务使用此名称区分调用者
        req.set_RoleSessionName('test')
        req.set_Policy(policy_text)
        body = clt.do_action_with_exception(req)

        return body

    def set_oss_sign(self, role_arn, save_oss_file_name, exp_time, upload_file):
        """
        :param role_arn: 角色的资源名称
        :param save_oss_file_name:  保存到oss目录/文件名  settlement_excel_download/20200812200526.png
        :param exp_time: 过期时间
        :param upload_file: 本地上传文件名
        :return: 签名后的url地址
        """
        body = AliStsGenerateToKey.generate_sts_key(self, role_arn=role_arn)

        # 使用RAM账号的AccessKeyId和AccessKeySecret向STS申请临时token。
        token = json.loads(oss2.to_unicode(body))

        # 使用临时token中的认证信息初始化StsAuth实例。
        auth = oss2.StsAuth(token['Credentials']['AccessKeyId'],
                            token['Credentials']['AccessKeySecret'],
                            token['Credentials']['SecurityToken'])
        # 使用StsAuth实例初始化存储空间。
        bucket = oss2.Bucket(auth, self.endpoint, self.bucket_name)

        sign_url_put = bucket.sign_url('PUT', save_oss_file_name, exp_time)
        print(sign_url_put)
        rsp = requests.put(url=sign_url_put, data=open(upload_file, 'rb'))
        if rsp.status_code == 200:
            print('请求上传文件返回状态码: 200')

        file_exists = bucket.object_exists(key=save_oss_file_name)
        if file_exists:
            sign_url = bucket.sign_url('GET', save_oss_file_name, exp_time)
            print(sign_url)
            if requests.get(url=sign_url).status_code != 200:
                return False, '设置签名未生效或已过期, 请检查oss是否设置跨域、 oss权限及sts权限设置是否正确、过期策略时间!'
            return True, sign_url
        print('文件上传失败!')
        return False, '文件上传失败!'


class AliOssCors:
    def __init__(self, access_key_id, access_key_secret, endpoint, bucket_name):
        self.access_key_id = access_key_id
        self.access_key_secret = access_key_secret
        self.endpoint = endpoint
        self.bucket_name = bucket_name
        self.auth = oss2.Auth(self.access_key_id, self.access_key_secret)

    def get_oss_cors(self):
        bucket = oss2.Bucket(self.auth, self.endpoint, self.bucket_name)
        try:
            cors = bucket.get_bucket_cors()
        except oss2.exceptions.NoSuchCors:
            print('OSS未设置跨域! 开始设置跨域----->')
            AliOssCors.set_oss_cors(self)
            AliOssCors.get_oss_cors(self)
        else:
            print('获取跨域规则----->')
            for rule in cors.rules:
                print('AllowedOrigins={0}'.format(rule.allowed_origins))
                print('AllowedMethods={0}'.format(rule.allowed_methods))
                print('AllowedHeaders={0}'.format(rule.allowed_headers))
                print('ExposeHeaders={0}'.format(rule.expose_headers))
                print('MaxAgeSeconds={0}'.format(rule.max_age_seconds))

    @staticmethod
    def set_oss_cors(self):
        bucket = oss2.Bucket(self.auth, self.endpoint, self.bucket_name)
        rule = CorsRule(allowed_origins=['*'],
                        allowed_methods=['GET', 'HEAD', 'PUT', 'POST'],
                        allowed_headers=['*'],
                        max_age_seconds=0)
        # 注意:如果已存在的规则将会被覆盖。
        bucket.put_bucket_cors(BucketCors([rule]))

    def delete_oss_cors(self):
        bucket = oss2.Bucket(self.auth, self.endpoint, self.bucket_name)
        bucket.delete_bucket_cors()


oss_obj = AliOssCors(
    access_key_id='xxxxxxxxxxxxxxxxx',
    access_key_secret='xxxxxxxxxxxxxxxxx',
    endpoint='oss-cn-hangzhou.aliyuncs.com',
    bucket_name='xxxxxxxxxxxxxxxxx')
oss_obj.get_oss_cors()


p = AliStsGenerateToKey(
    endpoint='oss-cn-hangzhou.aliyuncs.com',
    access_key_id='xxxxxxxxxxxxxxxxx',
    access_key_secret='xxxxxxxxxxxxxxxxx',
    bucket_name='xxxxxxxxxxxxxxxxx')
p.set_oss_sign(
    role_arn='acs:ram::{xxxxxxxxxxxxxxxxx}',
    save_oss_file_name='test.png',
    exp_time=300,
    upload_file='22.png'  # 本地图片
)

 

STS中临时授权时出现“You are not authorized to do this action. You should be authorized by RAM“报错

代码中使用的AccessKey和AccessKeySecret是主账号的,并非RAM用户的。 必须要创建子帐号才key才可以

 

https://help.aliyun.com/document_detail/100624.html?spm=a2c4g.11186623.2.10.5e474529lXELjN#concept-xzh-nzk-2gb

https://help.aliyun.com/document_detail/28798.html?spm=a2c4g.11186623.2.10.29bc203dUpOWmQ#reference-smb-tzy-xdb

https://help.aliyun.com/document_detail/32033.html?spm=a2c4g.11186623.2.23.64e33b49Q6fLpK#section-zx1-55k-kfc

ansible register以及循环,判断


2020年7月02日 12:41:19   2,136 次浏览

register

注意:

register变量的命名不能用 -中横线,比如dev-sda6_result,则会被解析成sda6_result,dev会被丢掉,所以不要用-
ignore_errors这个关键字很重要,一定要配合设置成True,否则如果命令执行不成功,即 echo $?不为0,则在其语句后面的ansible语句不会被执行,导致程序中止。

拉取远程主机 /tmp/test/目录下所有文件

- hosts: ansible-demo3 
  tasks:
    - name: find
      find: paths=/tmp/test/ patterns="*" recurse=yes
      register: file_list

    - name: get
      fetch:src="{{ item.path }}" dest=./
      with_items: "{{ file_list.files }}"

循环

ansible中的循环都是借助迭代来实现的。基本都是以”with_”开头。以下是常见的几种循环。

with_items迭代列表

ansibel支持迭代功能。例如,有一大堆要输出的命令、一大堆要安装的软件包、一大堆要copy的文件等等。

例如,要安装一堆软件包。

---
    - hosts: localhost
      tasks: 
        - yum: name="{{item}}" state=installed
          with_items: 
            - pkg1
            - pkg2
            - pkg3

它会一个一个迭代到特殊变量{{item}}处。

with_dict迭代字典项

使用”with_dict”可以迭代字典项。迭代时,使用”item.key”表示字典的key,”item.value”表示字典的值。

例如:

---
    - hosts: localhost
      tasks:
        - debug: msg="{{item.key}} & {{item.value}}"
          with_dict: { address: 1,netmask: 2,gateway: 3 }

另一种情况,字典是已存储好的。例如ansible facts中的ansible_eth0.ipv4,其内容如下:

"ipv4": {
    "address": "192.168.100.65",
    "netmask": "255.255.255.0",
    "gateway": "192.168.100.2"
}

这种情况下,with_dict处可以直接指定该字典的key。即:

---
    - hosts: localhost
      tasks:
        - debug: msg="{{item.key}} & {{item.value}}"
          with_dict: ansible_eth0.ipv4

再例如,直接引用playbook中定义的vars。

---
 - hosts: 192.168.100.65
   gather_facts: False
   vars:
     user: 
        longshuai_key: 
           name: longshuai
           gender: Male
        xiaofang_key: 
           name: xiaofang
           gender: Female
   tasks:
      - name: print hash loop var
        debug: msg="{{ item.key }} & {{ item.value.name }} & {{ item.value.gender }}"
        with_dict: "{{ user }}"

with_fileglob迭代文件

例如,拷贝一堆用通配符匹配出来的文件到各远程主机上。

---
    - hosts: centos
      tasks: 
        - copy: src="{{item}}" dest=/tmp/
          with_fileglob:
            - /tmp/*.sh
            - /tmp/*.py

注意,通配符无法匹配”/”,因此无法递归到子目录中,也就无法迭代子目录中的文件。

with_lines迭代行

with_lines很好用,可以将命令行的输出结果按行迭代。

例如,find一堆文件出来,copy走。

---
    - hosts: localhost
      tasks:
        - copy: src="{{item}}" dest=/tmp/yaml
          with_lines:
            - find /tmp -type f -name "*.yml"

with_nested嵌套迭代

嵌套迭代是指多次迭代列表项。例如:

---
    - hosts: localhost
      tasks:
        - debug: msg="{{item[0]}} & {{item[1]}}"
          with_nested: 
            - [a,b]
            - [1,2,3]

结果将得到”a & 1″、”a & 2″、”a & 3″、”b & 1″、”b & 2″和”b & 3″共6个结果。

条件判断

在ansible中,只有when可以实现条件判断。

tasks: 
  - name: config the yum repo for centos 6
    yum_repository:
       name: epel
       description: epel
       baseurl: http://mirrors.aliyun.com/epel/6/$basearch/
       gpgcheck: no
    when: ansible_distribution_major_version == "6"

注意两点:

when判断的对象是task,所以和task在同一列表层次。它的判断结果决定它所在task是否执行,而不是它下面的task是否执行。

when中引用变量的时候不需要加{{ }}符号。

此外,还支持各种逻辑组合。

tasks:

# 逻辑或
  - command: /sbin/shutdown -h now
    when: (ansible_distribution == "CentOS" and ansible_distribution_major_version == "6") or
          (ansible_distribution == "Debian" and ansible_distribution_major_version == "7")

# 逻辑与
  - command: /sbin/shutdown -t now
    when:
      - ansible_distribution == "CentOS"
      - ansible_distribution_major_version == "6"

# 取反
  - command: /sbin/shutdown -t now
    when: not ansible_distribution == "CentOS"

还可以直接直接引用布尔值的变量。

---
    - hosts: localhost
      vars:
        epic: False

      tasks:
        - debug: msg="This certainly is epic!"
          when: not epic

此外,可以使用jinja2的defined来测试变量是否已定义,使用undefined可以取反表示未定义。例如:

tasks:
    - shell: echo "I've got '{{ foo }}' and am not afraid to use it!"
      when: foo is defined

    - fail: msg="Bailing out. this play requires 'bar'"
      when: bar is undefined