DaemonSet

DaemonSet 确保全部(或者某些)节点上运行一个 Pod 的副本。 当有节点加入集群时, 也会为他们新增一个 Pod 。 当有节点从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod。

DaemonSet 的一些典型用法:

  • 在每个节点上运行集群守护进程
  • 在每个节点上运行日志收集守护进程
  • 在每个节点上运行监控守护进程

一种简单的用法是为每种类型的守护进程在所有的节点上都启动一个 DaemonSet。 一个稍微复杂的用法是为同一种守护进程部署多个 DaemonSet;每个具有不同的标志, 并且对不同硬件类型具有不同的内存、CPU 要求。

k8s 去除master节点污点NoSchedule,添加master节点 尽量不调度

k8s 去除master节点(k8s-master02)污点NoSchedule,添加master节点 尽量不调度 PreferNoSchedule标签

#添加 尽量不调度 PreferNoSchedule 
kubectl taint nodes k8s-master02 node-role.kubernetes.io/master:PreferNoSchedule
#去除污点NoSchedule,最后一个"-"代表删除
kubectl taint nodes k8s-master02 node-role.kubernetes.io/master:NoSchedule-

 

ElevatedButton 背景修改

You can style ElevatedButton by using the styleFrom static method or the ButtonStyle class. The first one is more convenience than the second one.

Using styleFrom to style an ElevatedButton:

ElevatedButton(
      child: Text('Button'),
      onPressed: () {},
      style: ElevatedButton.styleFrom({
           Color primary, // set the background color 
           Color onPrimary, 
           Color onSurface, 
           Color shadowColor, 
           double elevation, 
           TextStyle textStyle, 
           EdgeInsetsGeometry padding, 
           Size minimumSize, 
           BorderSide side, 
           OutlinedBorder shape, 
           MouseCursor enabledMouseCursor, 
           MouseCursor disabledMouseCursor, 
           VisualDensity visualDensity, 
           MaterialTapTargetSize tapTargetSize, 
           Duration animationDuration, 
           bool enableFeedback
     }),
),

k8s pod的生命周期

1.pod资源-spec.containers

– name:镜像运行起来之后叫容器,该字段为容器名

image:镜像名字

imagePullPolicy:表示从哪拉取镜像,

Always:不管本地有没有镜像,都要从仓库中下载镜像,也就是说,即使本地有镜像了,也不使用本地镜像,而是从仓库下载;

Never:从来不从仓库下载镜像,也就是说本地有镜像就用,没有就算了;

IfNotPresent:如果本地存在就直接使用,不存在才从仓库下载,默认的策略是:当镜像标签版本是latest,则策略是Always;其余都是IfNotPresent.

指定策略为ifNotPresent,即使image指定的版本是latest,每次启动容器,也不会从仓库重新下载镜像.

ports:指定暴露容器端口号,可以指定多个端口,如下:

1
2
3
4
5
6
7
8
9
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    ports:
    - name: http
      containerPort: 80
    - name: https
      containerPort: 443

在yaml中,docker field name和k8s field name的对应关系:

docker field    k8s field

ENTRYPOINT     command

CMD         args

1
2
3
4
5
6
7
args:相当于dockerfile里面的cmd
command:相当于docker里面的entrypoint
执行命令的优先级:
如果没有提供command和args,则用docker中的默认启动命令;
如果提供了command,则镜像中的CMD和ENTRYPOINT都不生效;
如果没有提供command,提供了args,则CMD没用了,将args当成参数传给ENTRYPOINT;
如果提供了command和args,则镜像中的CMD和ENTRYPOINT都不生效;

2.标签

一个标签可以对应多个资源,一个资源也可以有多个标签,它们是多对多的关系;一个资源拥有多个标签,可以实现不同维度的管理;标签是key=value格式的,key最大63个字符,只能是字母、数字、_、-、.五种类型的组合,只能以字母或数字开头结尾.

1
2
3
4
5
6
7
8
9
kubectl get pods --show-labels
# 用-l过滤标签中有app的pod
kubectl get pods -l app --show-labels
#  用-L显示pod中的标签哪些有app,哪些有run
kubectl get pods -L app,run
# 多加一个标签
kubectl label pods pod-demo release=haha
# 修改标签
kubectl label pods pod-demo release=stable --overwrite

