Fork me on GitHub
Suzf  Blog

Archive Tech

How to get notified on specific Lambda function error patterns using CloudWatch

这篇文章演示了如何使用 Amazon CloudWatch log subscription 自动对特定 AWS Lambda 函数错误发出警报通知。CloudWatch Logs 让您可以在日志条目与模式匹配时调用 Lambda 函数。 Amazon CloudWatch alarms 用于在错误发生时发出通知Lambda 函数;这个通知不会提供有关错误任何的细节。对于您需要在通知中显示具体说明错误的情况,您可以使用 CloudWatch Logs subscription来实现。CloudWatch Logs 订阅让您可以匹配日志中具有特定错误模式的条目,并收到有关这些错误详细信息的通知。这为您节省了额外的步骤解析日志。当在您的 Lambda 函数中检测到该错误模式时,您还可以将其用作构建自动反应措施的蓝图。

这篇文章将向您介绍如何配置触发 AWS Lambda 函数来处理匹配日志的 CloudWatch 日志订阅。Lambda 函数使用 Amazon SNS  发送包含特定错误详细信息和日志位置的电子邮件。

解决方案架构图

Architecture Diagram showing how cloudwatch log subscription triggers error processing lambda function to send email notification with specific log error

此解决方案的架构相对简单。您有一堆 Lambda 函数,您希望收到有关其特定严重错误的通知。CloudWatch Logs 从这些 Lambda 函数中过滤特定模式的日志。例如 ERROR、CRITICAL 或自定义错误。错误处理 Lambda 函数依次向 Amazon SNS 主题发布消息,可以订阅该主题以在发生错误时收到电子邮件。

出于本文的目的,我们将使用以下示例 Lambda 函数生成错误日志:

import logging
import os

logging.basicConfig(level=logging.DEBUG)
logger=logging.getLogger(__name__)


def lambda_handler(event, context):
    logger.setLevel(logging.DEBUG)
    logger.debug("This is a sample DEBUG message.. !!")
    logger.error("This is a sample ERROR message.... !!")
    logger.info("This is a sample INFO message.. !!")
    logger.critical("This is a sample 5xx error message.. !!")

部署教程

前置条件

要实施此解决方案,您必须创建:

  • SNS topic
  • IAM role
  • Lambda function
  • CloudWatch log trigger

步骤 1: 创建 SNS topic

要创建 SNS 主题,请完成以下步骤:

  1. 打开 Amazon SNS console
  2. 在左侧导航栏选择 Topics
  3. 选择创建 topic. 选择 Standard 类型,输入 topic name `MySNSTopic` 点击 Create topic.  创建 成功后自动跳转到 MySNSTopic 页面。详细信息部分显示主题的名称、ARN、显示名称(可选)和主题所有者的 AWS 账户 ID。
  4. 在详情页面,拷贝 topic ARN到粘贴板,比如: arn:aws:sns:us-east-1:123456789012:MySNSTopic
  5. 在做了导航,选择 Subscriptions and Create subscription.
  6. Create subscription 页面,做一下操作:
    1. 输入之前拷贝的 topic ARN: arn:aws:sns:us-east-1:123456789012:MySNSTopic
    2. Protocol 选择 Email
    3. Endpoint, 输入可以接受邮件的邮件地址
    4. 选择 Create subscription.
  7. 请注意,对于电子邮件订阅,您必须通过点击电子邮件收到的确认订阅链接来确认订阅。确认订阅后,您就可以接收电子邮件通知了。

步骤 2: 创建 IAM role

