Jenkins 备份与恢复(K8s 部署 + NFS 存储)

Jenkins 备份与恢复(K8s 部署 + NFS 存储)

记录时间:2026-09-14
环境:K8s devops 命名空间 / helm chart jenkinsci/jenkins 5.9.39 / Jenkins 2.568.1-lts-jdk21 / storageClass nfs-data-57(host57 虚拟机自建 NFS)

一、背景

Jenkins 用 helm 部署在 K8s(helm install jenkins ./jenkins-5.9.39.tgz -n devops -f values.yaml),JENKINS_HOME 挂在 jenkins-data-pvc(100Gi,RWX),agent 的 workspace 也复用这个 PVC。这套环境吃过两次亏:一次是插件升级冲突起不来;一次是核心跨大版本升级(2.516.2 → 2.568.1)后 Kubernetes 等插件大面积加载失败,Kubernetes cloud 配置一片空白,agent 拉不起来,所有构建瘫瘓。

我参考了一篇插件备份的文章(打包 plugins 目录、每天留 7 份、故障时回滚),按它的思路起步,推演后发现只备 plugins 不够:PVC 整个损坏时,光有插件重建不出这套环境,凭据、密钥、作业定义都在 JENKINS_HOME 里。所以最终方案是两层:插件层管插件冲突时分钟级回滚,配置层管整个环境重建。

内容 频率 / 保留 解决的问题 恢复耗时
插件层 $JENKINS_HOME/plugins 整个目录 每天 1 次 / 保留 7 份 插件升级冲突起不来(最高频故障) 分钟级
配置层 JENKINS_HOME 全量(排除可重建大块) 每天 1 次 / 保留 30 天 PVC 损坏、误删、迁移重建 十几分钟

配套产出三个文件(都在本目录):jenkins-backup.sh(备份)、jenkins-restore-plugins.sh(插件回滚)、本 README(方案与恢复手册)。

二、方案设计

2.1 只备份 jenkins-data-pvc 一个 PVC

部署一共三个 PVC,我逐个判断过:

PVC 容量 结论 理由
jenkins-data-pvc 100Gi 备份 JENKINS_HOME 全部状态
jenkins-maven-pvc 100Gi 不备 Maven 本地仓库,可从远端仓库重新拉取,绝大多数是公共依赖
jenkins-node-pvc 1Gi 不备 node 缓存,同上

2.2 workspace 不备份

workspace/ 是每次构建时从 Git 检出的临时工作区,重新构建即重建,纳入备份只会让体积和恢复时间失控(当前有 142 个 workspace 目录)。构建产物该由 Harbor/制品库存档,不靠 workspace 留存。

注意:如果某个 Job 在 workspace 里放了不可重建的内容(手工拷的文件、外部下载的依赖),那是用法问题,应该把内容移到 userContent/ 或上传制品库,而不是靠备份 workspace 兜底。

2.3 备什么、不备什么

我用排除法打包整个 JENKINS_HOME,而不是逐个列文件。插件会不断新增自己的全局配置 xml,白名单必然漏。

纳入备份(保留全量)

内容 说明
credentials.xml + secrets/ + identity.key.enc + secret.key 凭据与加密密钥,恢复后凭据可解密的完整前提
config.xml 主配置(/manage/configure 页面的内容就落在这里)
jobs/ 作业定义(各 job 的 config.xml)
users/nodes/ 用户与节点定义
scriptApproval.xml 脚本审批白名单,漏备恢复后要逐条重新审批
casc_configs/init.groovy.d/userContent/ 配置即代码挂载、初始化脚本、静态内容
各插件全局配置 *.xml 其中 GlobalConfigFiles(全局配置文件)、GlobalLibraries(共享库)、GitLab 连接配置尤其重要
plugins/ *.jpi、解压目录、*.pinned*.disabled

排除(可重建,体积大头)