3.标签选择器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
等值关系:=、==、!=
kubectl get pods -l release=stable,app=myapp --show-labels
集合关系:KEY in (VALUE1,VALUE2….)、KEY notin (VALUE1,VALUE2….)、KEY、!KEY
kubectl get pods -l "release notin (stable,haha)"
许多资源支持内嵌字段定义其使用的标签选择器:
matchLabels:直接给定键值;
matchExpressions:基于给定的表达式来定义使用标签选择器,
{key:"KEY",operator:"OPERATOR",values:[VAL1,VAL2,...]}
常见操作符(operator):In、NotIn:values字段的值必须为非空列表;
Exists、NotExists:values字段的值必须为空列表.
# nodes对象也有标签
kubectl get nodes --show-labels
# 给node1节点打个disktype=ssd的标签
kubectl label nodes k8s-node1 disktype=ssd
nodeSelector:节点选择器,可以指定pod运行在哪个节点上
nodeName:可以直接指定运行节点
在maniteste/pod-demo.yaml文件spec字段中添加这两行,即可改变pod的运行节点
spec:
  ...
  nodeSelector:
    disktype: ssd

4.annotations:资源注释

1
2
3
4
5
6
7
8
9
与label不同的地方在于,它不能用于挑选资源对象,仅用于为对象提供"元数据"
metadata:
  name: pod-demo
  namespace: default
  labels:
    app: myapp
    tier: frontend
  annotations:
    mowang.com/create_by"cluster admin"

5.Pod生命周期

在一个pod中,可以运行多个容器,但通常在一个pod里面运行一个容器,容器在创建之前,有多个初始化容器(init container)用来进行初始化环境,init container执行完,它就退出了,接下来是主容器(main container)开始启动,主容器启动时也要初始化主容器里面的环境,在主容器刚启动时,用户可以手动嵌入一个操作叫post start;在主容器结束前,也可以做一个收尾操作pre stop,用来在主容器结束前做一个清理.

Pod生命周期中的重要行为:初始化容器、容器探测

liveness probe–存活性探测:用于判定主容器是否处于存活状态;

readiness probe–就绪性探测:用于判定容器中的主进程是否准备就绪以及能否对外提供服务.

在post start后,先做存活性探测,再做就绪性探测,Pod的状态:Pending(挂起,没有匹配到可运行节点),Running,Failed,Success,Unknown.

创建pod的大致流程:

apiserver会将创建请求的目标状态保存到etcd,接着请求scheduler进行调度,将调度结果保存到etcd中,目标节点上的kubelet通过apiserver拿到用户提交的创建清单,根据清单在当前节点上创建并运行这个Pod,并将结果返回给apiserver,再把结果存到etcd中.

健康检查分三个层次:1.直接执行命令;2.向tcp连接请求;3.向http发get请求.

6.livenessProbe–存活状态探测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# 探针类型
kubectl explain pod.spec.containers.livenessProbe
exec    <Object>
httpGet <Object>
tcpSocket   <Object>
failureThreshold:表示探测失败次数,默认是3,探测3次失败,才认为是真失败了;
periodSeconds:周期间隔时长,默认10s探测一次;
timeoutSeconds:超时时间,表示发出探测,对方始终没有响应,需要等多久,默认等1s;
initialDelaySeconds:默认是容器一启动就开始探测,但是此时容器可能还没启动完,此时探测肯定是失败的,
所以initialDelaySeconds表示容器启动多长时间后才开始探测.
ExecAction举例:
vim liveness-exec.yaml
apiVersion: v1
kind: Pod
metadata:
  name: liveness-exec-pod
  namespace: default
spec:
  containers:
  - name: liveness-exec-container
    image: busybox:latest
    imagePullPolicy: IfNotPresent
    command: ["/bin/sh","-c","touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 3600"]
    livenessProbe:
      exec:
        command: ["test","-e","/tmp/healthy"]
      initialDelaySeconds: 2
      periodSeconds: 3