创建 IAM role, 需要完成一下操作。获取更多信息,请看 Creating an IAM role

  1. IAM console 页面,  在左侧导航栏 选择  Policies, 之后选择 Create Policy.
  2. 选择 JSON 选项卡 输入下面 IAM policy, 用之前创建的SNS topic ARN  替换 Topic ARN :
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "sns:Publish",
            "Resource": "arn:<partition>:sns:<region>:<AWS account number>:<name of the SNS topic from previous step>"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:<partition>:logs:<region>:<AWS account number>:log-group:/aws/lambda/<name of the lambda function you are going to create in next step>:*"
        }
    ]
}
  1. 选择 Review policy.
  2. 给这个 policy 输入 name (MyCloudWatchRole) 并 Create policy。记下此策略的名称以供后续步骤使用
  3. 在左侧导航栏, 选择 Roles 点击 Create role.
  4. Select role type 页面, 选择 AWS 服务作为您的可信实体,并在常见用例下选择 Lambda。
  5. 选择 Next: Permissions.
  6. 过滤刚刚创建的策略名称,然后选中该复选框。
  7. 选择 Next: Tags, 并给它一个合适的标签。
  8. 选择 Next: Review. 为这个 IAM 角色指定一个合适的名称,并记下来以备将来使用。
  9. 选择 Create role.

步骤 3: 创建 Lambda function

要创建 Lambda 函数,请完成以下步骤。 获取更多信息,参见 Create a Lambda Function with the console.

  1. Lambda console, 选择 Author from scratch。
  2. Function Name, 输入函数名称。
  3.  Runtime, 选择 Python 3.7.
  4. Execution role, 选择 Use an existing role, 选择之间创建的 IAM role.
  5. 选择 Create Function, 移除 default function, 拷贝下列代码到 Function Code 窗口:
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# A copy of the License is located at## http://aws.amazon.com/apache2.0/
# or in the "license" file accompanying this file.
# This file is distributed on an "AS IS" 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.
# Description: This Lambda function sends an email notification to a given AWS SNS topic when a particular
#              pattern is matched in the logs of a selected Lambda function. The email subject is
#              Execution error for Lambda-<insert Lambda function name>.
#              The JSON message body of the SNS notification contains the full event details.

# Author: Sudhanshu Malhotra

import base64
import boto3
import gzip
import json
import logging
import os

from botocore.exceptions import ClientError

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


def logpayload(event):
    logger.setLevel(logging.DEBUG)
    logger.debug(event['awslogs']['data'])
    compressed_payload = base64.b64decode(event['awslogs']['data'])
    uncompressed_payload = gzip.decompress(compressed_payload)
    log_payload = json.loads(uncompressed_payload)
    return log_payload


def error_details(payload):
    error_msg = ""
    log_events = payload['logEvents']
    logger.debug(payload)
    loggroup = payload['logGroup']
    logstream = payload['logStream']
    lambda_func_name = loggroup.split('/')
    logger.debug(f'LogGroup: {loggroup}')
    logger.debug(f'Logstream: {logstream}')
    logger.debug(f'Function name: {lambda_func_name[3]}')
    logger.debug(log_events)
    for log_event in log_events:
        error_msg += log_event['message']
    logger.debug('Message: %s' % error_msg.split("\n"))
    return loggroup, logstream, error_msg, lambda_func_name


def publish_message(loggroup, logstream, error_msg, lambda_func_name):
    sns_arn = os.environ['snsARN']  # Getting the SNS Topic ARN passed in by the environment variables.
    snsclient = boto3.client('sns')
    try:
        message = ""
        message += "\nLambda error  summary" + "\n\n"
        message += "##########################################################\n"
        message += "# LogGroup Name:- " + str(loggroup) + "\n"
        message += "# LogStream:- " + str(logstream) + "\n"
        message += "# Log Message:- " + "\n"
        message += "# \t\t" + str(error_msg.split("\n")) + "\n"
        message += "##########################################################\n"

        # Sending the notification...
        snsclient.publish(
            TargetArn=sns_arn,
            Subject=f'Execution error for Lambda - {lambda_func_name[3]}',
            Message=message
        )
    except ClientError as e:
        logger.error("An error occured: %s" % e)


def lambda_handler(event, context):
    pload = logpayload(event)
    lgroup, lstream, errmessage, lambdaname = error_details(pload)
    publish_message(lgroup, lstream, errmessage, lambdaname)
  1. Environment variables 中,输入以下键值对:
    • Key= snsARN
    • Value= the ARN of the MySNSTopic created earlier
  2. 点击 Save.

