六、控制器
本文最后更新于105 天前,其中的信息可能已经过时,如有错误请发送邮件到big_fw@foxmail.com

控制器简介

什么是控制器

Kubernetes 中内建了很多 controller(控制器),这些相当于一个状态机,用来控制 Pod 的具体状态和行为

控制器分类

无状态服务

  • RC:保证副本数量与期望值一致
  • RS:类似于 RC,但是多了标签的运算符匹配
  • Deployment:通过控制创建不同的 RS 实现了滚动更新
  • DaemonSet:保证每个节点有且只有一个 Pod 运行(动态)
  • Job:运行批处理任务,保证一次或多次的任务成功
  • CronJob:周期性的创建 job 实现批处理任务的运行

有状态服务

  • statefulSet:StatefulSet 作为 Controller 为 Pod 提供唯一的标识。它可以保证部署和 scale 的顺序以及满足持久化存储的需求

控制器类型

  • ReplicationController 和 ReplicaSet
  • Deployment
  • DaemonSet
  • StateFulSet
  • Job/CronJob
  • Horizontal Pod Autoscaling

1.RS与RC

标签选择器

selector:
  app: nginx

如果下面的容器没有控制器设置的标签,那么他就不属于这个控制器

template:模板,从metadata开始

在老版本里如果匹配不到标签,那么就删除,在创建再删除,无限循环
在新版本里好多了,他会自动比对标签是否一致。如果不一致不允许运行

在集群中需要对较为复杂的pod去下对应的标签,是为了给这些pod逻辑的分组,这样更方便用户的访问

如果只有一个班,那可以直接说这个班,但是人多了,有十个班,就需要给每个班级一个名字
未来Kubernetes就是运行很多很多的服务,论坛,网站等等。那就必须逻辑分组

标签在谁那归谁管理,但是如果把他再改回来,他会很残忍的把最后新创建的删除了,生产环境中不要这样做

RS 与 RC 与 Deployment 关联

RC (ReplicationController )主要的作用就是用来确保容器应用的副本数始终保持在用户定义的副本数 。即如果有容器异常退出,会自动创建新的Pod来替代;而如果异常多出来的容器也会自动回收(保证副本数量与期望值一致)

Kubernetes 官方建议使用 RS(ReplicaSet ) 替代 RC (ReplicationController ) 进行部署,RS 跟 RC 没有本质的不同,只是名字不一样,并且 RS 支持集合式的 selector

RC 控制器

apiVersion: v1
kind: ReplicationController
metadata:
  name: frontend
spec:
  replicas: 3
  selector:
    app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: php-redis
        image: wangyanglinux/myapp:v1
        lifecycle:
          postStart:
            exec:
              command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /message"]
          preStop:
            exec:
              command: ["/bin/sh", "-c", "echo Hello from the preStop handler > /message"]
        env:
        - name: GET_HOSTS_FROM
          value: dns
          name: zhangsan
          value: "123"
        ports:
        - containerPort: 80

RS 控制器

RS和RC没有太大的区别,除了标签运算符,集合式的,模板中的标签只需要包含RS中的标签即可
RS是集合式的标签匹配,而且还有逻辑运算符

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
  template:
    metadata:
      labels:
        tier: frontend
        app: nginx
    spec:
      containers:
      - name: myapp
        image: wangyanglinux/myapp:v1
        env:
        - name: GET_HOSTS_FROM
          value: dns
        ports:
        - containerPort: 80

selector.matchExpressions

rs 在标签选择器上,除了可以定义键值对的选择形式,还支持 matchExpressions 字段,可以提供多种选择。
目前支持的操作包括:

  • In:label 的值在某个列表中(- In是只要模板中含有一个RS标签就可以,不需要是子集)
  • NotIn:label 的值不在某个列表中
  • Exists:某个 label 存在
  • DoesNotExist:某个 label 不存在
    • RS有两个选项,一个是匹配标签,一个是匹配运算符,RC有的他全都有,RC在新版中即将被废弃
    • 加上这些选项会让标签玩的很花,那就意味着更加灵活,可以有更多的分类,当以后集群里的pod多了,可以很好的避免pod之间互相冲突