initialDelaySeconds:延迟几秒探测
periodSeconds:探测周期,多长时间探测一次
kubectl create -f liveness-exec.yaml
可以看到restart次数会随着时间增长
liveness-HTTPGetAction举例
vim liveness-httpGet.yaml
apiVersion: v1
kind: Pod
metadata:
  name: liveness-httpget-pod
  namespace: default
spec:
  containers:
  - name: liveness-httpget-container
    image: ikubernetes/myapp:v1
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
    livenessProbe:
      httpGet:
         port: http
         path: /index.html
      initialDelaySeconds: 1
      periodSeconds: 3
kubectl create -f liveness-httpGet.yaml
kubectl exec -it liveness-httpget-pod  -- /bin/sh
rm -rf  /usr/share/nginx/html/index.html

当删除pod里面的index.html之后,liveness监测到文件被删除了,容器就会重启,容器会重新初始化,里面就会又生成index.html文件,所以只重启一次,restarts次数为1.

7.readlinessProbe–准备就绪型探针

readiness-HTTPGetAction举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
vim readiness-httget.yaml
apiVersion: v1
kind: Pod
metadata:
  name: readiness-httpget-pod
  namespace: default
spec:
  containers:
kind: Pod
metadata:
  name: readiness-httpget-pod
  namespace: default
spec:
  containers:
  - name: readiness-httpget-container
    image: ikubernetes/myapp:v1
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
    readinessProbe:
      httpGet:
         port: http
         path: /index.html
      initialDelaySeconds: 1
      periodSeconds: 3

进入容器删除index.html,ready变成0/1,但是status是runing的,说明nginx进程正常,但index.html丢失,则判定nginx没有就绪.

poststart示例

postStart:如果执行操作失败了,容器将被终止并且重启,而重启与否是由重启策略决定;

preStop:容器在终止前要立即执行的命令,等这些命令执行完了,容器才能终止.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
vim poststart-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: poststart-pod
  namespace: default
spec:
  containers:
  - name: busybox-httpd
    image: busybox:latest
    imagePullPolicy: IfNotPresent
    lifecycle:
       postStart:
         exec:
           command: ["/bin/sh","-c","echo Home_Page >> /tmp/index.html"]
    #command: ['/bin/sh','-c','sleep 3600']
    command: ["/bin/httpd"]
    args: ["-f","-h /tmp"]  # -f是前台,-h是家目录
容器启动后默认执行的命令,但容器启动不能依赖于postStart执行的结果
kubectl create -f  poststart-pod.yaml

restartPolicy–容器的重启策略

一旦pod中的容器挂了,重启容器,有如下策略:

Always:表示容器挂了总是重启,这是默认策略,很耗费资源,所以Always是这么做的:

第一次挂了立即重启,如果再挂了就延时10s重启,第三次挂了就等20s重启…以此类推

OnFailures:状态是Failure时才重启,正常退出则不重启;

Never:表示容器挂了不予重启;

容器的终止策略

1
2
3
4
5
6
7
8
9
10
11
k8s会给容器30s的时间进行终止,如果30s后还没终止,就会强制终止.
kill -l
kill -15 pid  SIGTERM
系统会发送一个SIGTERM的信号给对应的程序.当程序接收到该signal后,将会发生以下的事情:
a.程序立刻停止;
b.当程序释放相应资源后再停止;
c.程序可能仍然继续运行.
大部分程序接收到SIGTERM信号后,会先释放自己的资源,然后在停止.但是也有程序可以在接受到信号量后,
做一些其他的事情,并且这些事情是可以配置的.
如果程序正在等待IO,可能就不会立马做出相应,也就是说:SIGTERM多半是会被阻塞的、忽略的.
kill -9 pid 强行终止 SIGKILL

 

Kubernetes Liveness 和 Readiness Probes的最佳实践

Kubernetes Liveness 和 Readiness探针可用于通过减少运行问题和提高服务质量来使服务更健壮和更具弹性。但是,如果不仔细设置这些探针,则它们可能会严重降低服务的整体运行性能。

在本文中,我将探讨在实现KubernetesLiveness 和 Readiness探针时如何避免使服务可靠性变差。虽然本文的重点是Kubernetes,但我将重点介绍的概念适用于用于推断服务的运行状况并采取自动补救措施的任何应用程序或基础设施机制。