workspace/caches/logs/updates/fingerprints/remoting/*.logsimpleDiskUsageCheckqueue.xml.bak

注意secrets/ + secret.key + identity.key.enc + credentials.xml 是一套,缺任何一个,恢复后所有凭据都无法解密。这是这类备份最常见的失败点。

注意*.pinned 必须一起备份。Jenkins 靠它锁定插件版本以对抗自动升级,只备 *.jpi 的话,恢复后插件又被自动升级回冲突版本。本环境首包实测(2026-09-14):plugins 目录内 .pinned 为 0,插件均经 Update Center 在线安装、本就不生成 pinned;脚本打包整个 plugins 目录,有无 pinned 天然覆盖,无需特殊处理。

2.4 在 NFS 服务端本地执行,备份目标必须异机

备份脚本在 NFS 服务端 host57 上本地跑,不从 K8s Pod 里通过网络读写 NFS。理由:JENKINS_HOME 就在本机 /nfs_data 下,本地 tar 绕开 NFS 协议与海量小文件的元数据开销,这台 NFS 的 fsync 抖动之前诊断过(2026-09-04),走网络只会更慢。

备份目标不能写回 /nfs_data:host57 是一台虚拟机自建 NFS,同盘存储防不住磁盘或主机故障。脚本里做了硬性拦截,BACKUP_ROOT 指向 /nfs_data 下会直接报错退出。

三、部署备份脚本

3.1 安装(在 host57 执行)

install -m 0755 jenkins-backup.sh jenkins-restore-plugins.sh /usr/local/bin/

# 备份目录为 /k8s_nas/jenkins_backup(NAS 挂载,异机存储),确认 NAS 已挂载即可
mkdir -p /k8s_nas/jenkins_backup

# 试跑一次
/usr/local/bin/jenkins-backup.sh

3.2 定时任务

crontab -e
# 凌晨低峰执行
30 2 * * * /usr/local/bin/jenkins-backup.sh >> /var/log/jenkins-backup.log 2>&1

脚本在 BACKUP_ROOT 下生成 plugins/full/ 两个目录,每个压缩包配一个同名 .meta,记录打包时的核心版本与插件 jpi 数量。恢复前先看这个文件,插件与核心版本强绑定。

3.3 备份脚本全文(jenkins-backup.sh)

首包实测(2026-09-14):14:45 启动、14:48 完成,全程 2.5 分钟;plugins 层 362MB、配置层 1.3GB(含构建历史)。以下内容与 /usr/local/bin/jenkins-backup.sh 当前线上版本一致。

#!/usr/bin/env bash
# ============================================================================
# Jenkins 分层备份脚本 —— 在 NFS 服务端(host57)本地执行
#
# 为什么在 NFS 服务端跑:JENKINS_HOME 就在本机 /nfs_data 下,本地 tar 打包绕开
# NFS 协议与海量小文件的元数据开销(该 NFS 已知有 fsync 抖动);从 K8s Pod 里
# 通过 NFS 读写来备份会慢一个数量级。
#
# 分两层备份:
#   ① plugins 层:每天打包、保留 7 份 —— 插件冲突时分钟级回滚(最高频事故)
#   ② 全量配置层:每天打包、保留 30 天 —— 灾难恢复(凭据/密钥/jobs/全局配置)
# 两层互补:插件层管"回得去",配置层管"重建得起来"。
#
# 安装(host57):
#   install -m 0755 jenkins-backup.sh /usr/local/bin/
#   crontab -e  →  30 2 * * * /usr/local/bin/jenkins-backup.sh >> /var/log/jenkins-backup.log 2>&1
# ============================================================================
set -euo pipefail

# ---------------- 配置区(按实际环境调整)----------------
# PVC 目录名带 PVC uid(/nfs_data/devops-jenkins-data-pvc-pvc-<uid>),
# PVC 删除重建后 uid 会变,故用 glob 自动定位;必要时 export JH_OVERRIDE=... 覆盖。
JENKINS_HOME="${JH_OVERRIDE:-$(ls -d /nfs_data/devops-jenkins-data-pvc-pvc-* 2>/dev/null | head -1)}"
# 备份根目录:必须是异机挂载点(远端 NFS/CIFS/备份服务器)或另一块物理盘。
# 绝不可写回 /nfs_data —— 与源数据同一块盘/同一台虚机,磁盘或主机故障时一起丢。
# 实际使用 /k8s_nas(NAS 挂载,异机存储),满足要求;脚本仍保留 /nfs_data 硬性拦截兜底。
BACKUP_ROOT="${BACKUP_ROOT:-/k8s_nas/jenkins_backup}"
KEEP_PLUGINS=7        # 插件备份保留份数
KEEP_FULL_DAYS=30     # 全量配置备份保留天数
# --------------------------------------------------------

if [ -z "${JENKINS_HOME:-}" ] || [ ! -d "${JENKINS_HOME:-}" ]; then
  echo "ERROR: 未找到 JENKINS_HOME(已尝试 glob: /nfs_data/devops-jenkins-data-pvc-pvc-*)"
  exit 1
fi
case "$BACKUP_ROOT" in
  /nfs_data/*) echo "ERROR: BACKUP_ROOT 不能位于 /nfs_data(与源数据同盘,不防磁盘/主机故障)"; exit 1 ;;
esac
# 防挂载点失效(读 /proc/mounts 判断,刻意不 stat 挂载点:NFS 假死时 stat 会无限阻塞卡死脚本)。
# 挂载记录不存在时必须拒绝执行:mkdir -p 会在本机根盘静默建目录,备份悄悄落到本机。
MNT_DIR=$(dirname "$BACKUP_ROOT")
if ! grep -qs " ${MNT_DIR} nfs" /proc/mounts && ! grep -qs " ${BACKUP_ROOT} nfs" /proc/mounts; then
  echo "ERROR: $MNT_DIR 没有 NFS 挂载记录——NAS 未挂载时备份会写到本机盘,请先挂载" >&2
  exit 1
fi
# 运行锁(锁文件放本机 /var/lock,不能放 NAS:NAS 假死时连拿锁都会卡)。
# 上一次备份未结束(典型如 NAS 假死把 tar 挂起)时本次直接跳过,防 cron 堆积挂起进程。
exec 9>/var/lock/jenkins-backup.lock
flock -n 9 || { echo "上一次备份仍在运行(可能 NAS 异常挂起),本次跳过。检查:ps -ef | grep jenkins-backup"; exit 1; }

STAMP=$(date +%Y%m%d_%H%M%S)
DAY=$(date +%F_%H%M)
# 记录核心版本:插件与 Jenkins 核心版本强绑定,恢复前必须核对
CORE=$(cat "$JENKINS_HOME/jenkins.install.InstallUtil.lastExecVersion" 2>/dev/null | tr -d '\n' || echo unknown)

mkdir -p "$BACKUP_ROOT/plugins" "$BACKUP_ROOT/full"

# ① 插件层:打包整个 plugins 目录
# 含 *.jpi(插件包)、同名解压目录、*.pinned(版本固定标记——关闭自动升级后
# 靠它锁定版本,漏备会导致恢复后又被自动升级)、*.disabled。
tar --numeric-owner -czf "$BACKUP_ROOT/plugins/plugins_${STAMP}.tar.gz" \
  -C "$JENKINS_HOME" plugins
printf 'core=%s\nstamp=%s\njpi_count=%s\n' \
  "$CORE" "$STAMP" "$(ls -1 "$JENKINS_HOME/plugins" 2>/dev/null | grep -c '\.jpi$' || true)" \
  > "$BACKUP_ROOT/plugins/plugins_${STAMP}.meta"

# ② 全量配置层:打包 JENKINS_HOME,排除可重建的大块
# workspace/ 由构建从 Git 重建;caches/logs/updates/fingerprints/remoting 同类,
# 均无恢复价值(workspace 是体积大头,纳入会使备份窗口与恢复时间失控)。
tar --numeric-owner -czf "$BACKUP_ROOT/full/jenkins-home_${DAY}.tar.gz" \
  -C "$JENKINS_HOME" \
  --exclude='./workspace' \
  --exclude='./caches' \
  --exclude='./logs' \
  --exclude='./updates' \
  --exclude='./fingerprints' \
  --exclude='./remoting' \
  --exclude='./*.log' \
  --exclude='./simpleDiskUsageCheck' \
  --exclude='./queue.xml.bak' \
  .
printf 'core=%s\nstamp=%s\n' "$CORE" "$DAY" > "$BACKUP_ROOT/full/jenkins-home_${DAY}.meta"

# ③ 保留策略:插件按份数、全量按天数
ls -1t "$BACKUP_ROOT/plugins"/plugins_*.tar.gz 2>/dev/null | tail -n +$((KEEP_PLUGINS+1)) | xargs -r rm -f
ls -1t "$BACKUP_ROOT/plugins"/plugins_*.meta     2>/dev/null | tail -n +$((KEEP_PLUGINS+1)) | xargs -r rm -f
find "$BACKUP_ROOT/full" -name 'jenkins-home_*.tar.gz' -mtime +"$KEEP_FULL_DAYS" -delete
find "$BACKUP_ROOT/full" -name 'jenkins-home_*.meta'   -mtime +"$KEEP_FULL_DAYS" -delete

echo "[$(date '+%F %T')] backup ok: core=$CORE plugins=$BACKUP_ROOT/plugins/plugins_${STAMP}.tar.gz"

四、恢复流程

4.1 场景 A:插件冲突导致起不来(最常见)

# 1) 停 controller(先用 kubectl -n devops get deploy 确认 Deployment 名)
kubectl -n devops scale deploy <jenkins-deploy> --replicas=0

# 2) 回滚插件目录(不带参数会列出最近 7 份备份)
/usr/local/bin/jenkins-restore-plugins.sh /k8s_nas/jenkins_backup/plugins/plugins_20260914_023000.tar.gz

# 3) 起服务并验证
kubectl -n devops scale deploy <jenkins-deploy> --replicas=1
kubectl -n devops logs -f deploy/<jenkins-deploy>    # 确认插件全部加载成功

回滚脚本会把故障现场保留在 $JENKINS_HOME/plugins.broken.<时间戳>。恢复后我用 diff -rq plugins.broken.<ts> plugins 对比,就能定位是哪个插件引发的冲突,确认无用再删。

4.2 场景 B:配置被误改、误删单个文件

从配置层备份包里单独解出目标文件,不需要整体恢复:

tar -xzf /k8s_nas/jenkins_backup/full/jenkins-home_2026-09-14_0230.tar.gz -C /tmp ./config.xml
# 对比确认后再覆盖回 $JENKINS_HOME

4.3 场景 C:PVC 损坏、整套重建

# 1) 停 Jenkins
kubectl -n devops scale deploy <jenkins-deploy> --replicas=0
# 2) 解包回 PVC 目录(host57 本地解,或起临时 Pod 挂 jenkins-data-pvc 解)
tar --numeric-owner -xzf /k8s_nas/jenkins_backup/full/jenkins-home_2026-09-14_0230.tar.gz -C "$JENKINS_HOME"
# 3) 起 Jenkins,验证三件事:能登录、凭据可正常使用、Job 列表与插件版本正常
kubectl -n devops scale deploy <jenkins-deploy> --replicas=1

4.4 插件回滚脚本全文(jenkins-restore-plugins.sh)

#!/usr/bin/env bash
# ============================================================================
# Jenkins 插件目录回滚 —— 在 NFS 服务端(host57)执行
#
# 适用场景:插件自动升级/版本冲突导致 Jenkins 起不来(最高频故障)。
# 核心动作三步:停服务 → 解压备份 → 起服务,分钟级恢复。
#
# 前置(必须):先停 Jenkins controller,否则回滚期间文件被占用、且会立即被回写。
#   kubectl -n devops get deploy                              # 确认 Deployment 名
#   kubectl -n devops scale deploy <jenkins-deploy> --replicas=0
#
# 用法:
#   jenkins-restore-plugins.sh <plugins_YYYYmmdd_HHMMSS.tar.gz>
#   (不带参数则列出可用的 7 份备份供选择)
# ============================================================================
set -euo pipefail

BACKUP_ROOT="${BACKUP_ROOT:-/k8s_nas/jenkins_backup}"
BK="${1:-}"

if [ -z "$BK" ]; then
  echo "用法: $0 <plugins_YYYYmmdd_HHMMSS.tar.gz>"
  echo "可用备份(最新在前):"
  ls -1t "$BACKUP_ROOT/plugins"/plugins_*.tar.gz 2>/dev/null | head -7 || echo "  (无备份,请检查 BACKUP_ROOT)"
  exit 1
fi
[ -f "$BK" ] || { echo "ERROR: 备份包不存在: $BK"; exit 1; }

JENKINS_HOME="${JH_OVERRIDE:-$(ls -d /nfs_data/devops-jenkins-data-pvc-pvc-* 2>/dev/null | head -1)}"
if [ -z "${JENKINS_HOME:-}" ] || [ ! -d "${JENKINS_HOME:-}" ]; then
  echo "ERROR: 未找到 JENKINS_HOME(已尝试 glob: /nfs_data/devops-jenkins-data-pvc-pvc-*)"
  exit 1
fi

# 恢复前核对核心版本:插件与核心版本强绑定,核心升过级时旧插件可能反而不兼容
META="${BK%.tar.gz}.meta"
if [ -f "$META" ]; then
  echo "备份元数据:  $(tr '\n' ' ' < "$META")"
  echo "当前核心版本: $(cat "$JENKINS_HOME/jenkins.install.InstallUtil.lastExecVersion" 2>/dev/null || echo unknown)"
  echo "→ 若两者差异较大,先确认兼容性(Ctrl-C 可中止)"; sleep 5
fi

STAMP=$(date +%Y%m%d_%H%M%S)
# 保留故障现场:事后可用 diff -rq 定位是哪个插件引发的冲突
mv "$JENKINS_HOME/plugins" "$JENKINS_HOME/plugins.broken.$STAMP"
mkdir -p "$JENKINS_HOME/plugins"
# --numeric-owner 保持原 uid/gid:NFS 上属主错乱会导致 Jenkins 无权限读取而启动失败
tar --numeric-owner -xzf "$BK" -C "$JENKINS_HOME"

echo " 插件目录已回滚。"
echo "   故障现场: $JENKINS_HOME/plugins.broken.$STAMP"
echo "            (恢复后可用 diff -rq 对比定位冲突插件,确认无用再删除)"
echo "   下一步:   kubectl -n devops scale deploy <jenkins-deploy> --replicas=1"
echo "            然后查看 controller 启动日志确认插件全部加载成功"

五、配置的事实源与升级防护

5.1 UI 页面的配置到底存在哪

做备份清单时我把每个 UI 页面的落盘位置查了一遍,结论是分三层,备份覆盖情况完全不同:

配置 UI 入口 持久化位置 备份覆盖
系统全局配置 /manage/configure $JENKINS_HOME/config.xml 覆盖(配置层)
各插件全局配置 /manage/... $JENKINS_HOME/*.xml 覆盖(配置层)
Cloud 定义(UI 手工创建时) /manage/cloud/ $JENKINS_HOME/jenkins.model.Jenkins.clouds/*.xml 覆盖(配置层)
Kubernetes cloud + agent 模板(本环境实况) /manage/cloud/kubernetes/ helm ConfigMap,不在 JENKINS_HOME 不覆盖,事实源在 values

第四行是我查出来的盲区:PVC 里没有 jenkins.model.Jenkins.clouds/ 目录,只有 casc_configs/(ConfigMap 的运行时挂载)。读了 devops/jenkins-jenkins-jcasc-config 这个 ConfigMap(data key jcasc-default-config.yaml)实锤:Kubernetes cloud 的全部配置(serverUrl、jenkinsUrl、tunnel、默认 jnlp agent 模板、workspaceVolume)都由 chart 的 JCasC 渲染(controller.JCasC.defaultConfig: true),根本不落 JENKINS_HOME。

这也解释了那次升级后”Kubernetes 插件配置要重新配一遍”:helm upgrade 重新渲染 ConfigMap,JCasC 重新应用时把 UI 上的手工修改重置掉。这类配置丢失的根因是配置事实源不在 UI,备份救不了。

5.2 把 Kubernetes cloud 定制固化到 values

正确解法是把定制写进 helm-jenkins/values.yaml,让配置随 git 管理、helm upgrade 自动生效:

controller:
  JCasC:
    configScripts:
      kubernetes-cloud: |-
        jenkins:
          clouds:
            - kubernetes:
                name: kubernetes
                serverUrl: "https://kubernetes.default"
                # 把 UI 上定制过的字段搬进来

摘取当前生效配置:Manage Jenkins → Configuration as Code → View Configuration(或访问 https://jenkins.xxx.com/configuration-as-code/reference),把与 chart 默认不一致的段搬进 configScripts

注意casc_configs/ 目录虽然在 JENKINS_HOME 内、会被配置层备份打包,但它是 ConfigMap 的运行时快照,恢复后真正生效的仍是 helm release 的 ConfigMap。

5.3 升级 SOP

上次事故的机理:核心跨大版本 → kubernetes 等插件加载失败 → JCasC 应用 clouds 段失败 → cloud 配置空白。插件没加载起来时 JCasC 对应段必然失败,所以升级验证必须盯住 cloud 在位。

升级前:

  1. 手动跑一次备份:/usr/local/bin/jenkins-backup.sh
  2. 导出当前生效 JCasC 配置(Configuration as Code → Download Configuration),存入 helm-jenkins/,命名 jcasc-baseline-<日期>.yaml(当前基线为 jcasc-baseline-20260914.yaml),与上一份基线 diff
  3. 核心 LTS 只逐版本升、不跨大版本;对照插件管理页确认 kubernetes/pipeline/credentials 等核心插件的最低核心要求
  4. 记录插件清单:ls -1 $JENKINS_HOME/plugins/*.jpi(备份包里已含)

升级后验证:

  1. ConfigMap 里 clouds 段完整:kubectl -n devops get cm jenkins-jenkins-jcasc-config -o yaml | grep -A5 clouds
  2. UI /manage/cloud/ 确认 Kubernetes cloud 配置在位、字段不为空
  3. 试拉一个 agent:随便触发一个 Job,确认 agent Pod 起来并注册成功
  4. 启动日志无插件加载失败:kubectl -n devops logs deploy/<jenkins-deploy> | grep -iE "failed to load|error" | head
  5. Configuration as Code 管理页无应用报错

5.4 事故时快速恢复(cloud 空白、agent 起不来)

  1. 定位:ConfigMap 里 clouds 段是否完整(对照 jcasc-baseline-*.yaml
  2. ConfigMap 完整、Jenkins 内空白:强制 JCasC 重新应用。kubectl -n devops edit cm jenkins-jenkins-jcasc-config 随便改一个无害内容触发 config-reloader 热加载,或 kubectl -n devops rollout restart deploy/<jenkins-deploy>
  3. ConfigMap 缺失、残缺:helm rollback jenkins <上一revision> -n devops(PVC 带 helm.sh/resource-policy: keep 注解,回滚不动数据)
  4. 仍不行:把 jcasc-baseline-<日期>.yaml 内容经 Manage Jenkins → Configuration as Code → Apply 手工应用
  5. 全部失败:走 4.3 场景 C 全量重建

六、遇到的问题与注意事项

  • 备份写回同一块 NFS 盘没有意义 → BACKUP_ROOT 必须是异机挂载点或另一块物理盘,脚本已硬性拦截 /nfs_data 路径
  • 凭据四件套拆着备份 → 恢复后凭据全部无法解密,见 2.3 的成套要求
  • 打包解包不带 --numeric-owner → NFS 上属主错乱,Jenkins 无权限读取直接起不来
  • PVC 目录名带 uid(/nfs_data/devops-jenkins-data-pvc-pvc-<uid>),PVC 重建后会变 → 脚本用 glob 自动定位,必要时 JH_OVERRIDE 显式指定
  • 一致性:核心配置 xml 只在保存时写入、改动低频,tar 的秒级打包窗口内不一致风险可忽略;要求更严时备份前先 curl -X POST .../quietDown(只停接新任务,不影响运行中的构建),备完 cancelQuietDown
  • 关闭插件自动升级:Manage Jenkins → Plugins → Advanced settings,取消自动升级勾选(设置持久化在 JENKINS_HOME 内,会随备份保住;恢复到干净环境后重新确认一次)。插件自动升级正是插件冲突事故的源头
  • 没验证过的备份等于没有备份 → 每月演练一次恢复,test 集群可以当恢复靶场
  • 长期方向:全局配置也纳入配置即代码(JCasC)后,真正不可再生的只剩凭据与密钥,备份会更轻、更可靠

七、参考资料

  • jenkinsci/jenkins helm chart:https://github.com/jenkinsci/helm-charts (JCasC 与 persistence 配置说明)
  • Jenkins 官方备份建议:https://www.jenkins.io/doc/book/system-administration/backing-up/
暂无评论

发送评论 编辑评论


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