Kubernetes的PodSelector在选择Pod时确实支持部分标签匹配。在Kubernetes中,PodSelector可以使用matchLabels和matchExpressions两种方式来定义选择器。matchLabels允许通过精确匹配标签键值对来选择Pod,而matchExpressions则提供了更灵活的匹配选项,支持基于标签的操作符,如In, NotIn, Exists, 和 DoesNotExist。

这意味着PodSelector不仅可以选择具有完全匹配标签集的Pod,还可以选择满足特定标签条件的Pod集合。例如,matchExpressions可以用来选择所有环境标签为production或staging的Pod,并且这些Pod不应该有tier标签

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: rs-demo
spec:
  selector:
    matchExpressions:
      - key: app
        operator: Exists
  template:
    metadata:
      labels:
        app: tomcatvdfsrfdsfs
    spec:
      containers:
        - name: rs-c1
          image: wangyanglinux/myapp:v1
          ports:
            - containerPort: 80
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: rs-demo
spec:
  selector:
    matchExpressions:
      - key: app
        operator: In
        values:
        - spring-k8s
        - hahahah
  template:
    metadata:
      labels:
        app: sg-k8s
    spec:
      containers:
        - name: rs-c1
          image: wangyanglinux/myapp:v1
          ports:
            - containerPort: 80

RS 与 Deployment 的关联

file

2.Deployment

Deployment 为 Pod 和 ReplicaSet 提供了一个声明式定义(declarative)方法,用来替代以前的ReplicationController 来方便的管理应用。典型的应用场景包括:

  • 定义Deployment来创建Pod和ReplicaSet
  • 滚动升级和回滚应用
  • 扩容和缩容
  • 暂停和继续Deployment

deployment实际上通过调用RS对象来创建Pod,而且你可能看到很多的RS期望为零,因为他是在等待回滚

Ⅰ、部署一个简单的 Nginx 应用

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 3
  selector:
    matchExpressions:
      - key: app
        operator: In
        values:
        - nginx
        - hahahah
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: wangyanglinux/myapp:v1
        ports:
        - containerPort: 80
kubectl create -f https://kubernetes.io/docs/user-guide/nginx-deployment.yaml --record
## --record参数可以记录命令,我们可以很方便的查看每次 revision 的变化

Ⅱ、扩容

kubectl scale deployment nginx-deployment --replicas 10

Ⅲ、如果集群支持 horizontal pod autoscaling 的话,还可以为Deployment设置自动扩展

kubectl autoscale deployment nginx-deployment --min=10 --max=15 --cpu-percent=80

# --cpu-percent=80 指定了自动缩放器触发扩展或收缩的目标CPU使用率阈值
# 即当Pod的平均CPU利用率达到或超过80%时,自动缩放器会尝试增加Pod的数量
# 相反,如果CPU利用率低于80%,自动缩放器可能会减少Pod的数量。

Ⅳ、更新镜像也比较简单

kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1

Ⅴ、回滚

kubectl rollout undo deployment/nginx-deployment

kubectl rollout 所有参数

kubectl rollout 是 Kubernetes 命令行工具中用于管理资源滚动更新的一系列命令

kubectl rollout 命令的主要功能:

kubectl rollout 命令是 Kubernetes 命令行工具 kubectl 中用于管理部署(Deployment)、副本集(ReplicaSet)和状态集(StatefulSet)滚动更新的子集。该命令的主要功能包括:

  • 滚动更新(Rollout): 允许用户逐步更新部署中的 Pods,以确保服务的高可用性和最小化停机时间。
  • 回滚(Undo): 如果新的部署版本出现问题,可以使用 kubectl rollout undo 命令回滚到之前的稳定版本。
  • 暂停和恢复(Pause and Resume): 用户可以暂时暂停滚动更新过程,以便进行调试或等待修复,之后可以恢复更新。
  • 状态检查(Status): 显示当前滚动更新的状态,包括已完成的更新、正在进行的更新和失败的更新。
  • 重启(Restart): 强制重启指定的资源中的所有 Pods。
  • 历史查看(History): 查看部署的更新历史,包括每个版本的更改细节。