Kubernetes Liveness 和 Readiness Probes

Kubernetes使用Liveness探针来决定何时重新启动容器。如果容器没有响应(可能是由于多线程缺陷而导致应用程序死锁),则尽管程序代码存在缺陷,重新启动容器仍可以使应用程序可用。无疑,它比在半夜安排运维来重新启动容器要好。

Kubernetes使用Readiness探针来确定容器何时可用于接受流量。Readiness探针用于控制将哪些Pod用作服务的后端。当Pod的所有容器都准备就绪时,将其视为就绪。如果未准备就绪,则将其从服务负载平衡器中删除。例如,如果某个容器在启动时加载了较大的缓存,并且花了几分钟启动,那么您不希望在该容器就绪之前将请求发送到该容器,否则请求将失败,您希望将请求路由到其他容器。能够处理请求。

在撰写本文时,Kubernetes支持三种用于实现Liveness 和 Readiness探针的机制:1)在容器内运行命令,2)针对容器发出HTTP请求,或3)针对容器打开TCP套接字。

探针具有许多配置参数来控制其行为,例如执行探针的频率。启动容器后启动探针要等待多长时间;探测失败之前经过的秒数;以及在放弃之前探针可以失败多少次。对于Liveness探针,放弃意味着将重新启动Pod。对于Readiness探针,放弃意味着不将流量路由到Pod,但是Pod不会重新启动。Liveness 和 Readiness探针可以结合使用。

Readiness Probes最佳实践

Kubernetes文档以及许多博客文章和示例在某种程度上误导了人们在启动容器时强调了Readiness探针的使用。通常这是最常见的考虑因素-我们希望避免在将Pod准备好接受流量之前将请求路由到该Pod。但是,在容器的整个生命周期中将周期性(periodSeconds)调用Readiness探针,以便当容器中的某个依赖项不可用时,或者在运行大型批处理作业,执行维护等操作时,容器可以使其自身暂时不可用。

如果您没有意识到启动容器后将继续调用Readiness探针,这些探针可能在运行时导致严重问题。即使您了解这种行为,但如果准备Readiness探针未考虑异常的系统动态,您仍然可能会遇到严重的问题。我将通过一个例子来说明这一点。

下面的应用程序是在Scala中使用Akka HTTP实现的,它会在启动之前将大缓存加载到内存中,然后才能处理请求。加载缓存后,将加载的原子变量设置为true。如果缓存加载失败,该容器将退出并由Kubernetes重新启动,并具有指数退避延迟。

object CacheServer extends App with CacheServerRoutes with CacheServerProbeRoutes {
  implicit val system = ActorSystem()
  implicit val materializer = ActorMaterializer()
  implicit val executionContext = ExecutionContext.Implicits.global

  val routes: Route = cacheRoutes ~ probeRoutes

  Http().bindAndHandle(routes, "0.0.0.0", 8888)

  val loaded = new AtomicBoolean(false)

  val cache = Cache()
  cache.load().onComplete {
    case Success(_) => loaded.set(true)
    case Failure(ex) =>
      system.terminate().onComplete {
        sys.error(s"Failed to load cache : $ex")
      }
  }
}

该应用程序将以下 /readiness HTTP路由用于Kubernetes Readiness探针。如果加载了缓存,则 /readiness 路由将始终成功返回。

trait CacheServerProbeRoutes {
  def loaded: AtomicBoolean

  val probeRoutes: Route = path("readiness") {
    get {
      if (loaded.get) complete(StatusCodes.OK)
      else complete(StatusCodes.ServiceUnavailable)
    }
  }
}

HTTP Readiness 探针的配置如下:

spec:
  containers:
  - name: cache-server
    image: cache-server/latest
    readinessProbe:
      httpGet:
        path: /readiness
        port: 8888
      initialDelaySeconds: 300
      periodSeconds: 30

这种Readiness探针实现极为可靠。在加载缓存之前,请求不会路由到应用程序。加载缓存后,/readiness 路由将永久返回HTTP 200,并且将始终将pod视为就绪。