步骤 4. 创建 CloudWatch log trigger

  1. 要添加触发器,请选择添加触发器,然后从下拉列表中选择 CloudWatch Logs。
  2. Log group 下拉列表,选择要获取错误通知的 Lambda 函数的 CloudWatch 日志组名称。在我们的例子中,这将是上面讨论的示例错误生成 Lambda 函数的日志组。
  3. 在过滤器名称输入适当的值,并在过滤器模式下,输入您希望收到通知的日志的过滤器值。例如 - “?ERROR ?WARN ?5xx” 将过滤 日志中包含 ERROR、WARN 或 5xx 的日志。 Log filter and pattern syntax 中包含更多其他复杂模式的示例。Lambda function trigger configuration screenshot showing with filter pattern- ?ERROR ?WARN ?5xx
  4. 启用 trigger & 添加

解决方案验证

为验证我的解决方案,我将运行生成错误日志 Lambda 函数并过滤日志中包含 “?ERROR ?WARN ?5xx” 的日志进行邮件通知,如下所示:

email notification screenshot showing specific error messages like [ERROR] and [CRITICAL]

同样,我可以为任何特定错误创建一个过滤器模式,例如过包含 5xx 的日志,并仅针对该错误日志获得以下通知:

Lambda function trigger configuration screenshot with error filter pattern set to 5xx

email notification screenshot showing specific error messages- 5xx error message

卸载

为了避免持续收费,删除在前面的步骤中创建的资源,包括 CloudWatch Events rule Lambda function SNS topic

结论

这篇文章演示了如何使用 CloudWatch Log 过滤器来解析 Lambda 函数的日志并通过电子邮件通知过滤到的错误信息。有关进一步阅读,请参阅:

 

此文为翻译 源链接为 https://aws.amazon.com/blogs/mt/get-notified-specific-lambda-function-error-patterns-using-cloudwatch/

感谢  Sudhanshu Malhotra 和 Rajat Mathur 的分享

Terraform – External Data Source

前言

简而言之,外部数据源(不是特别推荐!) 意味着试图从外部数据源获取一些信息并将其呈现给Terraform。Terraform外部数据源执行shell脚本或python或任何其他程序。Terraform像使用其他数据源一样使用程序的输出。这意味着外部数据源为Terraform提供了一种与外部世界交互的方式。这是非常有用的!

注意: 外部数据源与AWS、Azure或谷歌云提供商或任何其他云提供商无关。

 

外部数据源基本介绍

如上所述的外部数据源允许Terraform与外部环境进行数据交互。那么这里的捕获信息是什么(这里总是有一个捕获信息),程序的输入和输出必须是JSON对象。要通过shell脚本使用Terraform外部数据源,您最好了解 jq。

注意:外部数据源允许Terraform与外部环境进行交互。敲黑板,所有必需的软件都要事先安装好,否则以后可能会出现令人讨厌的意外。只有当你在Terraform 中找不到解决方案时才使用它。

 

一个潜在的用例-我发现这很有用(您可能不同意),当获取有关AWS(或任何其他云)资源的信息时,Terraform数据资源没有提供有关该云资源的所有信息。例如,用于Kafka的AWS Managed Service的Terraform数据资源没有为代理提供子网信息(至少在我写代码之前没有)。尽管这些信息可以通过AWS CLI或boto库获得。

这里没有太多参数可以传递给外部数据源,只有两个!

外部数据源参数描述
程序Python 或 shell 的可执行程序
查询作为JSON传递给程序的变量

注意: 程序被 locally 所执行

最后,这里是我们博客的一个用例,我们想根据传递给shell脚本的environment 和url这两个参数返回ip地址和端口。这些信息在Terraform中是不可用的,因为它总是神奇地变化;)。在我们继续之前,shell脚本和python的示例几乎都做同样的事情,只是为了说明这篇文章的目的。

 

外部数据源 & shell 脚本

让我们看看如何通过调用shell脚本获取terrraform外部数据源。将执行以下步骤

创建一个shell脚本
从Terraform外部数据源调用shell脚本
向它传递一些参数并进行一些处理。
处理完成后,脚本将输出作为JSON对象返回到terraform。

注意: 确保您已经安装了jq,除非您想手动处理JSON对象,同时安装您的shell脚本任何其他库/包。

 

