<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Mental Studio</title>
    <link>https://we8log.com/mental/</link>
    <description>Recent content on Mental Studio</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>zh</language>
    <copyright>CC 4.0 BY</copyright>
    <lastBuildDate>Sun, 04 Jan 2026 21:54:21 +0800</lastBuildDate>
    
        <atom:link href="https://we8log.com/mental/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>在K8s上部署Mastodon</title>
      <link>https://we8log.com/mental/post/26010/</link>
      <pubDate>Sun, 04 Jan 2026 21:54:21 +0800</pubDate>
      
      <guid>https://we8log.com/mental/post/26010/</guid>
      <description>&lt;h2 id=&#34;准备工作&#34;&gt;准备工作&lt;/h2&gt;
&lt;p&gt;因为原来用于跑毛象的服务器（参见旧文《&lt;a href=&#34;https://mental.we8log.com/mental/post/388/&#34;&gt;Docker下的Mastodon安装笔记&lt;/a&gt;》 ）到期停机了，正好手里有一个高配的arm免费机可以用，于是就打算把服务迁移过来——顺便试试部署到K8s上。&lt;/p&gt;
&lt;p&gt;需要：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;一台或多台服务器集群，内存至少4G，推荐8G，最好16G或更多，有外网IP，有一个域名&lt;/li&gt;
&lt;li&gt;安装Kubernetes集群（可以使用k3s、minikube或云服务商的K8s服务，本文使用一个kubeadm安装的单节点k8s，见《&lt;a href=&#34;https://mental.we8log.com/mental/post/25040/&#34;&gt;在单机上用kubeadm安装K8s&lt;/a&gt;》）&lt;/li&gt;
&lt;li&gt;一个SMTP服务——第三方或本地，这里用了第三方服务&lt;/li&gt;
&lt;li&gt;创建必要的存储目录（根据实际节点名称调整路径，这里使用/var/data）&lt;/li&gt;
&lt;li&gt;拉取Redis/Postgresql/ES/Mastodon镜像&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;crictl pull redis:7-alpine
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;crictl pull postgres:17-alpinie
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;crictl pull elasticsearch:7.10.1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;crictl pull ghcr.io/mastodon/mastodon:v4.5.2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;crictl pull ghcr.io/mastodon/mastodon-streaming:v4.5.2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;配置存储&#34;&gt;配置存储&lt;/h2&gt;
&lt;h3 id=&#34;1-创建命名空间&#34;&gt;1. 创建命名空间&lt;/h3&gt;
&lt;p&gt;首先创建mastodon命名空间：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl apply -f ns_mastodon.yml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ns_mastodon.yml&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;apiVersion: v1
kind: Namespace
metadata:
  name: mastodon
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;2-配置local-pvpvc用于有状态服务&#34;&gt;2. 配置Local PV/PVC（用于有状态服务）&lt;/h3&gt;
&lt;p&gt;我们使用local PV/PVC为PostgreSQL、Redis和Elasticsearch提供持久化存储：&lt;/p&gt;
&lt;h4 id=&#34;redis存储配置&#34;&gt;Redis存储配置&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 创建Redis数据目录（在目标节点上）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo mkdir -p /var/data/redis/data
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo chown 999:999 /var/data/redis/data
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 应用PV/PVC配置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl apply -f local_redis.yml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;local_redis.yml&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;apiVersion: v1
kind: PersistentVolume
metadata:
  name: local-redis-data
  labels:
    type: local
spec:
  capacity:
    storage: 10Gi  # 总容量，根据实际需要调整
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: local-redis-data
  local:
    path: /var/data/redis/data
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - myserver  # 替换为实际节点名称
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: redis-data-pvc
  namespace: mastodon
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi  # 主数据存储大小
  storageClassName: local-redis-data
  selector:
    matchLabels:
      type: local
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id=&#34;postgresql存储配置&#34;&gt;PostgreSQL存储配置&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 创建PostgreSQL数据目录（在目标节点上）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo mkdir -p /var/data/pgdata
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo mkdir -p /var/data/pgbak
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo chown 70:70 /var/data/pgdata /var/data/pgbak
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo chmod &lt;span style=&#34;color:#ae81ff&#34;&gt;700&lt;/span&gt; /var/data/pgdata
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 应用PV/PVC配置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl apply -f local_posgresql.yml  &lt;span style=&#34;color:#75715e&#34;&gt;# 内容参考redis，不过需要配置data和bak两个PV/PVC&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;elasticsearch存储配置&#34;&gt;Elasticsearch存储配置&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 创建Elasticsearch数据目录（在目标节点上）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo mkdir -p /var/data/elasticsearch
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo chown 1000:1000 /var/data/elasticsearch
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 应用PV/PVC配置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl apply -f local_elasticsearch.yml  &lt;span style=&#34;color:#75715e&#34;&gt;# 内容参考redis&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;3-配置nfs-pvpvc用于mastodon-public文件夹&#34;&gt;3. 配置NFS PV/PVC（用于Mastodon Public文件夹）&lt;/h3&gt;
&lt;p&gt;Mastodon需要共享存储来存储用户上传的媒体文件，因为需要同时给两个服务（web和streaming）使用，所以使用NFS PV/PVC：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 创建NFS共享目录&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo mkdir -p /var/data/mastodon
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo chown 991:991 /var/data/mastodon
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 配置NFS服务器（如果尚未配置）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 安装NFS服务器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo apt-get install nfs-kernel-server
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 配置NFS导出&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;echo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;/var/data/mastodon *(rw,sync,no_subtree_check,no_root_squash,all_squash,anonuid=991,anongid=991)&amp;#34;&lt;/span&gt; | sudo tee -a /etc/exports
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo exportfs -a  &lt;span style=&#34;color:#75715e&#34;&gt;# 应用配置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo systemctl restart nfs-kernel-server
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo exportfs -v  &lt;span style=&#34;color:#75715e&#34;&gt;# 查看应用结果&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 应用NFS配置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl apply -f nfs_mastodon.yml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;nsf_mastodon.yml&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-mastodon-data-web
  labels:
    type: nfs
spec:
  capacity:
    storage: 150Gi  # 总容量，根据实际需要调整
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nfs-mastodon-data-web
  nfs:
    server: 10.244.0.1  # flannel的IP
    path: /var/data/mastodon
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mastodon-data-web-pvc
  namespace: mastodon
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 150Gi  # 主数据存储大小
  storageClassName: nfs-mastodon-data-web
  selector:
    matchLabels:
      type: nfs
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-mastodon-data-sidekiq
  labels:
    type: nfs
spec:
  capacity:
    storage: 150Gi  # 总容量，根据实际需要调整
  volumeMode: Filesystem
  accessModes:
  - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nfs-mastodon-data-sidekiq
  nfs:
    server: 10.244.0.1  # 连同一个NFS
    path: /var/data/mastodon
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mastodon-data-sidekiq-pvc
  namespace: mastodon
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 150Gi  # 主数据存储大小
  storageClassName: nfs-mastodon-data-sidekiq
  selector:
    matchLabels:
      type: nfs
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;部署有状态服务stateful-headless-services&#34;&gt;部署有状态服务（Stateful Headless Services）&lt;/h2&gt;
&lt;h3 id=&#34;1-部署redis&#34;&gt;1. 部署Redis&lt;/h3&gt;
&lt;p&gt;创建Redis的StatefulSet配置：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl apply -f svc_redis.yml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;svc_redis.yml&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis
  namespace: mastodon