将此实现与以下应用程序进行对比,该应用程序向其依赖服务发出HTTP请求,这是其准备情况检查的一部分。像这样的Readiness探针对于在部署时捕获配置问题很有用,例如使用错误的证书进行双向TLS或错误的凭据进行数据库身份验证,以确保服务在准备就绪之前可以与其所有依赖项进行通信。

trait ServerWithDependenciesProbeRoutes {
  implicit def ec: ExecutionContext

  def httpClient: HttpRequest => Future[HttpResponse]

  private def httpReadinessRequest(
    uri: Uri,
    f: HttpRequest => Future[HttpResponse] = httpClient): Future[HttpResponse] = {
    f(HttpRequest(method = HttpMethods.HEAD, uri = uri))
  }

  private def checkStatusCode(response: Try[HttpResponse]): Try[Unit] = {
    response match {
      case Success(x) if x.status == StatusCodes.OK => Success(())
      case Success(x) if x.status != StatusCodes.OK => Failure(HttpStatusCodeException(x.status))
      case Failure(ex) => Failure(HttpClientException(ex))
    }
  }

  private def readinessProbe() = {
    val authorizationCheck = httpReadinessRequest("https://authorization.service").transform(checkStatusCode)
    val inventoryCheck = httpReadinessRequest("https://inventory.service").transform(checkStatusCode)
    val telemetryCheck = httpReadinessRequest("https://telemetry.service").transform(checkStatusCode)

    val result = for {
      authorizationResult <- authorizationCheck
      inventoryResult <- inventoryCheck
      telemetryResult <- telemetryCheck
    } yield (authorizationResult, inventoryResult, telemetryResult)

    result
  }

  val probeRoutes: Route = path("readiness") {
    get {
      onComplete(readinessProbe()) {
        case Success(_) => complete(StatusCodes.OK)
        case Failure(_) => complete(StatusCodes.ServiceUnavailable)
      }
    }
  }
}

这些并发的HTTP请求通常以毫秒为单位非常快速地返回。Readiness探针的默认超时为一秒。由于这些请求在绝大多数时间都成功,因此大多时候默认值就可以满足。

但是请考虑一下,如果某个临时服务的延迟稍有暂时的增加,该怎么办?可能是由于网络拥塞,垃圾收集暂停或临时增加了相关服务的负载。如果对依赖项的等待时间增加到甚至稍大于一秒,则准备就绪探测将失败并且Kubernetes将不再将流量路由到Pod。由于所有Pod都共享相同的依赖关系,因此支持该服务的所有Pod很可能会同时使“就绪性”探测失败。这将导致所有Pod从服务路由中删除。没有Pod支持该服务,Kubernetes将针对所有对该服务的请求返回HTTP 404(默认后端)。尽管我们已尽最大努力提高可用性,但我们已经创建了单点故障,使服务完全不可用。在这种情况下,我们将通过使客户端请求成功(尽管延迟稍有增加)来提供更好的最终用户体验,而不是一次或几秒钟地使整个服务不可用。

如果Readiness探针正在验证容器专有的依赖关系(私有缓存或数据库),则可以假设容器依赖项是独立的,那么您可以更加积极地使Readiness探针失败。但是,如果Readiness探针正在验证共享依赖关系(例如用于身份验证,授权,指标,日志记录或元数据的通用服务),则在使Readiness探针失败时应该非常保守。

所以建议如下:

  • 如果容器在Readiness探针中包含了共享的依赖关系,则将就绪探针超时设置为大于该依赖关系的最大响应时间。
  • 默认的failThreshold计数为3,即Readiness探针在不再将Pod视为就绪之前探测失败的次数。Readiness探针的频率(由periodSeconds参数确定),您可能需要增加failureThreshold计数。这样做的目的是避免在临时系统故障已经过去并且响应等待时间恢复正常之前,过早地使Readiness探针失败。

Liveness Probes 最佳实践

回想一下,Liveness探针故障将导致容器重新启动。与Readiness探针不同,Liveness探针检测依赖项是非常危险的。应使用Liveness探针检查容器本身是否没有响应。