Terraform 脚本

我们的terrraform脚本ext_data_source.tf是非常简单的,它调用shell脚本get_ip_port.sh,并传递一个参数p_env,它的值是dev。现在,这个值可以是其他任何东西,作为Terraform提供的资源的一部分,它是动态生成的!!

data "external" "get_ip_addres_using_shell_dev" {
 program = ["bash","scripts/get_ip_port.sh"]
 query = {
   p_env = "dev"
 }
}
output "ip_address_for_dev" {
 value = data.external.get_ip_addres_using_shell_dev.result.ip_address
}
output "port_num_for_dev" {
 value = data.external.get_ip_addres_using_shell_dev.result.port_num
}

Shell 脚本

#!/bin/bash
# Step#0 - Magical list of ip addresses and ports which cannot exist in terraform
declare -A test_var
test_var["dev"]="10.0.0.1:8081"
test_var["qa"]="10.0.0.2:8082"
test_var["uat"]="10.0.0.3:8083"
test_var["stage"]="10.0.0.4:8084"
test_var["prod"]="10.0.0.5:8085"
# Step#1 - Parse the input
eval "$(jq -r '@sh "p_env=\(.p_env)"')"
# Step#2 - Extract the ip address and port number based on the key passed
url_str=${test_var[$p_env]}
arr=(${url_str//:/ })
IP_ADDRESS=${arr[0]}
PORT_NUM=${arr[1]}
# Step#3 - Create a JSON object and pass it back
jq -n --arg ip_address "$IP_ADDRESS" \
     --arg port_num "$PORT_NUM" \
     '{"ip_address":$ip_address, "port_num":$port_num}'

让我们简单看一下shell脚本的逻辑

Step#1 – 输入解析为 JSON,数据被提取到一个名为 p_env 的变量中
Step#2 – 变量p_dev用于从hashmap中提取ip地址和端口号
Step#3 – 使用 jq 返回包含 ip 地址和端口号的 JSON 对象

让我们看一下运行terrraform apply 之后的输出

Do you want to perform these actions?
 Terraform will perform the actions described above.
 Only 'yes' will be accepted to approve.
 Enter a value: yes

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
ip_address_for_dev = "10.0.0.1"
port_num_for_dev = "8081"

 

外部数据源 & Python

Terraform 脚本

我们的terrraform脚本ext_data_source.tf非常简单,它调用Python脚本get_ip_port.py,并传递一个值为qa到参数p_env。正如您所看到的,这与前一节中的非常相似。

data "external" "get_ip_addres_using_python" {
 program = ["python3","scripts/get_ip_port.py"]
 query = {
   p_env = "qa"
 }
}
output "ip_address_for_qa" {
 value = data.external.get_ip_addres_using_python_dev.result.ip_address
}
output "port_num_for_qa" {
 value = data.external.get_ip_addres_using_python_dev.result.port_num
}

Python Script

import sys
import json
# Magical list of ip addresses and ports which cannot exist in terraform
test_var = dict()
test_var["dev"]   = "10.0.0.1:8081"
test_var["qa"]    = "10.0.0.2:8082"
test_var["uat"]   = "10.0.0.3:8083"
test_var["stage"] = "10.0.0.4:8084"
test_var["prod"]  = "10.0.0.5:8085"
# Step#1 - Parse the input
input = sys.stdin.read()
input_json = json.loads(input)
# Step#2 - Extract the ip address and port number based on the key passed
arr = test_var[input_json.get("p_env")].split(":")
ip_address = arr[0]
port_num = arr[1]

# Step#3 -  Create a JSON object and just print it(i.e send it to stdout)
output = {
   "ip_address": ip_address,
   "port_num": port_num
}
output_json = json.dumps(output,indent=2)
print(output_json)

下面是在python脚本中完成的步骤的概述,这些步骤与前一节中的shell脚本非常相似

Step#1 – 输入解析
Step#2 –  从字典中提取ip地址和端口号
Step#3 – 返回包含ip地址和端口号的JSON对象

让我们看一下运行terrraform apply 之后的输出

Do you want to perform these actions?
 Terraform will perform the actions described above.
 Only 'yes' will be accepted to approve.
 Enter a value: yes

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
ip_address_for_dev = "10.0.0.2"
port_num_for_dev = "8082"

从上面可以看出,外部数据源是将Terraform与外部环境连接起来的非常有用的工具。显然也不难理解。希望你觉得这篇文章有用。如果你喜欢它,请分享它并传播知识!

 

源文章 https://www.cloudwalker.io/2021/10/09/terraform-external-data-source/

How-to centralized integration of eventbridge event notifications sent to feishu

简介

在使用亚马逊云的过程中,各种服务的通知事件在日常运维里常常发挥着关键作用。但在实际使用过程中,这些通知常常因为各种原因被忽略而导致意外的损失。如:亚马逊云的维护事件会发送通知邮件到账号的注册邮箱,但这些邮箱通常无人值守,使得在维护事件发生时,客户会遭遇“意外”停机。另外,客户部署的工作负载也有各种自定义的通知希望集成到一个统一的客户端进行提醒;而现在各种流行的即时通信软件都有移动客户端,同时基本都支持webhook机制,外部通过API调用,即可传入相应的信息;因此即时通信软件是接收这些通知的理想终端。

但如果您管理着多个AWS账号,而这些账号可能又属于多个不同的AWS Organization,那您需要在每个账号里分别做配置;另外,日后新增账号时,也得配置一次。那这样看起来工作量也不小。

为此,我们在原有告警通知方案的基础上,增加了对AWS Organization和多子账号, 以便提高您的运维效率。

说明:此方案仅目前仅支持AWS Global Regions,在AWS 北京和宁夏区暂时不支持事件的跨区传输。

How to install Kubernetes with Kubeadm

Kubeadm 是一个提供了 `kubeadm init` 和 `kubeadm join` 的工具,作为创建 Kubernetes 集群的 “快捷途径” 的最佳实践。

kubeadm 通过执行必要的操作来启动和运行最小可用集群。按照设计,它只关注启动引导,而非配置机器。同样的,安装各种 “锦上添花” 的扩展,例如 Kubernetes Dashboard, 监控方案,以及特定云平台的扩展等。

How-to use rsyslog parse log to json format and then store in ES

前言

本文的主要目的是将 Apache 的访问日志转换成 JSON 格式,然后存储到 ES, 供 ELK Stack Platform 进行数据分析与统计。 尽管现在许多发行版本都已经默认安装上了 rsyslog, 但是还是推荐从 rsyslog repositories 获取最新的稳定版本。这样你将从中获益。你将需要下面的软件包:

  • rsyslog-mmnormalize. This gives you mmnormalize, a module that will do the parsing of common Apache logs to JSON
  • rsyslog-elasticsearch, for the Elasticsearch output

让我现在就开始配置吧。需要执行以下操作:

  1. 加载所需模块
  2. 将 Apache log 传送到中心日志服务器
  3. 配置主队列缓冲您的消息。 这也是定义工作线程数和批量大小的地方(也可以是 Elasticsearch 块的大小)
  4. 将 Apache log 转换成 JSON
  5. 定义一个模板,您可以在其中指定 JSON 信息的格式。 您可以使用此模板通过Elasticsearch输出将日志发送到 Elasticsearch / Logstash

[Forward] Centralized logging for fun and profit

Originally posted on Centralized logging for fun and profit

Setting up a centralized log server using syslog isn't as hard as many may believe. Whether it's logs from Apache, nginx, email services, or even from your own Python applications having a central log server gives you many benefits:

Benefits to a centralized logs

  • Reduces disk space usage and disk I/O on core servers that should be busy doing something else. This is especially true if you want to log all queries to your database. Doing this on the same disk as your actual database creates a write for every read and an extra write for every write.
  • Removes logs from the server in the event of an intrusion or system failure. By having the logs elsewhere you at least have a chance of finding something useful about what happened.
  • All of your logs are in one place, duh! This makes things like grepping through say Apache error logs across multiple webservers easier than bouncing around between boxes. Any log processing and log rotation can also be centralized which may delay your sysadmin from finally snapping and killing everyone.