spec:
  serviceName: redis-headless  # 需要一个 Headless Service 管理网络标识
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:7-alpine
        ports:
        - containerPort: 6379
        volumeMounts:
        - name: redis-data
          mountPath: /data
        livenessProbe:
          exec:
            command: [&amp;#34;redis-cli&amp;#34;, &amp;#34;ping&amp;#34;]
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          exec:
            command: [&amp;#34;redis-cli&amp;#34;, &amp;#34;ping&amp;#34;]
          initialDelaySeconds: 5
          periodSeconds: 10
      volumes:
      - name: redis-data
        persistentVolumeClaim:
          claimName: redis-data-pvc  # 直接使用现有 PVC
  volumeClaimTemplates: []
---
apiVersion: v1
kind: Service
metadata:
  name: redis-headless
  namespace: mastodon
spec:
  clusterIP: None  # Headless Service，直接解析到 Pod IP
  selector:
    app: redis
  ports:
  - port: 6379
    name: redis
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;2-部署postgresql&#34;&gt;2. 部署PostgreSQL&lt;/h3&gt;
&lt;p&gt;PostgreSQL使用StatefulSet和Headless Service：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl apply -f svc_postgresql.yml  &lt;span style=&#34;color:#75715e&#34;&gt;# 内容参考svc_redis.yml，不过要mount两个卷&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;3-部署elasticsearch&#34;&gt;3. 部署Elasticsearch&lt;/h3&gt;
&lt;p&gt;创建Elasticsearch的StatefulSet配置：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl apply -f svc_elasticsearch.yml  &lt;span style=&#34;color:#75715e&#34;&gt;# 内容参考svc_redis.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id=&#34;4-验证部署&#34;&gt;4. 验证部署&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl get statefulsets -n mastodon
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl get pods -n mastodon -l app&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;postgresql
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl get services -n mastodon | grep postgresql
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;配置mastodon&#34;&gt;配置Mastodon&lt;/h2&gt;
&lt;h3 id=&#34;1-准备环境配置文件&#34;&gt;1. 准备环境配置文件&lt;/h3&gt;
&lt;p&gt;编辑 &lt;code&gt;env.production&lt;/code&gt; 文件，配置以下关键参数：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# Redis配置
REDIS_HOST=redis-headless.mastodon.svc.cluster.local
REDIS_PORT=6379

# 数据库配置
DB_HOST=postgresql-headless.mastodon.svc.cluster.local
DB_PORT=5432
DB_NAME=mastodon
DB_USER=mastodon
DB_PASS=你的数据库密码

# Elasticsearch配置
ES_ENABLED=true
ES_HOST=es-headless.mastodon.svc.cluster.local
ES_PORT=9200

# 域名配置
LOCAL_DOMAIN=你的域名
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;2-创建secret配置&#34;&gt;2. 创建Secret配置&lt;/h3&gt;
&lt;p&gt;将环境配置创建为Secret：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl create secret generic mastodon-env --from-env-file&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;env.production -n mastodon
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;3-部署mastodon服务&#34;&gt;3. 部署Mastodon服务&lt;/h3&gt;
&lt;p&gt;注意：除非是全新安装的实例，否则此操作需要在数据库恢复后进行。&lt;/p&gt;
&lt;p&gt;创建Mastodon的Deployment配置：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl apply -f svc_mastodon.yml  &lt;span style=&#34;color:#75715e&#34;&gt;# 类似svc_redis.yml，不过里面有三个服务：web,streaming,sidekiq&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;其中mastodon的web和streaming是以NodePort方式输出服务。&lt;/p&gt;
&lt;h2 id=&#34;恢复数据库&#34;&gt;恢复数据库&lt;/h2&gt;
&lt;h3 id=&#34;1-全库备份恢复方式&#34;&gt;1. 全库备份恢复方式&lt;/h3&gt;
&lt;p&gt;使用pg_dumpall备份全库再恢复到k8s里。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 从源docker环境备份整个库&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker exec postgresql pg_dumpall -U postgres | gzip &amp;gt; pgoldbak.sql.gz
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 把备份传输到k8s环境&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 恢复数据&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;gunzip -c pgoldbak.sql.gz | kubectl exec postgresql -i -n mastodon --command -- psql -U postgres
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;2-基于wal的增量备份恢复方式&#34;&gt;2. 基于WAL的增量备份恢复方式&lt;/h3&gt;
&lt;p&gt;备份恢复方式参考《&lt;a href=&#34;https://mental.we8log.com/mental/post/24090/&#34;&gt;PostgreSQL的连续备份配置&lt;/a&gt;》&lt;/p&gt;
&lt;h3 id=&#34;3-启动实例&#34;&gt;3. 启动实例&lt;/h3&gt;
&lt;p&gt;恢复数据库确认无误后即可启动毛象实例。&lt;/p&gt;
&lt;h2 id=&#34;启动服务&#34;&gt;启动服务&lt;/h2&gt;
&lt;p&gt;所有服务启动后检查状态：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl get all -n mastodon
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果所有Pod都处于Running状态，说明部署成功。如果有问题，则需要查看日志检查原因。&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl logs -f &amp;lt;podname&amp;gt; -nmastodon
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;配置nginx反向代理&#34;&gt;配置Nginx反向代理&lt;/h2&gt;
&lt;p&gt;创建Nginx配置，将流量代理到Mastodon服务：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cat &amp;gt; nginx/mastodon.conf &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;lt;&amp;lt; EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;upstream backend {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    server &amp;lt;NODE_IP&amp;gt;:3xxxx;  # 替换为节点IP和mastodon-web的NodePort
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;upstream streaming {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    server &amp;lt;NODE_IP&amp;gt;:3yyyy;  # 替换为节点IP和mastodon-streaming的NodePort
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;server {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    listen 80;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    server_name 你的域名;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    return 301 https://\$server_name\$request_uri;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;server {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    listen 443 ssl http2;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    server_name 你的域名;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    ssl_certificate /etc/ssl/你的域名/fullchain.pem;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    ssl_certificate_key /etc/ssl/你的域名/privkey.pem;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    root /var/data/mastodon/public;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    location / {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        try_files \$uri @proxy;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    location @proxy {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_set_header Host \$host;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_set_header X-Real-IP \$remote_addr;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_set_header X-Forwarded-Proto \$scheme;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_set_header Proxy &amp;#34;&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_pass_header Server;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_pass http://backend;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_buffering off;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_redirect off;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_http_version 1.1;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_set_header Upgrade \$http_upgrade;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_set_header Connection &amp;#34;upgrade&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        tcp_nodelay on;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    location /api/v1/streaming/ {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_set_header Host \$host;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_set_header X-Real-IP \$remote_addr;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_set_header X-Forwarded-Proto \$scheme;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_set_header Proxy &amp;#34;&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_pass http://streaming;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_buffering off;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_redirect off;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_http_version 1.1;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_set_header Upgrade \$http_upgrade;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        proxy_set_header Connection &amp;#34;upgrade&amp;#34;;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;        tcp_nodelay on;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;日常运维&#34;&gt;日常运维&lt;/h2&gt;
&lt;h3 id=&#34;1-监控服务状态&#34;&gt;1. 监控服务状态&lt;/h3&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 查看所有资源状态&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl get all -n mastodon
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 查看Pod日志&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl logs -f deployment/mastodon-web -n mastodon
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl logs -f deployment/mastodon-streaming -n mastodon
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl logs -f deployment/mastodon-sidekiq -n mastodon
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 查看有状态服务日志&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl logs -f statefulset/postgresql -n mastodon
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl logs -f statefulset/redis -n mastodon
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl logs -f statefulset/elasticsearch -n mastodon
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;2-定期清理&#34;&gt;2. 定期清理&lt;/h3&gt;
&lt;p&gt;由于联邦宇宙中的内容会不断增加，需要定期清理过期数据，具体实现与之前docker方式部署一样，只是把docker命令改成相应的kubectl命令。&lt;/p&gt;
&lt;p&gt;可以将这些清理任务添加到crontab中定期执行。&lt;/p&gt;
&lt;h3 id=&#34;3-备份与恢复&#34;&gt;3. 备份与恢复&lt;/h3&gt;
&lt;p&gt;也可以参考前文实现，或者参考pgsql的wal备份那篇用备份wal的方式实现连续备份。&lt;/p&gt;
&lt;h3 id=&#34;4-升级mastodon版本&#34;&gt;4. 升级Mastodon版本&lt;/h3&gt;
&lt;p&gt;升级Mastodon需要按顺序进行：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;备份数据库&lt;/li&gt;
&lt;li&gt;更新镜像版本&lt;/li&gt;
&lt;li&gt;重新部署服务&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 更新镜像版本&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sed -i &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;s|ghcr.io/mastodon/mastodon:v4.5.2|ghcr.io/mastodon/mastodon:新版本|g&amp;#39;&lt;/span&gt; svc_mastodon.yml
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sed -i &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;s|ghcr.io/mastodon/mastodon-streaming:v4.5.2|ghcr.io/mastodon/mastodon-streaming:新版本|g&amp;#39;&lt;/span&gt; svc_mastodon.yml
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 重新部署&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl apply -f svc_mastodon.yml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;K8s下的rollout升级：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 不涉及数据库变更的小版本升级可以用rollout方式平稳升级：&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl set image deployment/mastodon-streaming streaming&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;ghcr.io/mastodon/mastodon-streaming:v4.5.4 -nmastodon
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl set image deployment/mastodon-sidekiq sidekiq&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;ghcr.io/mastodon/mastodon:v4.5.4 -nmastodon
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl set image deployment/mastodon-web mastodon-web&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;ghcr.io/mastodon/mastodon:v4.5.4 -nmastodon
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl rollout status deployment/mastodon-streaming -nmastodon
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl rollout status deployment/mastodon-sidekiq -nmastodon
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl rollout status deployment/mastodon-web -nmastodon
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;升级完成。&lt;/p&gt;
&lt;h2 id=&#34;故障排除&#34;&gt;故障排除&lt;/h2&gt;
&lt;h3 id=&#34;1-pod无法启动&#34;&gt;1. Pod无法启动&lt;/h3&gt;
&lt;p&gt;检查Pod状态和日志：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl describe pod &amp;lt;pod-name&amp;gt; -n mastodon
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl logs &amp;lt;pod-name&amp;gt; -n mastodon
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;常见问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;存储卷挂载失败：检查PV/PVC状态 &lt;code&gt;kubectl get pv,pvc -n mastodon&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;资源配置不足：检查资源限制 &lt;code&gt;kubectl describe pod &amp;lt;pod-name&amp;gt; -n mastodon | grep -A 5 Resources&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;镜像拉取失败：检查镜像名称和标签&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;2-服务无法访问&#34;&gt;2. 服务无法访问&lt;/h3&gt;
&lt;p&gt;检查服务状态：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl get services -n mastodon
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl describe service &amp;lt;service-name&amp;gt; -n mastodon
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;确保NodePort在正确范围内（30000-32767）。&lt;/p&gt;
&lt;h3 id=&#34;3-数据库连接问题&#34;&gt;3. 数据库连接问题&lt;/h3&gt;
&lt;p&gt;检查数据库服务：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 测试数据库连接&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl run test-db-connection --rm -i --restart&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;Never &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  --image&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;postgres:17-alpine --namespace&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;mastodon -- &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;  psql -h postgresql-headless.mastodon.svc.cluster.local -U postgres -c &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;\l&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;4-存储问题&#34;&gt;4. 存储问题&lt;/h3&gt;
&lt;p&gt;检查存储状态：&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl get pv,pvc -n mastodon
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;kubectl describe pvc &amp;lt;pvc-name&amp;gt; -n mastodon
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;确保本地目录存在且有正确权限。&lt;/p&gt;
&lt;h2 id=&#34;总结&#34;&gt;总结&lt;/h2&gt;
&lt;p&gt;本指南介绍了如何使用Kubernetes部署Mastodon实例，包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;存储配置&lt;/strong&gt;：使用local PV/PVC为有状态服务（PostgreSQL、Redis、Elasticsearch）提供持久化存储&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;网络配置&lt;/strong&gt;：使用NFS PV/PVC为Mastodon public文件夹提供共享存储&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务部署&lt;/strong&gt;：使用StatefulSet和Headless Service部署有状态服务，使用Deployment部署无状态服务&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;运维管理&lt;/strong&gt;：包括监控、清理、备份、升级等日常运维操作&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;相比docker-compose部署方式，Kubernetes部署提供了更好的可扩展性、高可用性和运维便利性。可以根据实际需求调整资源配置和副本数量。&lt;/p&gt;
&lt;h2 id=&#34;注意事项&#34;&gt;注意事项&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;安全性&lt;/strong&gt;：生产环境需要配置更严格的安全策略，如Network Policies、Pod Security Policies等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能&lt;/strong&gt;：根据实际负载调整资源限制和副本数量&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;监控&lt;/strong&gt;：建议配置Prometheus和Grafana进行监控&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;备份&lt;/strong&gt;：定期备份数据库和重要数据&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;更新&lt;/strong&gt;：关注Mastodon和安全更新，及时升级&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如有问题，请参考Mastodon官方文档或Kubernetes官方文档。&lt;/p&gt;
&lt;p style=&#34;text-align: right&#34;&gt;推送到&lt;a href=&#34;http://www.go4pro.org/&#34;&gt;[go4pro.org]&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>将supervisor迁移到systemd</title>
      <link>https://we8log.com/mental/post/25120/</link>
      <pubDate>Sat, 20 Dec 2025 21:58:45 +0800</pubDate>
      
      <guid>https://we8log.com/mental/post/25120/</guid>
      <description>&lt;h2 id=&#34;历史问题&#34;&gt;历史问题&lt;/h2&gt;
&lt;p&gt;在前systemd时代，要配置一个启动运行的服务还是比较麻烦的，所以才有了supervisor这种服务，但是现在systemd已经很成熟了，不再需要多此一举：&lt;/p&gt;
&lt;p&gt;systemd启动supervisord，supervisord再来启动自定义服务&lt;/p&gt;
&lt;p&gt;是时候把supervisor的服务迁移到systemd下了。&lt;/p&gt;
&lt;h2 id=&#34;转换配置文件&#34;&gt;转换配置文件&lt;/h2&gt;
&lt;p&gt;首先是把&lt;code&gt;/etc/supervisor/conf.d/&lt;/code&gt;下面的服务配置文件改成systemd格式的，然后放到&lt;code&gt;/etc/systemd/system/&lt;/code&gt;下。&lt;/p&gt;
&lt;p&gt;转换对照表如下：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Supervisor 指令&lt;/th&gt;
&lt;th&gt;systemd 指令&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;command&lt;/td&gt;
&lt;td&gt;ExecStart&lt;/td&gt;
&lt;td&gt;运行程序的完整路径和参数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;directory&lt;/td&gt;
&lt;td&gt;WorkingDirectory&lt;/td&gt;
&lt;td&gt;程序运行的起始目录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;user&lt;/td&gt;
&lt;td&gt;User&lt;/td&gt;
&lt;td&gt;运行该服务的用户&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;environment&lt;/td&gt;
&lt;td&gt;Environment&lt;/td&gt;
&lt;td&gt;环境变量设置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;autostart&lt;/td&gt;
&lt;td&gt;WantedBy=multi-user.target&lt;/td&gt;
&lt;td&gt;是否随系统启动&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;autorestart&lt;/td&gt;
&lt;td&gt;Restart=always&lt;/td&gt;
&lt;td&gt;进程退出后是否自动重启&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stdout_logfile&lt;/td&gt;
&lt;td&gt;StandardOutput&lt;/td&gt;
&lt;td&gt;标准输出（通常交给 journald）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stderr_logfile&lt;/td&gt;
&lt;td&gt;StandardError&lt;/td&gt;
&lt;td&gt;标准错误输出&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;一个典型的systemd服务单元配置如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[Unit]
Description=My Python Application
After=network.target

[Service]
# 基础配置
User=www-data
Group=www-data
WorkingDirectory=/var/www

# 环境变量 (注意语法：空格分隔或多次声明)
Environment=NODE_ENV=production
Environment=PORT=8080

# 启动命令 (必须使用绝对路径)
ExecStart=/usr/bin/python3 /var/www/app.py

# 重启策略
Restart=always
# 启动失败后的等待时间（可选）
RestartSec=5

# 日志处理：默认会发送到 journald
StandardOutput=append:/var/log/my-app.out.log
StandardError=inherit

[Install]
# 允许随系统启动
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;管理systemd服务&#34;&gt;管理systemd服务&lt;/h2&gt;
&lt;p&gt;常用的命令如下：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# 新增或修改配置文件以后需要重新加载，类似 supervisorctl reread &amp;amp;&amp;amp; supervisorctl update，或者更粗暴的supervisorctl reload
systemctl daemon-reload
# 查看服务状态
systemctl status [yourservice]
# 设置服务开机自启动
systemctl enable [yourservice]
# 加上--now选项可以在设置后立即启动服务，同理用disable命令可以禁用开机自启动
# 手工启动服务
systemctl start [yourservice]
&lt;/code&gt;&lt;/pre&gt;&lt;p style=&#34;text-align: right&#34;&gt;推送到&lt;a href=&#34;http://www.go4pro.org/&#34;&gt;[go4pro.org]&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Linuxmint中蓝牙配对问题的处理</title>
      <link>https://we8log.com/mental/post/25110/</link>
      <pubDate>Sat, 08 Nov 2025 17:35:07 +0800</pubDate>
      
      <guid>https://we8log.com/mental/post/25110/</guid>
      <description>&lt;h2 id=&#34;起因&#34;&gt;起因&lt;/h2&gt;
&lt;p&gt;最近常用的这个Linuxmint系统忽然无法添加蓝牙设备了，表现为可以搜索到要添加的设备，但是点击添加后，显示正在连接，很长时间连不上，然后就报错了。有时也可以连接上，但是无法使用，一会之后又断开了。&lt;/p&gt;
&lt;p&gt;问AI半天也没有解决方案，什么重启蓝牙服务，重装蓝牙管理器之类的方法都试过了，没一个管用的。甚至还试了改polkit权限配置、重新加载蓝牙内核模块之类的方法，也没用。&lt;/p&gt;
&lt;h2 id=&#34;解决&#34;&gt;解决&lt;/h2&gt;
&lt;p&gt;自己试了半天后发现问题出在无法弹出配对确认框上，但是这个问题始终没有找到解决方案，最后只能通过手工配对的方式来解决：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# 进入命令行状态：
bluetoothctl
# 在交互界面操作：
power on
agent on
scan on
# 现在开始扫描周围的蓝牙设备，记录一下要添加的设备MAC
# 配对设备
pair &amp;lt;设备MAC&amp;gt;
# 信任设备（就是弹框需要做的操作）
trust &amp;lt;设备MAC&amp;gt;
# 连接设备
connect &amp;lt;设备MAC&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这样配对好以后就可以正常使用蓝牙设备了。&lt;/p&gt;
&lt;p style=&#34;text-align: right&#34;&gt;推送到&lt;a href=&#34;http://www.go4pro.org/&#34;&gt;[go4pro.org]&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Ansible贴士二则</title>
      <link>https://we8log.com/mental/post/25100/</link>
      <pubDate>Sun, 12 Oct 2025 15:22:55 +0800</pubDate>
      
      <guid>https://we8log.com/mental/post/25100/</guid>
      <description>&lt;h2 id=&#34;避免主机ssh指纹确认&#34;&gt;避免主机SSH指纹确认&lt;/h2&gt;
&lt;p&gt;通常在第一次用SSH连接一台远程主机的时候，都会弹出一个SSH指纹警告，需要人工确认后，把这个指纹加到本地.ssh/knwon_hosts文件中。以后要是同一远程主机的指纹变化了（比如被冒充），就会导致SSH报错后断开连接，除非你能确认这种变化，那就需要从known_hosts里把原来的指纹删除：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ssh-keygen -R remote_host:port
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后再次连接并确认记录新指纹。&lt;/p&gt;
&lt;p&gt;手工单次连接时顺手确认一下没什么问题，但是用Ansible连接实际上也是走ssh，也要验证指纹，所以也会有这个交互过程。&lt;/p&gt;
&lt;p&gt;但这就麻烦了，因为通常用Ansible跑playbook时都是需要处理一堆主机的，每个都这样人工确认就太麻烦了，所以更好的办法是先批量更新一下known_hosts：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# 主机地址列表
HOSTS=(xxx yyy zzz)

for h in &amp;#34;${HOSTS[@]}&amp;#34;
do
    ssh-keyscan -p 22 $h &amp;gt;&amp;gt; ~/.ssh/knwon_hosts
done
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;使用指定的key连接&#34;&gt;使用指定的key连接&lt;/h2&gt;
&lt;p&gt;通常用ssh连接时，可以通过-i参数指定key文件，比如：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ssh -i /path_to/your_key_file user@host
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ansible里也可以指定，使用ansible_ssh_private_key_file变量：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;ansible_ssh_private_key_file: /path_to/your_key_file
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;外一则&#34;&gt;外一则&lt;/h2&gt;
&lt;p&gt;之前在Ansible中创建用户用的生成密码命令：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;python3 -c &amp;#34;import crypt; print(crypt.crypt(&amp;#39;{{ plain_password }}&amp;#39;))&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;现在会报一个警告：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;DeprecationWarning: &amp;#39;crypt&amp;#39; is deprecated and slated for removal in Python 3.13
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;从Python3.13开始要移除crypt模块了，所以需要换个方式。&lt;/p&gt;
&lt;p&gt;一个方式是使用passlib模块：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;python3 -c &amp;#34;from passlib.hash import sha512_crypt; print(sha512_crypt.hash(&amp;#39;your_password&amp;#39;))&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;不过更好的方法是使用Ansible内置的过滤器：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;- name: 创建用户
  hosts: all
  vars:
    plain_password: &amp;#34;your_password&amp;#34;
  
  tasks:
    - name: 创建用户（使用过滤器生成哈希）
      user:
        name: youruser
        password: &amp;#34;{{ plain_password | password_hash(&amp;#39;sha512&amp;#39;) }}&amp;#34;
        state: present
      no_log: true
&lt;/code&gt;&lt;/pre&gt;&lt;p style=&#34;text-align: right&#34;&gt;推送到&lt;a href=&#34;http://www.go4pro.org/&#34;&gt;[go4pro.org]&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>基于wstunnel的反向隧道配置</title>
      <link>https://we8log.com/mental/post/25090/</link>
      <pubDate>Sat, 06 Sep 2025 15:19:00 +0800</pubDate>
      
      <guid>https://we8log.com/mental/post/25090/</guid>
      <description>&lt;h2 id=&#34;需求&#34;&gt;需求&lt;/h2&gt;
&lt;p&gt;因为家里一条宽带换成移动的，没有外网IP了，所以从外面接入不太方便，IPv6虽然也是个方案，但是内网那台arm小机器是很老的Debian7，没有驱动也升级不了系统，还是搞个隧道简单点。&lt;/p&gt;
&lt;p&gt;之前《&lt;a href=&#34;https://mental.we8log.com/mental/post/375/&#34;&gt;使用docker和wstun实现隧道和反向隧道&lt;/a&gt;》，但是这个用Node.js实现的太笨重了，而且debian7上也装不了docker。还好现在有新方案，就是这个&lt;a href=&#34;https://github.com/erebe/wstunnel&#34;&gt;用Rust实现的wstunnel&lt;/a&gt;。一个可执行文件轻松跑起，还支持32位arm linux。&lt;/p&gt;
&lt;p&gt;简单起见这里不再说正向隧道的配置了，参考前文，换成wstunnel即可。下面只说反向隧道的。&lt;/p&gt;
&lt;h2 id=&#34;服务端&#34;&gt;服务端&lt;/h2&gt;
&lt;p&gt;直接运行是最简单的：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;wstunnel server ws://[::]:8080
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是这样有个安全风险就是只要能连接到这个websocket服务端就可以任意映射端口，等于绕过了防火墙的检查。&lt;/p&gt;
&lt;p&gt;虽然官方建议是可以加上https和随机的path-prefix加以防护，但我觉得还是直接包进docker里更保险一点。&lt;/p&gt;
&lt;p&gt;用Rust的好处之一就是这是独立可执行文件，没有依赖，直接用一个最小系统镜像（如alpine:3.22）就能运行：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;docker run -d --name wstunnel -v $HOME/opt:/root/opt -p 127.0.0.1:8080:8080 -p 0.0.0.0:8022:8022 alpine:3.22 /root/opt/wstunnel server ws://[::]:8080
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这个命令的功能就是用alpine:3.22的镜像来运行wstunnel（放在$HOME/opt下），把websocket端口8080映射到本地，把反向隧道的端口8022映射到外网。这样即使有其它的客户端想到映射别的端口，或者映射服务器的端口都无法实现，因为都是在docker容器里面。&lt;/p&gt;
&lt;p&gt;当然为了保险起见，可以继续叠加官方的安全手段。&lt;/p&gt;
&lt;h2 id=&#34;客户端&#34;&gt;客户端&lt;/h2&gt;
&lt;p&gt;因为无法运行docker，就只能直接运行了：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;wstunnel client -R &amp;#39;tcp://[::]:8022:127.0.0.1:22&amp;#39; ws://server:8080
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;为了保持运行，我实际上是用supervisor加持了一下。&lt;/p&gt;
&lt;p&gt;远程的地址实际上也不是这个，而是用https代理过的。&lt;/p&gt;
&lt;h2 id=&#34;服务端https代理配置&#34;&gt;服务端https代理配置&lt;/h2&gt;
&lt;p&gt;也是使用Nginx的反向代理，如前文：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# 在http段加上全局配置
map $http_upgrade $connection_upgrade {
    default upgrade;
    &amp;#39;&amp;#39; close;
}
# 在server段加上代理配置
    location / {
        proxy_pass http://127.0.0.1:8080;
        
        # 启用WebSocket支持的关键配置
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        # 保持连接和代理头信息
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # 设置较长的超时时间以保持持久连接
        proxy_read_timeout 86400;
        proxy_send_timeout 86400;
        keepalive_timeout 86400;
    }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这样配置以后，只要服务端和客户端保持运行，就可以通过服务端的8022端口连接到客户机的22。&lt;/p&gt;
&lt;p style=&#34;text-align: right&#34;&gt;推送到&lt;a href=&#34;http://www.go4pro.org/&#34;&gt;[go4pro.org]&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>在CentOS8.5上使用docker-compose</title>
      <link>https://we8log.com/mental/post/25080/</link>
      <pubDate>Sat, 02 Aug 2025 15:13:17 +0800</pubDate>
      
      <guid>https://we8log.com/mental/post/25080/</guid>
      <description>&lt;h2 id=&#34;起因&#34;&gt;起因&lt;/h2&gt;
&lt;p&gt;说过很多次，我非常不喜欢CentOS，想用RedHat系就掏钱去用RHEL，不然就老老实实Debian之类。然而却不得不经常要面对别人装的CentOS。&lt;/p&gt;
&lt;p&gt;这回就是这样，需要在一个别人装的CentOS8.5上装docker-compose。&lt;/p&gt;
&lt;h2 id=&#34;安装docker&#34;&gt;安装docker&lt;/h2&gt;
&lt;p&gt;CentOS早就停止支持这事大家都知道了吧，当然旧版还是有archive源可以用的，我就用了一个阿里云的archive源来安装。&lt;/p&gt;
&lt;p&gt;先备份原来的源配置，然后换成阿里，再安装：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;cp /etc/yum.repos.d/epel.repo /etc/yum.repos.d/epel.repo.backup
cp /etc/yum.repos.d/epel-modular.repo /etc/yum.repos.d/epel-modular.repo.backup
# 下载并替换epel配置
wget -O /etc/yum.repos.d/epel.repo https://mirrors.aliyun.com/repo/epel-archive-8.repo
# 然后更新一下
yum update
# 因为RH系不提供docker，而是使用podman，所以只能安装兼容的podman-docker
yum install podman-docker
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;安装docker-compose&#34;&gt;安装docker-compose&lt;/h2&gt;
&lt;p&gt;虽然这事已经是陈年往事了，但还是说明一下：因为docker是带有一个docker服务的，这点被嫌弃很多年，所以后来出了一些不需要服务的容器方案，比如containerd和podman。K8s也已经完全抛弃了docker。&lt;/p&gt;
&lt;p&gt;但用这些方案带来的问题就是有些docker的功能不支持，比如docker-compose。&lt;/p&gt;
&lt;p&gt;为了提供类似的功能，也有一些部分解决方案，比如现在我们要用的。&lt;/p&gt;
&lt;p&gt;首先是配置podman.socket以提供一个docker服务兼容接口：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# 启用podman.socket服务
systemctl enable --now  podman.socket
systemctl status podman.socket
# 配置环境变量供docker-compose使用
export DOCKER_HOST=unix:///run/podman/podman.sock
# 也可以配置到.bashrc里
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后安装一下docker-compose：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# 先升级一下可能需要的依赖
pip3 install --user -U pip
pip3 install --user -U wheel
pip3 install --user -U setuptools
pip3 install --user -U setuptools_rust
# 安装docker-compose
pip3 install --user docker-compose
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;现在就可以正常使用docker-compose了：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;~/.local/bin/docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;但是有些功能还是不能用的，比如docker-compose build，这种就只能先用podman把镜像先build好再用docker-compose了。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;podman build -t your_image .
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;至少是能用了。&lt;/p&gt;
&lt;p style=&#34;text-align: right&#34;&gt;推送到&lt;a href=&#34;http://www.go4pro.org/&#34;&gt;[go4pro.org]&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>用uv代替pip和venv</title>
      <link>https://we8log.com/mental/post/25070/</link>
      <pubDate>Sat, 26 Jul 2025 11:17:09 +0800</pubDate>
      
      <guid>https://we8log.com/mental/post/25070/</guid>
      <description>&lt;h2 id=&#34;为什么要换&#34;&gt;为什么要换&lt;/h2&gt;
&lt;p&gt;python的环境管理用过很多了，从最早的virtualenv到后来封装后的virtualenvwraper再到现在用的自带的venv，确实是越来越好用的。不过最近这个用rust写的uv似乎比较红，群里的MK和95也推荐，所以我也来试试。&lt;/p&gt;
&lt;h2 id=&#34;安装&#34;&gt;安装&lt;/h2&gt;
&lt;p&gt;MK说只要pip install uv就可以了，但是在我的mint上似乎没这么简单。&lt;/p&gt;
&lt;p&gt;首先是现在不允许直接安装系统python包了，怕破坏依赖关系，虽然可以加参数强制安装，但毕竟有风险。这种情况我通常是去apt里找一个类似：&lt;code&gt;python3-uv&lt;/code&gt;这样的包来装，但不幸的是mint里还没有。&lt;/p&gt;
&lt;p&gt;只好退而用另一个替代方案：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sudo apt install pipx
pipx install uv  # 官方源如果太慢，可以用-i参数使用国内镜像源
# 安装路径在$HOME/.local/bin，记得加到PATH里
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;使用&#34;&gt;使用&lt;/h2&gt;
&lt;p&gt;使用方法类似venv，只是需要前面加上uv命令。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;uv venv # 在当前路径下创建.venv的虚拟环境，如果不想用.venv，可以在后面指定名字
. .venv/bin/activate # 切换到虚拟环境
uv pip install -r requirements.txt # 也可以用-i使用国内镜像源，不过uv不支持在requirements.txt里用--index指定源
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;安装速度真的是比pip高不知道哪里去……&lt;/p&gt;
&lt;p&gt;需要注意的是，uv pip并不会使用默认的&lt;code&gt;~/.pip/pip.conf&lt;/code&gt;里的配置，而是有自己的配置文件：&lt;code&gt;~/.config/uv/uv.toml&lt;/code&gt;，所以如果想要指定默认镜像，还得加到这个配置文件里，比如：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# ~/.config/uv/uv.toml

index-url = &amp;#34;https://mirrors.aliyun.com/pypi/simple/&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;虚拟环境&#34;&gt;虚拟环境&lt;/h2&gt;
&lt;p&gt;需要注意的是，在pip中，可以在不切换到虚拟环境的情况，直接使用类似&lt;code&gt;.venv/bin/pip&lt;/code&gt;命令来直接管理虚拟环境里的包。这个功能在docker构建的时候特别好用。但是uv不行，不论是否在虚拟环境里，运行的uv命令都是在&lt;code&gt;~/.local/bin&lt;/code&gt;下的，所以如果想在不切换到虚拟环境的情况下进行虚拟环境里的包管理则需要通过两种方式：&lt;/p&gt;
&lt;p&gt;一种是在虚拟环境里安装一个uv，然后使用这个uv。另一种是指定使用虚拟环境里的python。&lt;/p&gt;
&lt;p&gt;安装uv法：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;. .venv/bin/activate # 切换到虚拟环境
uv pip install uv  # 在虚拟环境里安装uv
deactivate # 退出虚拟环境
.venv/bin/uv pip install -r requirements.txt  # 在虚拟环境里安装依赖
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;指定python法：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;~/.local/bin/uv pip install -p .venv/bin/python -r requirements.txt  # 在.venv虚拟环境里安装依赖
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;个人推荐后者，因为比较简洁。&lt;/p&gt;
&lt;p&gt;其实根本问题就是uv创建虚拟环境的时候没有默认在环境里装一个uv，但pip的虚拟环境里默认是有pip的。&lt;/p&gt;
&lt;p style=&#34;text-align: right&#34;&gt;推送到&lt;a href=&#34;http://www.go4pro.org/&#34;&gt;[go4pro.org]&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>DBeaver的数据库连接保持配置</title>
      <link>https://we8log.com/mental/post/25060/</link>
      <pubDate>Sat, 21 Jun 2025 11:09:33 +0800</pubDate>
      
      <guid>https://we8log.com/mental/post/25060/</guid>
      <description>&lt;h2 id=&#34;问题&#34;&gt;问题&lt;/h2&gt;
&lt;p&gt;因为需要管理mysql和postgresql，所以我通常使用DBeaver（CE版）来管理，但是它有个问题就是一会不操作，数据库连接就会断开并且操作卡死，需要退出应用再重新运行才行，相当麻烦，所以前几年我在Mac下连mysql还是经常用Sequel Pro（后来的Ace）。&lt;/p&gt;
&lt;p&gt;这几年改用Linux以后，就完全用DBeaver了，虽然中间也研究过一些配置，但也并不总是管用，想想还是把这些设置都记录一下，慢慢总结看看到底哪些方法是管用的。&lt;/p&gt;
&lt;h2 id=&#34;全局配置&#34;&gt;全局配置&lt;/h2&gt;
&lt;p&gt;首先打开DBeaver的系统设置，找到全局设置（在菜单的“窗口”-“首选项”），将“连接”下的“错误和超时”设置中的“执行错误”-“自动重连次数”改大一点，比如3次，增加连接成功率。&lt;/p&gt;
&lt;h2 id=&#34;数据库连接配置&#34;&gt;数据库连接配置&lt;/h2&gt;
&lt;p&gt;然后在具体的数据库连接的“编辑连接”中的“连接设置”-“驱动属性”里修改以下几项（根据数据库不同可能有所不同）：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;autoReconnect=True
connectionTimeout=3600
tcpKeepAlive=True
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;ssh隧道连接配置&#34;&gt;SSH隧道连接配置&lt;/h2&gt;
&lt;p&gt;如果通过SSH隧道连接，还需要再修改SSH里的“高级”：&lt;/p&gt;
&lt;p&gt;修改“长连接时间间隔”，我一般用30秒，因为这里单位是毫秒，所以填：30000。&lt;/p&gt;
&lt;p&gt;这项用于每30秒与SSH服务端通信一下，保持SSH连接。&lt;/p&gt;
&lt;p&gt;经过以上几荐修改后，数据库连接可以较为稳定，正常情况下都不会断连，即使因为网络中断而断连后，重连也很正常，不像之前会重连卡死或反复重连失败。&lt;/p&gt;
&lt;p style=&#34;text-align: right&#34;&gt;推送到&lt;a href=&#34;http://www.go4pro.org/&#34;&gt;[go4pro.org]&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>用Alembic进行数据库版本管理</title>
      <link>https://we8log.com/mental/post/25050/</link>
      <pubDate>Sun, 25 May 2025 10:41:02 +0800</pubDate>
      
      <guid>https://we8log.com/mental/post/25050/</guid>
      <description>&lt;h2 id=&#34;序&#34;&gt;序&lt;/h2&gt;
&lt;p&gt;用SQLAlchemy进行数据库开发是很方便，特别是它有强大的ORM，只要在model里把数据库模型定义好，直接初始化一下就行了。&lt;/p&gt;
&lt;p&gt;但是实际的开发不可能一开始就把数据库定义得这么完善，另外，也有时会碰到先有数据库再进行开发的情况，这时就比较麻烦了。&lt;/p&gt;
&lt;p&gt;所以需要有工具支持。&lt;/p&gt;
&lt;p&gt;Alembic就是一个配合SQLAlchemy的数据库管理工具。&lt;/p&gt;
&lt;p&gt;在介绍Alembic之前，先来看看已经有数据库时要怎么办。&lt;/p&gt;
&lt;h2 id=&#34;已有数据库&#34;&gt;已有数据库&lt;/h2&gt;
&lt;p&gt;如果你之前已经用过SQLAlchemy，更常见的情况是实际的数据库经过一段时间的修改已经与早期定义的models不一致。不论是之前已有数据库，还没创建models，还是已有models，但与数据库不一致，都可以如下处理。&lt;/p&gt;
&lt;p&gt;这就需要另外一个工具：sqlacodegen。安装的话就是一句PIP的事情：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;pip install sqlacodegen
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后用命令（以Mysql为例，pg的话就用psycopg2）：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;sqlacodegen mysql+pymysql://user:pass@localhost:3306/dbname --outfile db/models.py
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;即可从数据库生成一个SQLAlchemy的models定义文件。有了这个保持一致的models才好用alembic来管理。&lt;/p&gt;
&lt;h2 id=&#34;初始化&#34;&gt;初始化&lt;/h2&gt;
&lt;p&gt;安装就不说了，也是一句PIP的事情。&lt;/p&gt;
&lt;p&gt;然后在项目路径下执行：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;alembic init alembic
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;会创建几个文件和目录：alembic.ini，alembic目录及其下面的env.py, README, script.py.mako和versions目录。&lt;/p&gt;
&lt;p&gt;最简单的用法就是在alembic.ini里找到sqlalchemy.url参数，改为当前开发环境的数据库连接。然后修改&lt;code&gt;alembic/env.py&lt;/code&gt;：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;from db.models import Base  # 从你的sqlalchemy的models定义中导入Base

# ...

target_metadata = Base.metadata  # 把原来默认的None改为你的Base.metadata，以便alembic可以找到你的数据库模型定义
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后执行：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;alembic revision --autogenerate -m &amp;#34;init&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;即可以versions目录下生成一个版本文件。&lt;/p&gt;
&lt;p&gt;如果数据库不存在，则这个版本可以作为初始化的建库基础。如果数据库已存在，那models一定要与现在的数据库保持一致，最好是用sqlacodegen从数据库重新生成一个models。&lt;/p&gt;
&lt;p&gt;然后运行：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;alembic upgrade head
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果数据库是新的空库，这个操作就会根据models的定义创建一个数据库结构。如果数据库已存在并且与models定义一致，即可将开发环境的数据库的alembic版本标记为当前版本，以便后续变更。&lt;/p&gt;
&lt;p&gt;之后部署到正式环境，只需要配置好正式环境的数据库链接，然后运行&lt;code&gt;alembic upgrade head&lt;/code&gt;，即可创建一个和开发环境一致的数据库。&lt;/p&gt;
&lt;h2 id=&#34;版本升级&#34;&gt;版本升级&lt;/h2&gt;
&lt;p&gt;随着开发工作的进行，数据库肯定也会变更，这个时候要注意，不要直接在开发数据库上变更，所有的变更都要在sqlalchemy的models上进行。在更新了models以后，执行：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;alembic revision --autogenerate -m &amp;#34;new feature&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;生成一个new feature的alembic新版本记录。同样在生成以后&lt;code&gt;alembic upgrade head&lt;/code&gt;更新一下开发环境的数据库版本。这样数据库就与models一致了，可以继续进行开发。&lt;/p&gt;
&lt;p&gt;部署到正式环境后，同样执行一下&lt;code&gt;alembic upgrade head&lt;/code&gt;，即可将正式环境数据库更新到和开发环境一致，这时再部署代码运行就不会有数据库不一致的问题了。&lt;/p&gt;
&lt;h2 id=&#34;关于配置&#34;&gt;关于配置&lt;/h2&gt;
&lt;p&gt;修改alembic.ini里的数据库链接固然方便，但是不便于与生产环境同步，所以最好还是动态读取实际的配置。&lt;/p&gt;
&lt;p&gt;假设配置信息放在一个单独的配置文件config.py里，生产环境和开发环境用的配置文件是不一样的，代码里通过一个Config类去读取，类似这样：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;class Config:
    config: dict = load_config()  # 从配置文件里读取配置
    DB_URL: str = config.get(&amp;#34;DB_URL&amp;#34;)  # 读取数据库配置
    
settings = Config()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;那么我们现在就可以来修改alembic的配置，让它去读取实际的配置，而不是写死在alembbic.ini里，这样不论是在开发环境还是在生产环境运行alembic，使用的都是正确的数据库配置。&lt;/p&gt;
&lt;p&gt;首先修改alembic.ini，把&lt;code&gt;sqlalchemy.url&lt;/code&gt;一项注释掉，然后修改alembic/env.py，让它改为读取项目配置，而不是alembic.ini：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# 导入配置
from config import settings

# ...

# 修改run_migrations_offline函数
def run_migrations_offline() -&amp;gt; None:
    # url = config.get_main_option(&amp;#34;sqlalchemy.url&amp;#34;)  # 注释掉原来读取alembic.ini的语句
    url = settings.DB_URI  # 改为读取项目配置
    
    # ...
    
# ...

# 修改run_migrations_online函数
def run_migrations_online() -&amp;gt; None:
    # 读取项目配置并设置到alembic的sqlalchemy配置里
    url = settings.DB_URI
    config.set_main_option(&amp;#34;sqlalchemy.url&amp;#34;, url)
    
    connectable = engine_from_config(
        config.get_section(config.config_ini_section, {}),
        prefix=&amp;#34;sqlalchemy.&amp;#34;,
        poolclass=pool.NullPool,
    )

    # ...
    
#...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这样就可以方便地在开发环境和生产环境之间同步了。&lt;/p&gt;
&lt;p&gt;记得每次开发环境有修改过数据库模型，就创建一个alembic版本。&lt;/p&gt;
&lt;p&gt;——注意，只能在models.py里修改，不得直接修改数据库&lt;/p&gt;
&lt;p&gt;提交代码的时候把alembic版本一起提交上去，在正式环境上部署时同样要把变更的alembic版本更新下来，然后运行&lt;code&gt;alembic upgrade head&lt;/code&gt;同步生产环境的数据库结构，这样就能保持一致了。&lt;/p&gt;
&lt;p style=&#34;text-align: right&#34;&gt;推送到&lt;a href=&#34;http://www.go4pro.org/&#34;&gt;[go4pro.org]&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>在单机上用kubeadm安装K8s</title>
      <link>https://we8log.com/mental/post/25040/</link>
      <pubDate>Sat, 12 Apr 2025 12:52:44 +0800</pubDate>
      
      <guid>https://we8log.com/mental/post/25040/</guid>
      <description>&lt;h2 id=&#34;起因&#34;&gt;起因&lt;/h2&gt;
&lt;p&gt;最近（其实已经好几个月了）搞了一台配置比较高的机器，想用来跑多一些应用，当然直接用docker也不是不行，或者加上portainer，但还是觉得k8s更好玩一些。&lt;/p&gt;
&lt;p&gt;因为之前是按照常规的组集群的方式安装的，这回只有单机，又不想用minikube之类的，所以还是按kubeadm的方式来装。加上新版本有一些变化，就懒得再去改旧文，还是重新水一篇吧。&lt;/p&gt;
&lt;p&gt;基本的系统环境是：3C19G的ARM平台，系统是ubuntu 24.04LTS&lt;/p&gt;
&lt;h2 id=&#34;基本安装&#34;&gt;基本安装&lt;/h2&gt;
&lt;p&gt;首先，新版的k8s已经不再使用docker，改用其它容器实现方案，我这边选择的是containerd。操作接近原来的docker，大部分情况下把docker命令换成crictl即可。&lt;/p&gt;
&lt;h3 id=&#34;ansible剧本&#34;&gt;Ansible剧本&lt;/h3&gt;
&lt;p&gt;用的还是之前那个剧本，稍作修改，比如docker代理不需要了，因为服务器本身在国外。&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;---
- name: Install packages
  apt:
    pkg:
      - arptables
      - ebtables
      - containerd
    update_cache: no
    install_recommends: no
    autoremove: yes
    autoclean: yes

- name: Update alternatives
  alternatives:
    name: &amp;#34;{{ item.name }}&amp;#34;
    path: &amp;#34;{{ item.path }}&amp;#34;
  with_items:
    - { name: &amp;#39;iptables&amp;#39;, path: &amp;#39;/usr/sbin/iptables-legacy&amp;#39; }
    - { name: &amp;#39;ip6tables&amp;#39;, path: &amp;#39;/usr/sbin/ip6tables-legacy&amp;#39; }
    - { name: &amp;#39;arptables&amp;#39;, path: &amp;#39;/usr/sbin/arptables-legacy&amp;#39; }
    - { name: &amp;#39;ebtables&amp;#39;, path: &amp;#39;/usr/sbin/ebtables-legacy&amp;#39; }

- name: Turn off swap
  command: swapoff -a
  
- name: Turn off swap forever
  lineinfile:
    path: /etc/fstab
    regexp: &amp;#34;^([^#].*none\\s+swap\\s+sw.*)$&amp;#34;
    line: &amp;#34;#\\g&amp;lt;1&amp;gt;&amp;#34;
    backrefs: yes

- name: Ensure directory for apt keyrings exists
  ansible.builtin.file:
    path: /etc/apt/keyrings
    state: directory
    mode: &amp;#39;0755&amp;#39;

- name: Download Kubernetes GPG key
  ansible.builtin.get_url:
    url: https://pkgs.k8s.io/core:/stable:/v1.32/deb/Release.key
    dest: /tmp/kubernetes-key.gpg
    mode: &amp;#39;0644&amp;#39;
    force: no  # 幂等性

- name: Convert and install the key
  community.gpg.gpg_convert:
    executable: gpg
    key: /tmp/kubernetes-key.gpg
    keyring: /etc/apt/keyrings/kubernetes-apt-keyring.gpg
    state: present

- name: Clean up temporary key file
  ansible.builtin.file:
    path: /tmp/kubernetes-key.gpg
    state: absent

- name: Add repository of kubernetes
  apt_repository:
    repo: &amp;#34;deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.32/deb/ /&amp;#34;
    state: presen

- name: Actually install kubernetes
  apt:
    pkg:
      - kubelet
      - kubeadm
      - kubectl
    state: latest
    update_cache: yes
    install_recommends: no

- name: Hold kubernetes version
  dpkg_selections:
    name: &amp;#34;{{ item.name }}&amp;#34;
    selection: hold
  with_items:
    - { name: &amp;#39;kubelet&amp;#39; }
    - { name: &amp;#39;kubeadm&amp;#39; }
    - { name: &amp;#39;kubectl&amp;#39; }

- name: Get kubernetes images
  shell: kubeadm config images pull
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&#34;安装后配置&#34;&gt;安装后配置&lt;/h2&gt;
&lt;p&gt;以下都是在root用户下操作，故略去sudo。&lt;/p&gt;
&lt;h3 id=&#34;配置containerd和kubelet&#34;&gt;配置containerd和kubelet&lt;/h3&gt;
&lt;p&gt;先检查安装是否成功：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;kubectl cluster-info
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;然后确保containerd和kubelet服务已经正常启动&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;systemctl status containerd
systemctl status kubelet
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;检查containerd的配置文件，启用systemd的cgroup：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;# 如无则创建containerd配置文件，如有则略过
sudo mkdir -p /etc/containerd
containerd config default | tee /etc/containerd/config.toml &amp;gt; /dev/null
# 编辑配置文件，找到 [plugins.&amp;#34;io.containerd.grpc.v1.cri&amp;#34;.containerd.runtimes.runc.options] 部分
# 将 SystemdCgroup = false 修改为 SystemdCgroup = true
sed -i &amp;#39;s/SystemdCgroup = false/SystemdCgroup = true/&amp;#39; /etc/containerd/config.toml
# 重启 containerd
systemctl restart containerd
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;配置modules-load和sysctl参数：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;cat &amp;lt;&amp;lt;EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

# 设置所需的 sysctl 参数，这些参数在重启后仍然有效
cat &amp;lt;&amp;lt;EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF

# 应用 sysctl 参数设置，无需重启
sudo sysctl --system
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&#34;初始化control-plane&#34;&gt;初始化Control Plane&lt;/h3&gt;
&lt;p&gt;开始安装Control Plane&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;kubeadm init --pod-network-cidr 10.244.0.0/16 --apiserver-advertise-address=10.0.0.123 --kubernetes-version=v1.32.2
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其中的pod-network-cidr是Flannel的网段，如果使用不同的网络插件，要就指定不同的网段。&lt;/p&gt;
&lt;p&gt;apiserver-advertise-address是服务器的地址，不指定的话可能会导致服务启动失败。&lt;/p&gt;
&lt;p&gt;kubernetes-version是指定k8s的版本，以确保版本正确。&lt;/p&gt;
&lt;p&gt;初始化完成后返回这么一段内容：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run &amp;#34;kubectl apply -f [podnetwork].yaml&amp;#34; with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

  kubeadm join &amp;lt;control-plane-host&amp;gt;:&amp;lt;control-plane-port&amp;gt; --token &amp;lt;token&amp;gt; --discovery-token-ca-cert-hash sha256:&amp;lt;hash&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;其中包含几个重要信息：&lt;/p&gt;
&lt;p&gt;第一：如果要用非root用户操作kubectl，则需要在用户目录下创建.kube/config&lt;/p&gt;
&lt;p&gt;第二：如果要在集群里部署应用，则使用kubectl apply命令&lt;/p&gt;
&lt;p&gt;第三：如果有其它节点要加入这个集群，则使用kubeadm join命令，参数中的token和hash要注意安全保存，如果忘记记录token，可以用&lt;code&gt;kubeadm token list&lt;/code&gt;查看&lt;/p&gt;
&lt;p&gt;第四：token的有效期只有24小时，过期后需要重新创建&lt;code&gt;kubeadm token create&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;初始化完成后可以看看当前的状态：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;kubectl get nodes
kubectl get pods -A
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;正常情况下是刚开始的状态为Ready但很快变成NotReady，因为还没有安装Flannel网络。&lt;/p&gt;
&lt;h3 id=&#34;安装网络&#34;&gt;安装网络&lt;/h3&gt;
&lt;p&gt;还是安装常用的Flannel，路径与原来有所不同：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;现在再查看状态应该就是Ready了。&lt;/p&gt;
&lt;h3 id=&#34;将control-plane作为worker-node&#34;&gt;将control plane作为worker node&lt;/h3&gt;
&lt;p&gt;默认情况下control plane不作为worker node，所以k8s集群至少需要2台机器，但实际上也可以用一台机器跑，那就是将control plane也同时配置为worker node。&lt;/p&gt;
&lt;p&gt;首先查看一下control plane节点的污点状态：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;kubectl describe node &amp;lt;control-plane-node-name&amp;gt; | grep Taint
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以看到类似‘NoSchedule’这样的污点，表示不能在这里调试普通pod。将其移除：&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;kubectl taint nodes &amp;lt;control-plane-node-name&amp;gt; node-role.kubernetes.io/control-plane:NoSchedule-
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;之后就可以在这个节点上部署pod了。&lt;/p&gt;
&lt;p style=&#34;text-align: right&#34;&gt;推送到&lt;a href=&#34;http://www.go4pro.org/&#34;&gt;[go4pro.org]&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>