Liveness探针的一个问题是,探针可能实际上无法验证服务的响应能力。例如,如果服务托管两台Web服务器-一台用于服务路由,一台用于状态路由(如readiness和liveness或指标收集),则该服务可能会变慢或无响应,而Liveness探针路由会返回正常。为了有效,Liveness探针必须以与依赖服务类似的方式设置。

与Readiness 探针类似,考虑随时间变化的动态变化也很重要。如果Liveness探针超时太短,则响应时间的少量增加(可能是负载的暂时增加所引起的)可能会导致容器重新启动。重新启动可能会为支持该服务的其他Pod带来更多负载,从而导致Liveness探针故障进一步级联,从而使服务的整体可用性变得更糟。按客户端超时的顺序配置Liveness探针超时,并使用failureThreshold计数,可以防止这些级联失败。

Liveness探针的一个细微问题来自容器启动延迟随时间的变化。这可能是由于网络拓扑更改,资源分配更改或随服务扩展而增加负载的结果。如果由于Kubernetes节点故障或Liveness探针故障而重新启动了容器,并且initialDelaySeconds参数的时间不够长,则可能会导致永远无法启动该应用程序,因为该应用程序会在完全启动之前反复被杀死并重新启动。 initialDelaySeconds参数应大于容器的最大初始化时间。为避免这些动态变化随时间变化带来的意外,在一定程度上定期重启Pod很有好处-单个Pod一次支持服务运行数周或数月不一定是目标。重要的是,定期运行和评估部署,重新启动和故障是运行可靠服务的一部分。

所以建议如下:

  • 避免在Liveness探针中检查服务的依赖项。Liveness探针应该简单并且具有最小的响应时间。
  • 保守地设置Liveness探针超时,以便系统临时或永久更改,而不会导致Liveness探针故障过多。考虑将活动探针超时设置为与客户端超时相同的幅度。
  • 保守地设置initialDelaySeconds参数,以便即使启动动态随时间变化,也可以可靠地重新启动容器。

结论

KubernetesLiveness 和 Readiness 探针可以极大地提高服务的健壮性和弹性,并提供出色的最终用户体验。但是,如果您不仔细考虑如何使用这些探针,特别是如果您不考虑异常的系统动态(无论多么罕见),则有可能使服务的可用性变差,而不是变好。

Command CodeSign failed with a nonzero exit code解决方案

Xcode10升级到Xcode11,在真机运行程序出现下面错误

许多博客说清除缓存然后电脑重启就可以解决,这些方案在此处不可行。

 

经过一番折腾,最终解决方案:

关闭Xcode,在钥匙串中找到下面这个证书删除,然后打开Xcode运行,可以成功运行。

使用saltstack的sls功能

sls文件编写

[root@vm01 ~]# vim /etc/salt/master
#在master配置文件中添加以下内容
file_roots:
base:
– /srv/salt

[root@vm01 ~]# mkdir -p /srv/salt
[root@vm01 ~]# cd /srv/salt/
[root@vm01 salt]# pwd
/srv/salt
[root@vm01 salt]# vim top.sls
base:
‘*’:
– httpd

[root@vm01 salt]# vim httpd.sls
httpd:
pkg:
– installed

[root@vm01 salt]#
[root@vm01 salt]# salt ‘*’ service.available httpd
vm02:
False
[root@vm01 salt]#
[root@vm01 salt]# salt ‘*’ state.highstate -v
Executing job with jid 20160412203923856918
——————————————-

vm02:
———-
ID: httpd
Function: pkg.installed
Result: True
Comment: The following packages were installed/updated: httpd
Started: 20:39:25.356652
Duration: 46945.068 ms
Changes:
———-
httpd:
———-
new:
2.2.15-47.el6.centos.4
old:

Summary
————
Succeeded: 1 (changed=1)
Failed: 0
————
Total states run: 1
[root@vm01 salt]#
[root@vm01 salt]# salt ‘*’ service.available httpd
vm02:
True
[root@vm01 salt]#

#minion端查看httpd是否已经安装成功
[root@vm02 ~]# rpm -q httpd
httpd-2.2.15-47.el6.centos.4.x86_64
[root@vm02 ~]#
#ok,已经安装成功了,就这么简单。