子命令特有参数:

  • status: 列出升级过程/状态。
  • undo: 回滚升级(取消最近一次的滚动更新.也可以指定版本号)。
  • history: 显示升级的历史版本。
  • pause: 暂停滚动升级(金丝雀发布)。
  • resume: 恢复滚动升级。

更新 Deployment

注意: Deployment 的 rollout 当且仅当 Deployment 的 pod template(例如.spec.template)中的label更新或者镜像更改时被触发。其他更新,例如扩容Deployment不会触发 rollout

假如我们现在想要让 nginx pod 使用nginx:1.9.1的镜像来代替原来的nginx:1.7.9的镜像

$ kubectl set image deployment/nginx-deployment nginx=nginx:1.9.1
deployment "nginx-deployment" image updated

可以使用edit命令来编辑 Deployment

$ kubectl edit deployment/nginx-deployment
deployment "nginx-deployment" edited

查看 rollout 的状态

$ kubectl rollout status deployment/nginx-deployment
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
deployment "nginx-deployment" successfully rolled out

查看历史 RS

$ kubectl get rs
NAME                          DESIRED   CURRENT   READY   AGE
nginx-deployment-1564180365   3         3         0       6s
nginx-deployment-2035384211   0         0         0       36s

Deployment 更新策略

Deployment 可以保证在升级时只有一定数量的 Pod 是 down 的。默认的,它会确保至少有比期望的Pod数量少一个是up状态(最多一个不可用)

Deployment 同时也可以确保只创建出超过期望数量的一定数量的 Pod。默认的,它会确保最多比期望的Pod数量多一个的 Pod 是 up 的(最多1个 surge )

未来的 Kuberentes 版本中,将从1-1变成25%-25%

$ kubectl describe deployments

更新策略声明

kubectl explain deploy.spec.strategy.type

  • Recreate
  • rollingUpdate
    • maxSurge:指定超出副本数有几个,两种方式:1、指定数量 2、百分比
    • maxUnavailable : 最多有几个不可用

示例

strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate
kubectl patch deployment  nginx-deployment -p '{"spec":{"strategy":{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0}}}}'

金丝雀部署

刚开始更新就把其余的更新都停了,一般就只有一个能成功更新,如果等三四天他还没啥事,没啥bug,那就可以恢复更新了

$ kubectl set image deploy  nginx-deployment nginx=wangyanglinux/myapp:v2 && kubectl rollout pause deploy  nginx-deployment

$ kubectl rollout resume deploy  nginx-deployment

Rollover(多个rollout并行)

假如您创建了一个有5个niginx:1.7.9 replica的 Deployment,但是当还只有3个nginx:1.7.9的 replica 创建出来的时候您就开始更新含有5个nginx:1.9.1 replica 的 Deployment。在这种情况下,Deployment 会立即杀掉已创建的3个nginx:1.7.9的 Pod,并开始创建nginx:1.9.1的 Pod。它不会等到所有的5个nginx:1.7.9的 Pod 都创建完成后才开始改变航道

回退 Deployment

只要 Deployment 的 rollout 被触发就会创建一个 revision。也就是说当且仅当 Deployment 的 Pod template(如.spec.template)被更改,例如更新template 中的 label 和容器镜像时,就会创建出一个新的 revision。其他的更新,比如扩容 Deployment 不会创建 revision——因此我们可以很方便的手动或者自动扩容。这意味着当您回退到历史 revision 时,只有 Deployment 中的 Pod template 部分才会回退

