在main之前,IAR都做了什么
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在main之前,IAR都做了什么相关的知识,希望对你有一定的参考价值。
参考技术A 首先系统复位时,Cortex-M3从代码区偏移0x0000'0000处获取栈顶地址,用来初始化MSP寄存器的值。接下来从代码区偏移0x0000'0004获取第一个指令的跳转地址。这些地址,是CM3要求放置中断向量表的地方。
这里是一个程序的启动区的反汇编:
__vector_table:
08004000 2600
08004002 2000
08004004 7E1D
08004006 0800
这个程序是由IAP程序来启动的,IAP程序获取0x0800'4000处的MSP值(0x20002600),并设置为MSP的值,即主堆栈最大
范围是0x2000'0000~0x2000'25FF。接下来IAP程序获取0x0800'4004处的Reset_Handler的地址
(0x0800'7E1D),并跳转到Reset_Handler()执行。
IAP在这里完全是模仿了Cortex-M3的复位序列,也就是说,在没有IAP的系统上,CM3只能从0x0800'0000获取MSP,从
0x0800'0004获取第一条指令所处地址。而IAP就存在在0x0800'0000这个地址上,IAP的启动,已经消耗掉了这个复位序列,所以
IAP要启动UserApp程序的时候,也是完全模仿Cortex-M3的复位序列的。
接下来我们看看复位后第一句指令——Reset_Handler()函数里有什么。
若我们使用的是ST公司标准外设库,那么已经有了现成的Reset_Handler,不过他是弱定义——PUBWEAK,可以被我们重写的同名函数覆盖。一般来说,我们使用的都是ST提供的Reset_Handler,在V3.4版本的库中,可以在startup_stm32f10x_xx.s中找到这个函数:
PUBWEAK Reset_Handler
SECTION .text:CODE:REORDER(2)
Reset_Handler
LDR R0, =SystemInit
BLX R0
LDR R0, =__iar_program_start
BX R0
看来ST没有做太多的事,他只调用了自家库提供的SystemInit函数进行系统时钟、Flash读取的初始化,并把大权交给了
__iar_program_start这个IAR提供的“内部函数”了,我们就跟紧这个__iar_program_start跳转,看看IAR做了什
么,上面一段代码的反汇编如下:
Reset_Handler:
__iar_section$$root:
08007E1C 4801 LDR R0, [PC, #0x4]; LDR R0, =SystemInit
08007E1E 4780 BLX R0;BLX R0
08007E20 4801 LDR R0, [PC, #0x4];LDR R0, =__iar_program_start
08007E22 4700 BX R0;BX R0
08007E24 6C69
08007E26 0800
08007E28 7D8D
08007E2A 0800
细心的观众会发现地址是0x0800'7E1C,比我们查到的0x0800'7E1D差了1,这是ARM家族的遗留问题,因为ARM处理器的指令至
少是半字对齐的(16位THUMB指令集 or
32位ARM指令集),所以PC指针的LSB是常为0的,为了充分利用寄存器,ARM公司给PC的LSB了一个重要的使命,那就是在执行分支跳转时,PC
的LSB=1,表示使用THUMB模式,LSB=0,表示使用ARM模式,但在最新的Cortex-M3内核上,只使用了THUMB-2指令集挑大梁,所
以这一位要常保持1,所以我们查到的地址是0x0800'7E1D(C=1100,D=1101),放心,我们的CM3内核会忽略掉LSB(除非为0,那
么会引起一个fault),从而正确跳转到0x0800'7E1C。
从0x0800'7E20处的加载指令,我们可以算出__iar_program_start所处的位置,就是当前PC指针
(0x0800'7E24),再加上4,即0x0800'7E28处的所指向的地址——0x0800'7D8D(0x0800'7D8C),我们跟紧着跳
转,__iar_program_start果然在这里:
__iar_program_start:
08007D8C F000F88C BL __low_level_init
08007D90 2800 CMP R0, #0x0
08007D92 D001 BEQ __iar_init$$done
08007D94 F7FFFFDE BL __iar_data_init2
08007D98 2000 MOVS R0, #0x0
08007D9A F7FDFC49 BL main
我们看到IAR提供了__low_level_init这个函数进行了“底层”的初始化,进一步跟踪,我们可以查到__low_level_init这个函数做了些什么,不是不是我们想象中的不可告人。
__low_level_init:
08007EA8 2001 MOVS R0, #0x1
08007EAA 4770 BX LR
__low_level_init出乎想象的简单,只是往R0寄存器写入了1,就立即执行"BX
LR"回到调用处了,接下来,__iar_program_start检查了R0是否为0,为0,则执行__iar_init$$done,若不是0,就
执行__iar_data_init2。__iar_init$$done这个函数很简单,只有2句话,第一句是把R0清零,第二句就直接"BL
main",跳转到main()函数了。不过既然__low_level_init已经往R0写入了1,那么我们还是得走下远路——看看
__iar_data_init2做了些什么,虽然距离main只有一步之遥,不过这中间隐藏了编译器的思想,我们得耐心看下去。
__iar_data_init2:
08007D54 B510 PUSH R4,LR
08007D56 4804 LDR R0, [PC, #0x10]
08007D58 4C04 LDR R4, [PC, #0x10]
08007D5A E002 B 0x8007D62
08007D5C F8501B04 LDR R1, [R0], #0x4
08007D60 4788 BLX R1
08007D62 42A0 CMP R0, R4
08007D64 D1FA BNE 0x8007D5C
08007D66 BD10 POP R4,PC
08007D68 7C78
08007D6A 0800
08007D6C 7C9C
08007D6E 0800
看来IAR迟迟不执行main()函数,就是为了执行__iar_data_init2,我们来分析分析IAR都干了些什么坏事~
首先压R4,LR入栈,然后加载0x0800'7C78至R0,0x0800'7C9C至
R4,马上跳转到0x0800'7D62执行R0,R4的比较,结果若是相等,则弹出R4,PC,然后立即进入main()。不过IAR请君入瓮是自不会
那么快放我们出来的——结果不相等,跳转到0x0800'7D5C执行,在这里,把R0指向的地址——0x0800'7C78中的值——
0x0800'7D71加载到R1,并且R0中的值自加4,更新为0x0800'7C7C,并跳转到R1指向的地址处执行,这里是另一个IAR函
数:__iar_zero_init2:
__iar_zero_init2:
08007D70 2300 MOVS R3, #0x0
08007D72 E005 B 0x8007D80
08007D74 F8501B04 LDR R1, [R0], #0x4
08007D78 F8413B04 STR R3, [R1], #0x4
08007D7C 1F12 SUBS R2, R2, #0x4
08007D7E D1FB BNE 0x8007D78
08007D80 F8502B04 LDR R2, [R0], #0x4
08007D84 2A00 CMP R2, #0x0
08007D86 D1F5 BNE 0x8007D74
08007D88 4770 BX LR
08007D8A 0000 MOVS R0, R0
__iar_data_init2还没执行完毕,就跳转到了这个__iar_zero_inti2,且看我们慢慢分析这个帮凶——__iar_zero_inti2做了什么。
__iar_zero_inti2将R3寄存器清零,立即跳转到0x0800'7D80执行'LDR R2, [R0],
#0x4',这句指令与刚才在__iar_data_init2见到的'LDR R1, [R0],
#0x4'很类似,都为“后索引”。这回,将R0指向的地址——0x0800'7C7C中的值——0x0000'02F4加载到R2寄存器,然后R0中的
值自加4,更新为0x0800'7C80。接下来的指令检查了R2是否为0,显然这个函数没那么简单想放我我们,R2的值为2F4,我们又被带到了
0x0800'7D74处,随后4条指令做了如下的事情:
1、将R0指向的地址——0x0800'7C80中的值——0x2000'27D4加载到R1寄存器,然后R0中的值自加4,更新为0x0800'7C84。
2、将R1指向的地址——0x2000'27D4中的值——改写为R3寄存器的值——0,然后R1中的值自加4,更新为0x2000'27D8。
3、R2自减4
4、检查R2是否为0,不为0,跳转到第二条执行。不为,则执行下一条。
这简直就是一个循环!——C语言的循环for(r2=0x2F4;r2-=4;r!=0)...,我们看看循环中做了什么。
第一条指令把一个地址加载到了R1——0x2000'27D4
是一个RAM地址,以这个为起点,在循环中,对长度为2F4的RAM空间进行了清零的操作。那为什么IAR要做这个事情呢?消除什么记录么?用Jlink
查看这片内存区域,可以发现这片区域是我们定义的全局变量的所在地。也就是说,IAR在每次系统复位后,都会自动将我们定义的全局变量清零0。
清零完毕后,接下来的指令"LDR R2, [R0],
#0x4"将R0指向的地址——0x0800'7C84中的值——0加载到R2寄存器,然后R0中的值自加4,更新为0x0800'7C88。随后检查
R2是否为0,这里R2为0,执行'BX
LR'返回到__iar_data_init2函数,若是不为0,我们可以发现又会跳转至“4指令”处进行一个循环清零的操作。
开发和运维对K8S中的应用都做了什么?
微信公众号:运维开发故事,作者:乔克
在应用的整个生命周期里,开发和运维都和它密不可分。一个塑造它,一个保养它。
如果应用需要部署到K8S中,开发和运维在其中都做了什么呢?
开发侧
从开发侧来说,我们的应用应该具备以下能力:
- 具有健康检测接口
- 具有优雅退出能力
- 具有metrics接口
- 能够接入链路追踪系统
- 日志输出标准统一
定义健康检测接口
健康检测接口用于检测应用的健康状态,在K8S中,使用Readiness和Liveness分别来探测应用是否就绪和是否存活,如果未就绪或者未存活,K8S会采取相应的措施来确保应用可用。
如果我们应用未定义好相应的健康检测接口,K8S就无法判断应用是否正常可用,整个应用对我们来说就是黑匣子,也就谈不上应用稳定性了。
定义一个简单的健康检测接口如下:
package router
import (
"github.com/gin-gonic/gin"
v1 "go-hello-world/app/http/controllers/v1"
)
func SetupRouter(router *gin.Engine)
ruc := new(v1.RootController)
router.GET("/", ruc.Root)
huc := new(v1.HealthController)
router.GET("/health", huc.HealthCheck)
package v1
import (
"github.com/gin-gonic/gin"
"go-hello-world/app/http/controllers"
"go-hello-world/pkg/response"
"net/http"
)
type HealthController struct
controllers.BaseController
func (h *HealthController) HealthCheck(c *gin.Context)
response.WriteResponse(c, http.StatusOK, nil, gin.H
"result": "健康检测页面",
"status": "OK",
)
如上我们定义了health
接口,当应用启动后,只需要探测这个接口,如果返回OK,表示应用是正常的。
当然,上面的接口是非常简单的,在实际情况下,应用本身也许还依赖起来应用,比如redis,mysql,mq等,如果它们异常,应用是不是异常的呢?那我们的应用健康检测需不需要检测其他应用的健康状态呢?
既然我们定义好了健康检测接口,那我们的YAML模板就可以增加健康检测功能,如下:
readinessProbe:
httpGet:
path: /health
port: http
timeoutSeconds: 3
initialDelaySeconds: 20
livenessProbe:
httpGet:
path: /health
port: http
timeoutSeconds: 3
initialDelaySeconds: 30
定义优雅下线功能
应用发版是常规不能再常规的操作,通常情况下都是滚动更新的方式上线,也就是先起一个新应用,再删一个老应用。
如果这时候老应用有部分的流量,突然把老应用的进程杀了,这部分流量就无法得到正确的处理,部分用户也会因此受到影响。
怎么才会不受影响呢?
假如我们在停止应用之前先告诉网关或者注册中心,等对方把我们应用摘除后再下线,这样就不会有任何流量受到影响了。
在K8S中,当我们要删除Pod的时候,Pod会变成Terminating状态,kubelet看到Pod的状态如果为Terminating,就会开始执行关闭Pod的流程,给Pod发SIGTERM信号,如果达到宽限期Pod还未结束就给Pod发SIGKILL信号,从Endpoints中摘除Pod等。
从上面可知,Pod在停止之前会收到SIG信号,如果应用本身没有处理这些信号的能力,那应用如果知道什么时候该结束呢?
下面简单定义一个处理SIG信号的功能。
package shutdown
import (
"context"
"fmt"
"net/http"
"os"
"os/signal"
"time"
)
// 优雅退出
type Shutdown struct
ch chan os.Signal
timeout time.Duration
func New(t time.Duration) *Shutdown
return &Shutdown
ch: make(chan os.Signal),
timeout: t,
func (s *Shutdown) Add(signals ...os.Signal)
signal.Notify(s.ch, signals...)
func (s *Shutdown) Start(server *http.Server)
<-s.ch
fmt.Println("start exist......")
ctx, cannel := context.WithTimeout(context.Background(), s.timeout*time.Second)
defer cannel()
if err := server.Shutdown(ctx); err != nil
fmt.Println("Graceful exit failed. err: ", err)
fmt.Println("Graceful exit success.")
package main
import (
"github.com/gin-gonic/gin"
"go-hello-world/pkg/shutdown"
"go-hello-world/router"
"log"
"net/http"
"syscall"
"time"
)
func main()
r := gin.New()
// 注册路由
router.SetupRouter(r)
server := &http.Server
Addr: ":8080",
Handler: r,
// 运行服务
go func()
err := server.ListenAndServe()
if err != nil && err != http.ErrServerClosed
log.Fatalf("server.ListenAndServe err: %v", err)
()
// 优雅退出
quit := shutdown.New(10)
quit.Add(syscall.SIGINT, syscall.SIGTERM)
quit.Start(server)
当接收到SIG信号的时候,就会调用Shutdown
方法做应用退出处理。
除此,还要结合K8S的PreStop Hook
来定义结束前的钩子,如下:
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- sleep 30
如果使用注册中心,比如nacos,我们可以在PreStop Hook
中先告诉nacos要下线,如下:
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- "curl -X DELETE your_nacos_ip:8848/nacos/v1/ns/instance?serviceName=nacos.test.1&ip=$POD_IP&port=8880&clusterName=DEFAULT" && sleep 30
定义Metrics接口
Metrics主要用来暴露应用指标,可以根据实际情况自定义指标,以便于监控工具Prometheus进行数据收集展示。
有些语言有现成的exporter,比如java的jmx_exporter,没有的就需要自己在应用中集成。
比如:
package main
import (
"github.com/SkyAPM/go2sky"
v3 "github.com/SkyAPM/go2sky-plugins/gin/v3"
"github.com/SkyAPM/go2sky/reporter"
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go-hello-world/pkg/shutdown"
"go-hello-world/router"
"log"
"net/http"
"syscall"
"time"
)
var SKYWALKING_ENABLED = false
func main()
r := gin.New()
// 注册路由
router.SetupRouter(r)
server := &http.Server
Addr: ":8080",
Handler: r,
// 启动metrics服务
go func()
http.Handle("/metrics", promhttp.Handler())
if err := http.ListenAndServe(":9527", nil); err != nil
log.Printf("metrics port listen failed. err: %s", err)
()
// 运行服务
go func()
err := server.ListenAndServe()
if err != nil && err != http.ErrServerClosed
log.Fatalf("server.ListenAndServe err: %v", err)
()
// 优雅退出
quit := shutdown.New(10)
quit.Add(syscall.SIGINT, syscall.SIGTERM)
quit.Start(server)
这种会暴露默认的Http指标,可以通过curl 127.0.0.1:9527/metrics
获取指标。
......
# HELP promhttp_metric_handler_requests_total Total number of scrapes by HTTP status code.
# TYPE promhttp_metric_handler_requests_total counter
promhttp_metric_handler_requests_totalcode="200" 0
promhttp_metric_handler_requests_totalcode="500" 0
promhttp_metric_handler_requests_totalcode="503" 0
如果需要自定义指标的话,只需按规则定义即可,如下:
package metrics
import (
"github.com/prometheus/client_golang/prometheus"
"net/http"
"time"
)
var (
// HttpserverRequestTotal 表示接收http请求总数
HttpserverRequestTotal = prometheus.NewCounterVec(prometheus.CounterOpts
Name: "httpserver_request_total",
Help: "The Total number of httpserver requests",
,
// 设置标签:请求方法和路径
[]string"method", "endpoint")
HttpserverRequestDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts
Name: "httpserver_request_duration_seconds",
Help: "httpserver request duration distribution",
Buckets: []float640.1, 0.3, 0.5, 0.7, 0.9, 1,
,
[]string"method", "endpoint")
)
// 注册监控指标
func init()
prometheus.MustRegister(HttpserverRequestTotal)
prometheus.MustRegister(HttpserverRequestDuration)
func NewMetrics(router http.HandlerFunc) http.HandlerFunc
return func(w http.ResponseWriter, r *http.Request)
start := time.Now()
router(w, r)
duration := time.Since(start)
// httpserverRequestTotal 记录
HttpserverRequestTotal.With(prometheus.Labels"method": r.Method, "endpoint": r.URL.Path).Inc()
// httpserverRequestDuration 记录
HttpserverRequestDuration.With(prometheus.Labels"method": r.Method, "endpoint": r.URL.Path).Observe(duration.Seconds())
这样就定义了httpserver_request_total
和httpserver_request_duration_seconds
指标,引用过后就能在/metrics
中看到对应的数据。
定义好了指标,下面就是收集了。既可以通过自定义收集规则收集,也可以通过自动发现的方式收集,为了方便,主要采用自动发现的方式。
我们只需要在deployment的templates中定义好annotation,prometheeus就会自动添加采集目标,如下:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: httpserver
name: httpserver
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: httpserver
template:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "metrics"
labels:
app: httpserver
spec:
containers:
image: baidjay/httpserver:ubuntu-v3-metrics
imagePullPolicy: IfNotPresent
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- sleep 15
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: http
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 3
name: httpserver
ports:
- containerPort: 8080
name: http
protocol: TCP
- name: metrics
protocol: TCP
containerPort: 9527
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: http
scheme: HTTP
initialDelaySeconds: 20
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 3
定义Trace功能
Trace用于跟踪,每个请求都会生成一个TraceID
,这个ID会伴随请求的整个生命周期,我们也可以根据这个ID查询请求的整个链路情况。
链路追踪,目前市面上有很多开源系统,比如Skywalking,Jeager,Zipkin等,它们各有各的特点,如下。
Pinpoint | Zipkin | Jaeger | Skywalking | |
OpenTracing兼容 | 否 | 是 | 是 | 是 |
客户端支持语言 | java\\php | java\\go\\php等 | java\\go\\php等 | java\\nodejs\\php等 |
存储 | hbase | es\\mysql\\内存等 | es\\kafka\\内存等 | es\\mysql\\h2等 |
传输协议支持 | thrift | http\\mq | udp\\http | grpc |
UI丰富程度 | 高 | 低 | 中 | 中 |
实现方式 | 字节码注入 | 拦截请求 | 拦截请求 | 字节码注入 |
扩展性 | 低 | 高 | 高 | 中 |
Trace查询 | 不支持 | 支持 | 支持 | 支持 |
告警支持 | 支持 | 不支持 | 不支持 | 支持 |
JVM监控 | 支持 | 不支持 | 不支持 | 支持 |
性能损失 | 高 | 中 | 中 | 低 |
我比较推荐使用Jaeger,它是CNCF的毕业项目,成长空间和云原生的系统架构兼容性比较好。
不过,我这里采用的Skywalking。
Skywalking有许多现成的客户端,比如Java、Python等,可以直接使用,它们都会自动埋点,但是对于Go来说就只有自己手动埋点了,需要我们自己去写代码。
比如:
package main
import (
"github.com/SkyAPM/go2sky"
v3 "github.com/SkyAPM/go2sky-plugins/gin/v3"
"github.com/SkyAPM/go2sky/reporter"
"github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go-hello-world/pkg/shutdown"
"go-hello-world/router"
"log"
"net/http"
"syscall"
"time"
)
var SKYWALKING_ENABLED = false
func main()
r := gin.New()
// 配置skywalking
if SKYWALKING_ENABLED
rp, err := reporter.NewGRPCReporter("skywalking-oap:11800", reporter.WithCheckInterval(time.Second))
if err != nil
log.Printf("create gosky reporter failed. err: %s", err)
defer rp.Close()
tracer, _ := go2sky.NewTracer("go-hello-world", go2sky.WithReporter(rp))
r.Use(v3.Middleware(r, tracer))
// 注册路由
router.SetupRouter(r)
server := &http.Server
Addr: ":8080",
Handler: r,
// 启动metrics服务
go func()
http.Handle("/metrics", promhttp.Handler())
if err := http.ListenAndServe(":9527", nil); err != nil
log.Printf("metrics port listen failed. err: %s", err)
()
// 运行服务
go func()
err := server.ListenAndServe()
if err != nil && err != http.ErrServerClosed
log.Fatalf("server.ListenAndServe err: %v", err)
()
// 优雅退出
quit := shutdown.New(10)
quit.Add(syscall.SIGINT, syscall.SIGTERM)
quit.Start(server)
定义reporter用于上报数据给Skywalking,这就是一个简单的集成Trace的例子。
定义标准的日志
应用的可观测性主要来源日志、监控、链路追踪,标准的日志有利于日志收集以及排查问题。
原则上,不论是什么类型的日志输出,什么格式的日志内容,都能收集。但是为了方便友好,建议把日志输出到标准输出,这样收集更方便。
我个人理解,在K8s中,完全没必要把日志输出到文件,浪费不说,没多大意义,因为所有的日志我们都会收集到日志系统,而输出到文件的日志也会随着应用发版而丢失,所以输出到文件的意义是什么呢?
运维侧
开发把系统开发完,就会交付给运维部署。为了保障应用的稳定性,运维在部署应用的时候应该考虑以下几点。
- 应用尽可能保持无状态
- 应用尽可能保持高可用
- 应该具备优雅上线能力
- 应该具备异常自愈能力
- 可以使用HTTPS访问
应用尽可能保持无状态
K8S中可以部署有状态应用,也可以部署无状态应用。对于有状态应用,我其实很少部署到K8S中,大部分还是部署的无状态应用,至于为什么,用多了就晓得了。
对于业务应用,强烈建议使其保持无状态,就算有需要持久化的东西,要么保存到数据库,要么保存到对象存储或者其他单独的文件系统中,不要挂载到应用Pod上。
这样的好处是,应用和数据是分开的,应用可以随意启停、扩展、迁移等。
应用尽可能的保持高可用
保持高可用应该是每个运维人员的使命。
在K8S中,我们应该怎么配置呢?(1)应用Pod应该是多副本
(2)应用Pod之间做反亲和性,避免同一应用调度到同一台主机,如下。
......
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values: [ "httpserver" ]
topologyKey: kubernetes.io/hostname
......
(3) 为了避免应用因为节点维护等原因驱逐Pod,导致全部Pod被驱逐,特别配置了PodDisruptionBudget,保障应用至少有一个可用,如下。
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: httpserver
spec:
minAvailable: 1
selector:
matchLables:
app: httpserver
(4)如果某个节点因为一些原因需要驱逐一些Pod,为了避免重要应用被驱逐,应该给应用配置较高的QoS,如下:
resources:
limits:
cpu: "1"
memory: 2Gi
requests:
cpu: "1"
memory: 2Gi
应用具备优雅上线能力
所谓优雅上线能力,就是要确保应用能够提供服务了,再接入外界流量,不能在还没完全启动的情况下就提供服务。
在K8S中,应用在启动后会加入endpoints中,然后通过service接入流量,那在什么情况下才算启动成功呢?主要是通过K8S的ReadinessProbe
来进行检测。这时候开发的健康检测接口就派上用场了,如下:
...
readinessProbe:
failureThreshold: 3
httpGet:
path: /health
port: http
scheme: HTTP
initialDelaySeconds: 20
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 3
...
所以我们K8S的YAML文件应该加上如上的配置。
应该具备异常自愈能力
所谓异常自愈,就是应用本身在出现Crash,或者应用Pod所在节点出现异常的情况,应用能够自动重启或者迁移。这时候就需要通过K8S的LivenessProbe
来进行检测了,如下。
......
livenessProbe:
failureThreshold: 3
httpGet:
path: /health
port: http
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 3
......
当K8S的YAML清单加上如上配置过后,就会定时去探测应用是否正常,如果异常,就会触发重启的动作。如果是节点异常,K8S会对Pod进行重新调度。
可以使用HTTPS进行访问
应用通过HTTPS访问是比较常见的,企业级应用建议自己购买相应的SSL证书,然后进行配置即可。
比如。
# 创建证书secret
kubectl create secret tls httpserver-tls-secret --cert=path/to/tls.cert --key=path/to/tls.key
# 在ingress中引用
......
spec:
tls:
hosts:
- httpserver.coolops.cn
secretName: httpserver-tls-secret
rules:
- host: httpserver.coolops.cn
......
总结
上面介绍了开发和运维对于应用上线应该做的工作,不全但够用
。
在不同的企业都有不同的尿性,但是作为运维,我们都要牢牢记住稳定
永远是第一尿性。通过上面的梳理,我们的应用模板就整理如下:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: httpserver
name: httpserver
namespace: default
spec:
progressDeadlineSeconds: 600
replicas: 2
revisionHistoryLimit: 10
selector:
matchLabels:
app: httpserver
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "metrics"
labels:
app: httpserver
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values: [ "httpserver" ]
topologyKey: kubernetes.io/hostname
containers:
- env:
- name: TZ
value: Asia/Shanghai
- name: POD_NAME
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
image: baidjay/httpserver:ubuntu-v3-metrics
imagePullPolicy: IfNotPresent
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- sleep 15
livenessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: http
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 3
name: httpserver
ports:
- containerPort: 8080
name: http
protocol: TCP
- name: metrics
protocol: TCP
containerPort: 9527
readinessProbe:
failureThreshold: 3
httpGet:
path: /healthz
port: http
scheme: HTTP
initialDelaySeconds: 20
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 3
resources:
limits:
cpu: "1"
memory: 2Gi
requests:
cpu: "1"
memory: 2Gi
securityContext:
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
---
apiVersion: v1
kind: Service
metadata:
name: httpserver
spec:
ports:
- name: http
port: 8080
protocol: TCP
targetPort: http
- name: metrics
port: 9527
protocol: TCP
targetPort: metrics
selector:
app: httpserver
sessionAffinity: None
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: 100m
nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
nginx.ingress.kubernetes.io/service-weight: ""
nginx.org/client-max-body-size: 100m
name: httpserver-tls
spec:
tls:
- hosts:
- httpserver.coolops.cn
secretName: httpserver-tls-secret
rules:
- host: httpserver.coolops.cn
http:
paths:
- pathType: Prefix
path: /
backend:
service:
name: httpserver
port:
number: 8080
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: httpserver
spec:
minAvailable: 1
selector:
matchLabels:
app: httpserver
我是 乔克,《运维开发故事》公众号团队中的一员,一线运维农民工,云原生实践者,这里不仅有硬核的技术干货,还有我们对技术的思考和感悟,欢迎关注我们的公众号,期待和你一起成长!
以上是关于在main之前,IAR都做了什么的主要内容,如果未能解决你的问题,请参考以下文章