kubectl set image deployment/nginx-deployment nginx=nginx:1.91
kubectl rollout status deployments nginx-deployment
kubectl get pods
kubectl rollout history deployment/nginx-deployment
kubectl rollout undo deployment/nginx-deployment
kubectl rollout undo deployment/nginx-deployment --to-revision=2   ## 可以使用 --revision参数指定某个历史版本
kubectl rollout pause deployment/nginx-deployment    ## 暂停 deployment 的更新

您可以用kubectl rollout status命令查看 Deployment 是否完成。如果 rollout 成功完成,kubectl rollout status将返回一个0值的 Exit Code

$ kubectl rollout status deploy/nginx
Waiting for rollout to finish: 2 of 3 updated replicas are available...
deployment "nginx" successfully rolled out
$ echo $?
0

也可以把命令放一起实时监测

kubectl set image deploy  nginx-deployment nginx=wangyanglinux/myapp:v2 && kubectl rollout pause deploy  nginx-deployment && sleep 2s && kubectl rollout status deployments nginx-deployment

清理 Policy

您可以通过设置.spec.revisonHistoryLimit项来指定 deployment 最多保留多少 revision 历史记录。默认的会保留所有的 revision;如果将该项设置为0,Deployment 就不允许回退了

其实哪怕采用声明式的执行,滚动更新,etcd还是会保存RS等信息

更改deployment启动方式的四种方法

  • patch(可以放脚本)
  • set image(可以放脚本)
  • edit(简单)
  • apply -f (用的更多,更严谨)

3.DaemonSet

什么是 DaemonSet

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

比如每个机器都需要部署的zabbix-agent,mfs-chunkserver等等,而且有新的节点加入,他也会自动加入,保证一个节点一个
用rpm安装的k8s会自动帮你安装Kubernetes-proxy和coredns

使用 DaemonSet 的一些典型用法:

  • 运行集群存储 daemon,例如在每个 Node 上运行 glusterdceph
  • 在每个 Node 上运行日志收集 daemon,例如fluentdlogstash
  • 在每个 Node 上运行监控 daemon,例如 Prometheus Node Exporter、collectd、Datadog 代理、New Relic 代理,或 Ganglia gmond
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: deamonset-example
  labels:
    app: daemonset
spec:
  selector:
    matchLabels:
      name: deamonset-example
  template:
    metadata:
      labels:
        name: deamonset-example
    spec:
      containers:
      - name: daemonset-example
        image: wangyanglinux/myapp:v1

4.Job

Job 负责批处理任务,即仅执行一次的任务,它保证批处理任务的一个或多个 Pod 成功结束

[root@kube-master01 ~]# kubectl get jobs.batch -A
NAMESPACE       NAME                               COMPLETIONS   DURATION   AGE
ctt-helm        rabbitmq-ha                        1/1           2s         27h
ctt-helm        redis                              1/1           6m58s      26h
default         restart-elastic-1719783180         0/1           2d12h      2d12h
devops-dev      kong-migrations                    1/1           34m        499d
devops-dev      konga-migrations                   1/1           34m        499d
devops-test     kong-migrations                    1/1           59s        499d
devops-test     konga-migrations                   1/1           64s        499d

# duration:持续时间
特殊说明
  • spec.template格式同Pod
  • RestartPolicy仅支持Never或OnFailure; never永不重启,OnFailue失败重启
  • 单个Pod时,默认Pod成功运行后Job即结束
  • .spec.completions标志Job结束需要成功运行的Pod个数,默认为1,可以多写几个
  • .spec.parallelism标志并行运行的Pod的个数,默认为1,同时运行几个,备份数据库,改文件啥的一定是1,发邮件可以多个
  • spec.activeDeadlineSeconds标志失败Pod的重试最大时间,超过这个时间不会继续重试,比如编译一般最多半小时,但是2小时就干死他,单位秒

示例

求 π 值

算法:马青公式

π/4=4arctan1/5-arctan1/239

这个公式由英国天文学教授 约翰·马青 于 1706 年发现。他利用这个公式计算到了 100 位的圆周率。马青公式每计算一项可以得到 1.4 位的 十进制精度。因为它的计算过程中被乘数和被除数都不大于长整数,所以可以很容易地在计算机上编程实现

Python脚本

# -*- coding: utf-8 -*-
from __future__ import division
# 导入时间模块
import time
# 计算当前时间
time1=time.time()
# 算法根据马青公式计算圆周率 #
number = 1000
# 多计算10位,防止尾数取舍的影响
number1 = number+10
# 算到小数点后number1位
b = 10**number1
# 求含4/5的首项
x1 = b*4//5
# 求含1/239的首项
x2 = b // -239
# 求第一大项
he = x1+x2
#设置下面循环的终点,即共计算n项
number *= 2
#循环初值=3,末值2n,步长=2
for i in xrange(3,number,2):
  # 求每个含1/5的项及符号
  x1 //= -25
  # 求每个含1/239的项及符号
  x2 //= -57121
  # 求两项之和
  x = (x1+x2) // i
  # 求总和
  he += x
# 求出π
pai = he*4
#舍掉后十位
pai //= 10**10
# 输出圆周率π的值
paistring=str(pai)
result=paistring[0]+str('.')+paistring[1:len(paistring)]
print result
time2=time.time()
print u'Total time:' + str(time2 - time1) + 's'

dockerfile

FROM hub.c.163.com/public/python:2.7
ADD ./main.py /root
CMD /usr/bin/python /root/main.py

job.yaml

apiVersion: batch/v1
kind: Job
metadata:
  name: pi
spec:
  template:
    metadata:
      name: pi
    spec:
      containers:
      - name: pi
        image: maqing:v1
      restartPolicy: Never

5.CronJob

Cron Job 管理基于时间的 Job,即:

  • 在给定时间点只运行一次
  • 周期性地在给定时间点运行
  • job是创建Pod,cronjob是创建job

使用条件:当前使用的 Kubernetes 集群,版本 >= 1.8(对 CronJob)

典型的用法如下所示:

  • 在给定的时间点调度 Job 运行
  • 创建周期性运行的 Job,例如:数据库备份、发送邮件

CronJob Spec(具备job的spec)

  • spec.template 格式同 Pod
  • RestartPolicy仅支持Never或OnFailure
  • 单个Pod时,默认Pod成功运行后Job即结束
  • .spec.completions标志Job结束需要成功运行的Pod个数,默认为1
  • .spec.parallelism标志并行运行的Pod的个数,默认为1
  • spec.activeDeadlineSeconds标志失败Pod的重试最大时间,超过这个时间不会继续重试

CronJob Spec(多的子选项)

  • .spec.schedule:调度,必需字段,指定任务运行周期,格式同 Cron,分时日月周

  • .spec.jobTemplate:Job 模板,必需字段,指定需要运行的任务,格式同 Job

  • .spec.startingDeadlineSeconds :启动 Job 的期限(秒级别),该字段是可选的。如果因为任何原因而错过了被调度的时间,那么错过执行时间的 Job 将被认为是失败的。如果没有指定,则没有期限

    比如说八点需要备份,但是集群七点关机了,下午六点才修好,这时候用户访问高峰,去备份浪费较大资源是十分不明智的,所以可以设置超过时间

  • .spec.concurrencyPolicy:并发策略,该字段也是可选的。它指定了如何处理被 Cron Job 创建的 Job 的并发执行。只允许指定下面策略中的一种:

    • Allow(默认):允许并发运行 Job; 所以,备份数据库时这是绝对不允许的
    • Forbid:禁止并发运行,如果前一个还没有完成,则直接跳过下一个
    • Replace:取消当前正在运行的 Job,用一个新的来替换

    注意,当前策略只能应用于同一个 Cron Job 创建的 Job。如果存在多个 Cron Job,它们创建的 Job 之间总是允许并发运行。

  • .spec.suspend :挂起,该字段也是可选的。如果设置为 true,后续所有执行都会被挂起。它对已经开始执行的 Job 不起作用。默认值为 false

  • .spec.successfulJobsHistoryLimit.spec.failedJobsHistoryLimit :历史限制,是可选的字段。它们指定了可以保留多少完成和失败的 Job。默认情况下,它们分别设置为 31。设置限制的值为 0,相关类型的 Job 完成后将不会被保留。
    比如数据库的备份,实际上是保存在job创建的pod里面,所以必须要保留,不保留数据就直接没了

示例

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: hello
spec:
  schedule: "*/1 * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: hello
            image: busybox
            args:
            - /bin/sh
            - -c
            - date; echo Hello from the Kubernetes cluster
          restartPolicy: OnFailure
$ kubectl get cronjob
NAME      SCHEDULE      SUSPEND   ACTIVE    LAST-SCHEDULE
hello     */1 * * * *   False     0         <none>
$ kubectl get jobs
NAME               DESIRED   SUCCESSFUL   AGE
hello-1202039034   1         1            49s

$ pods=$(kubectl get pods --selector=job-name=hello-1202039034 --output=jsonpath={.items..metadata.name})

$ kubectl logs $pods
Mon Aug 29 21:34:09 UTC 2016
Hello from the Kubernetes cluster

# 注意,删除 cronjob 的时候不会自动删除 job,这些 job 可以用 kubectl delete job 来删除
$ kubectl delete cronjob hello
cronjob "hello" deleted

扩展

距离上次调度的时间点

  • 在第一次什么时候调度是随机的,不一定是60s调度的,可能是10.20.30...但是第一次和第二次一定是间隔60s的
  • 它也是异步过程,是从etcd里面抓取的,etcd里面是从别的组件调度的,这样是为了防止同步行为造成阻塞
  • 同步的话加入同时做一件事,我没做完你就得等着,那就很浪费资源,异步各做各的,就不用浪费了,有对应的数据就写在etcd里了

CrondJob 本身的一些限制
创建 Job 操作应该是 幂等的
不同的操作应该放在不同的cronjob里面,就像一个docker里不建议封装多个进程一样,允许,但不建议

6.StatefulSet

StatefulSet 作为 Controller 为 Pod 提供唯一的标识。它可以保证部署和 scale 的顺序

StatefulSet是为了解决有状态服务的问题(对应Deployments和ReplicaSets是为无状态服务而设计),其应用场景包括:

  • 稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现
  • 稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有Cluster IP的Service)来实现
  • 有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现
  • 有序收缩,有序删除(即从N-1到0)

7.HPA

应用的资源使用率通常都有高峰和低谷的时候,如何削峰填谷,提高集群的整体资源利用率,让 service 中的Pod个数自动调整呢?这就有赖于 HPA (Horizontal Pod Autoscaling)了,顾名思义,使 Pod 水平自动缩放

扩展

当一个POd死了以后Kubernetes会给他修复起来
但是修复启动起来又死了,再拉起,又死了
那就会慢慢延长拉起它的时间,而且是越来越长

声明式与命令式

命令式 kubectl create -f

  • 指定每一步操作方案,需要指定的十分清晰,没有趋向概念,比如,你先拿钱,左转,下楼......买煎饼果子
  • 如果想买肉夹馍,就需要直接回到身边,从头开始指定买肉夹馍的一步一步命令
  • 开发更喜欢命令式

声明式 kubectl apply -f

  • 直接告诉一个结果,他自己去办,直接给钱去给我买个煎饼果子,而且可以该目标,打电话去买肉夹馍
  • 简单,但是开发难度大,sql语句就是典型的声明式
  • 用户更喜欢声明式
男孩子都是香香软软的小猪
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