<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Yu-Chen Cheng</title><description>Yu-Chen Cheng&apos;s Blog</description><link>https://rudeigerc.dev/</link><language>zh-Hans</language><lastBuildDate>Sun, 22 Mar 2026 15:50:24 GMT</lastBuildDate><generator>@astrojs/rss</generator><copyright>© 2026 Yu-Chen Cheng. This site is licensed under a Creative Commons Attribution 4.0 International license.</copyright><follow_challenge><feedId>117292072638883840</feedId><userId>55130729149045760</userId></follow_challenge><item><title>Introduction to LLM Inference Benchmarking</title><link>https://rudeigerc.dev/posts/en/llm-inference-benchmarking/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/en/llm-inference-benchmarking/</guid><description>Provide a comprehensive analysis of the theoretical foundations and engineering practices of LLM inference benchmarking, including a comparison of mainstream open-source benchmarking frameworks.</description><pubDate>Thu, 28 Aug 2025 14:57:03 GMT</pubDate><content:encoded>&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;With the widespread adoption of Large Language Models (LLMs) in applications, LLM inference services have emerged as core components of modern artificial intelligence infrastructure. In production environments, LLM inference services encounter heterogeneous workloads, where different application scenarios have vastly different requirements for SLOs, or service level objectives, such as latency and throughput. For instance, &lt;em&gt;real-time chat applications&lt;/em&gt; require low Time to First Token (TTFT) and Time Per Output Token (TPOT), &lt;em&gt;Retrieval-Augmented Generation (RAG)&lt;/em&gt; scenarios can be relatively more lenient, while &lt;em&gt;offline batch inference&lt;/em&gt; focuses more on end-to-end throughput.&lt;/p&gt;
&lt;p&gt;Inference backends such as vLLM and SGLang have developed specialized benchmarking tools to evaluate core inference performance and optimization effects. Meanwhile, orchestration layer solutions such as AIBrix and llm-d also provide end-to-end benchmarking solutions to verify whether overall system performance in complex production environments meets expected SLOs. Notably, Kubernetes Serving Workgroup has also proposed &lt;a href=&quot;https://github.com/kubernetes-sigs/inference-perf&quot;&gt;kubernetes-sigs/inference-perf&lt;/a&gt;, aiming to establish a standardized performance evaluation framework for LLM inference services.&lt;/p&gt;
&lt;p&gt;This post will analyze the theoretical foundations and engineering practices of LLM inference benchmarking, providing a deep understanding of core principles and the main LLM inference benchmarking solutions currently available in the open-source community, offering practical guidance for performance evaluation of LLM inference services in production environments.&lt;/p&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;h3&gt;Benchmark vs. Inference Benchmark&lt;/h3&gt;
&lt;p&gt;To accurately understand the scope of discussion in this post, we need to clearly distinguish between two different types of LLM benchmarking:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LLM Benchmark&lt;/strong&gt;: LLM benchmarks focus on evaluating the capability performance of LLM models themselves, using standardized task suites (such as GLUE, MMLU, SWE-bench, etc.) to measure model accuracy and reliability across dimensions like language understanding, reasoning, code generation, and mathematical computation. This process is also called &lt;em&gt;model evaluation&lt;/em&gt;, primarily concerned with the quality of model outputs rather than the efficiency of the inference process, which is outside the scope of this post.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;LLM Inference Benchmark&lt;/strong&gt;: LLM inference benchmarks focus on benchmarking the performance of LLM inference services in real deployment environments, including key metrics such as latency, throughput, resource utilization, and stability. &lt;strong&gt;They focus on how to efficiently run models.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;Goals and Non-Goals&lt;/h3&gt;
&lt;h4&gt;Goals&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Understand the performance boundaries and bottleneck characteristics of inference services or hardware platforms.&lt;/li&gt;
&lt;li&gt;Quantitatively evaluate the actual effects of inference service optimization measures.&lt;/li&gt;
&lt;li&gt;Provide data support for capacity planning and resource configuration in production environments.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Non-Goals&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Evaluate the accuracy or quality of model responses in specific scenarios (Should be handled through LLM benchmarks).&lt;/li&gt;
&lt;li&gt;Provide real-time performance monitoring in production environments (Should be implemented through observability infrastructure like Prometheus and Grafana).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Metrics&lt;/h2&gt;
&lt;p&gt;LLM inference benchmarking involves multiple dimensions of performance metrics, which form the core for evaluating inference service performance.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Calculation Method&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;TTFT&lt;/td&gt;
&lt;td&gt;Time to generate the first output token&lt;/td&gt;
&lt;td&gt;Time to get first token - Start time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;TPOT&lt;/td&gt;
&lt;td&gt;Average time to generate two consecutive tokens&lt;/td&gt;
&lt;td&gt;(E2E latency - TTFT) / (Output tokens - 1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;E2E latency&lt;/td&gt;
&lt;td&gt;Complete latency from request submission to receiving full response&lt;/td&gt;
&lt;td&gt;End time - Start time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;Output Latency&lt;/td&gt;
&lt;td&gt;Output latency from request submission to receiving the first token&lt;/td&gt;
&lt;td&gt;E2E latency - TTFT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throughput&lt;/td&gt;
&lt;td&gt;Input Throughput&lt;/td&gt;
&lt;td&gt;Throughput during input processing&lt;/td&gt;
&lt;td&gt;Input tokens / TTFT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Throughput&lt;/td&gt;
&lt;td&gt;Output Throughput&lt;/td&gt;
&lt;td&gt;Throughput during output generation&lt;/td&gt;
&lt;td&gt;(Output tokens - 1) / Output latency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requests&lt;/td&gt;
&lt;td&gt;Error Rate&lt;/td&gt;
&lt;td&gt;Probability of request errors&lt;/td&gt;
&lt;td&gt;Number of error requests / Total requests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requests&lt;/td&gt;
&lt;td&gt;Requests Per Second&lt;/td&gt;
&lt;td&gt;Number of requests processed per unit time&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Inference Benchmarking Framework&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;../../../assets/llm-inference-benchmarking/llm-inference-benchmark-overview.png&quot; alt=&quot;LLM Inference Benchmarking Framework Overview&quot; /&gt;&lt;/p&gt;
&lt;p&gt;LLM inference benchmarking frameworks generally consist of several key components working together to provide performance evaluation:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data Generator&lt;/strong&gt;: The dataset used in benchmarking, responsible for providing input data that represents realistic workloads. The quality and characteristics of this data directly impact the relevance of benchmark results to real-world scenarios.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Load Generator&lt;/strong&gt;: The workload patterns used in benchmarking, controlling the timing and distribution of request submissions to simulate various traffic patterns that might occur in production environments.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Client&lt;/strong&gt;: The component used to send requests to inference services and collect metrics from responses. This handles the actual communication with the inference service and captures timing and performance data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Report Generator&lt;/strong&gt;: Processes collected metrics to export or generate comprehensive performance reports that help stakeholders understand system behavior and make informed decisions.&lt;/p&gt;
&lt;p&gt;The workflow begins with the Data Generator creating or loading appropriate datasets. The Load Generator then controls request timing according to preset load patterns, while the Client handles actual request sending and response processing. Finally, the Report Generator aggregates and analyzes the collected performance data into insights.&lt;/p&gt;
&lt;h3&gt;Data Generator&lt;/h3&gt;
&lt;p&gt;The data generator is responsible for providing input datasets for benchmarking, and its quality directly affects the representativeness and practical value of test results. Different data sources are suitable for different testing objectives and application scenarios.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Public Datasets&lt;/strong&gt;: Using publicly available datasets is suitable for general performance evaluation, though there may be differences from production scenario workloads. ShareGPT, which contains extensive real user conversations with ChatGPT, represents one of the most commonly used datasets for benchmarking chat-based inference services.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Synthetic Data&lt;/strong&gt;: Artificially generated synthetic data allows for precise control over input and output length distributions according to specified parameters. This approach offers several distribution options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Constant: Generates requests with completely consistent input and output lengths, primarily used for testing system stability under specific configurations.&lt;/li&gt;
&lt;li&gt;Normal Distribution: Generates length sequences following a normal distribution with specified mean and standard deviation.&lt;/li&gt;
&lt;li&gt;Uniform Distribution: Uniformly generates length sequences between specified minimum and maximum values.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Logs&lt;/strong&gt;: Using real data from inference services as the data source facilitates replaying data distributions from specific time periods in production environments, providing highly realistic workload patterns.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Traces&lt;/strong&gt;: Several research projects have released trace datasets that provide valuable insights into real-world usage patterns:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kvcache-ai/Mooncake/tree/main/FAST25-release/traces&quot;&gt;Mooncake&lt;/a&gt; traces from &quot;&lt;a href=&quot;https://www.usenix.org/conference/fast25/presentation/qin&quot;&gt;Mooncake: Trading More Storage for Less Computation — A KVCache-centric Architecture for Serving LLM Chatbot&lt;/a&gt;&quot; (FAST 2025) include timestamp, input/output lengths, and hash identifiers for cache analysis.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Azure/AzurePublicDataset/blob/master/data/AzureLLMInferenceTrace_conv.csv&quot;&gt;AzurePublicDataset&lt;/a&gt; from &quot;&lt;a href=&quot;https://arxiv.org/abs/2408.00741&quot;&gt;DynamoLLM: Designing LLM Inference Clusters for Performance and Energy Efficiency&lt;/a&gt;&quot; (HPCA 2025) provides CSV format with timestamps, context tokens and generated tokens.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alibaba-edu/qwen-bailian-usagetraces-anon&quot;&gt;Aliyun Bailian&lt;/a&gt; traces from &quot;&lt;a href=&quot;https://arxiv.org/abs/2506.02634&quot;&gt;KVCache Cache in the Wild: Characterizing and Optimizing KVCache Cache at a Large Cloud Provider&lt;/a&gt;&quot; (USENIX ATC 2025) offer detailed conversation structure with chat hierarchies and content hashing&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;chat_id&quot;: 159,                                   // Randomized chat identifier
  &quot;parent_chat_id&quot;: 55,                             // -1 for root requests
  &quot;timestamp&quot;: 61.114,                              // Seconds since request arrive
  &quot;input_length&quot;: 521,                              // Input token count
  &quot;output_length&quot;: 132,                             // Output token count
  &quot;type&quot;: &quot;text&quot;,                                   // Request type: text/search/image/file
  &quot;turn&quot;: 2,                                        // Conversation turn number
  &quot;hash_ids&quot;: [1089, 1090, 1091, 6326, ..., 13148]  // Salted SipHash blocks (16 tokens per block)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Load Generator&lt;/h3&gt;
&lt;p&gt;The load generator is responsible for controlling request sending timing and needs to simulate various load characteristics. The generated load will affect the accuracy and practical applicability of benchmark results.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Distribution Patterns&lt;/strong&gt;: Distribution patterns serve various testing objectives:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Constant: Sends requests at fixed time intervals, providing stable test results but unable to reflect workload heterogeneity and burstiness that characterize real-world usage.&lt;/li&gt;
&lt;li&gt;Poisson: Sends requests according to a Poisson distribution, more closely approximating real user behavior&apos;s random request arrival patterns and better simulating heterogeneous workloads.&lt;/li&gt;
&lt;li&gt;Sweep: Gradually increases load intensity within a specified request rate range according to preset steps, facilitating the discovery of system performance boundaries and capacity limits.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Traces&lt;/strong&gt;: Similar to the Dataset component, if traces provide request timestamps, they can also be used to form realistic workload patterns that replay historical traffic patterns.&lt;/p&gt;
&lt;p&gt;Beyond these distribution patterns, some benchmarking frameworks introduce fluctuation mechanisms that overlay variations on base workloads. This enables a more realistic reflection of uncertainties present in production environments.&lt;/p&gt;
&lt;h3&gt;Client&lt;/h3&gt;
&lt;p&gt;Benchmarking frameworks require clients to send requests and calculate and collect specific metrics based on returned data. Since mainstream LLM inference frameworks like vLLM and SGLang implement OpenAI API-compatible interfaces, most LLM inference benchmarking frameworks commonly adopt OpenAI API-compatible client implementations, simplifying the development and maintenance costs of benchmarking frameworks.&lt;/p&gt;
&lt;h4&gt;OpenAI API Integration&lt;/h4&gt;
&lt;p&gt;The OpenAI API provides structured responses that facilitate accurate metric collection:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://platform.openai.com/docs/api-reference/chat/create
{
  &quot;id&quot;: &quot;chatcmpl-B9MBs8CjcvOU2jLn4n570S5qMJKcT&quot;,
  &quot;object&quot;: &quot;chat.completion&quot;,
  &quot;created&quot;: 1741569952,
  &quot;model&quot;: &quot;gpt-4.1-2025-04-14&quot;,
  &quot;choices&quot;: [
    {
      &quot;index&quot;: 0,
      &quot;message&quot;: {
        &quot;role&quot;: &quot;assistant&quot;,
        &quot;content&quot;: &quot;Hello! How can I assist you today?&quot;,
        &quot;refusal&quot;: null,
        &quot;annotations&quot;: []
      },
      &quot;logprobs&quot;: null,
      &quot;finish_reason&quot;: &quot;stop&quot;
    }
  ],
  &quot;usage&quot;: {
    &quot;prompt_tokens&quot;: 19,
    &quot;completion_tokens&quot;: 10,
    &quot;total_tokens&quot;: 29,
    &quot;prompt_tokens_details&quot;: {
      &quot;cached_tokens&quot;: 0,
      &quot;audio_tokens&quot;: 0
    },
    &quot;completion_tokens_details&quot;: {
      &quot;reasoning_tokens&quot;: 0,
      &quot;audio_tokens&quot;: 0,
      &quot;accepted_prediction_tokens&quot;: 0,
      &quot;rejected_prediction_tokens&quot;: 0
    }
  },
  &quot;service_tier&quot;: &quot;default&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For inference services compatible with the OpenAI API, the interface definition provides usage statistics to obtain detailed token usage information, facilitating accurate calculation of key metrics. When inference services don&apos;t return usage information, the benchmarking client can use the model&apos;s corresponding tokenizer to approximately calculate token counts based on text content, ensuring calculation result consistency.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://platform.openai.com/docs/api-reference/chat_streaming/streaming
{&quot;id&quot;:&quot;chatcmpl-123&quot;,&quot;object&quot;:&quot;chat.completion.chunk&quot;,&quot;created&quot;:1694268190,&quot;model&quot;:&quot;gpt-4o-mini&quot;, &quot;system_fingerprint&quot;: &quot;fp_44709d6fcb&quot;, &quot;choices&quot;:[{&quot;index&quot;:0,&quot;delta&quot;:{&quot;role&quot;:&quot;assistant&quot;,&quot;content&quot;:&quot;&quot;},&quot;logprobs&quot;:null,&quot;finish_reason&quot;:null}]}
{&quot;id&quot;:&quot;chatcmpl-123&quot;,&quot;object&quot;:&quot;chat.completion.chunk&quot;,&quot;created&quot;:1694268190,&quot;model&quot;:&quot;gpt-4o-mini&quot;, &quot;system_fingerprint&quot;: &quot;fp_44709d6fcb&quot;, &quot;choices&quot;:[{&quot;index&quot;:0,&quot;delta&quot;:{&quot;content&quot;:&quot;Hello&quot;},&quot;logprobs&quot;:null,&quot;finish_reason&quot;:null}]}
{&quot;id&quot;:&quot;chatcmpl-123&quot;,&quot;object&quot;:&quot;chat.completion.chunk&quot;,&quot;created&quot;:1694268190,&quot;model&quot;:&quot;gpt-4o-mini&quot;, &quot;system_fingerprint&quot;: &quot;fp_44709d6fcb&quot;, &quot;choices&quot;:[{&quot;index&quot;:0,&quot;delta&quot;:{},&quot;logprobs&quot;:null,&quot;finish_reason&quot;:&quot;stop&quot;}]}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When using streaming APIs, inference services progressively send generated text fragments and use the final chunk to mark generation completion, providing token usage statistics (which may require separate implementation depending on inference service implementation).&lt;/p&gt;
&lt;h3&gt;Report Generator&lt;/h3&gt;
&lt;p&gt;The report generator aggregates and analyzes collected performance data, generating visualized reports that help users understand system performance characteristics and potential issues. Effective report generation transforms raw performance metrics into actionable insights that can guide optimization decisions and capacity planning efforts.&lt;/p&gt;
&lt;h2&gt;Comparison&lt;/h2&gt;
&lt;h3&gt;Frameworks&lt;/h3&gt;
&lt;p&gt;Several major frameworks have emerged in the LLM inference benchmarking landscape, each with distinct strengths and focus areas:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;sglang/genai-bench&lt;/strong&gt;: A token-level performance evaluation benchmarking tool from the SGLang community that provides detailed model service performance insights with user-friendly CLI tools and real-time UI interfaces for progress monitoring.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;vllm-project/guidellm&lt;/strong&gt;: A benchmarking tool that evaluates LLM deployment performance and resource requirements across different hardware configurations by simulating realistic inference workloads, open-sourced by Neural Magic (acquired by Red Hat) and later joined by the vLLM community.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;kubernetes-sigs/inference-perf&lt;/strong&gt;: A GenAI inference performance benchmarking tool that offers high scalability and supports benchmarking large-scale production environment inference deployments, originating from the Kubernetes Serving Workgroup.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;fmperf-project/fmperf&lt;/strong&gt;: A Python-based cloud-native benchmarking tool specifically open-sourced by IBM. It is designed to evaluate LLM inference service framework performance and energy consumption.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AIBrix Benchmark&lt;/strong&gt;: A benchmarking framework provided by the AIBrix project, specifically designed to evaluate AIBrix cloud-native LLM inference infrastructure performance, though it can also be used for individual LLM inference services.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NVIDIA TRI genai-perf&lt;/strong&gt;: A GenAI performance analyzer from NVIDIA Triton Inference Server, specifically designed to measure core metrics like throughput and latency for LLM inference services.&lt;/p&gt;
&lt;h3&gt;Comparison Overview&lt;/h3&gt;
&lt;p&gt;The comparison framework evaluates tools across several dimensions that matter most for practical deployment decisions:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data Generation&lt;/strong&gt;: The methods supported for generating or loading benchmark datasets, which affect how well the benchmark represents real-world usage patterns.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Load Generation&lt;/strong&gt;: The approaches supported for generating different workload patterns, crucial for testing system behavior under various traffic conditions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scenarios&lt;/strong&gt;: The supported scenarios such as text generation, RAG, embedding, and multimodal inference, which determine the tool&apos;s applicability to different use cases.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Supported Frameworks&lt;/strong&gt;: The inference frameworks that can be tested, affecting compatibility with existing infrastructure.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Metrics&lt;/strong&gt;: The performance metrics that can be collected and analyzed, determining the depth of performance insights available.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;../../../assets/llm-inference-benchmarking/llm-inference-benchmark-comparison-en.png&quot; alt=&quot;LLM Inference Benchmarking Framework Comparison&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Summary&lt;/h3&gt;
&lt;p&gt;Existing LLM inference benchmarking solutions have achieved relatively mature standards in core metric collection, with key metrics like TTFT and TPOT reaching basic consensus in definition and calculation methods. Mainstream solutions including &lt;a href=&quot;https://github.com/sglang/genai-bench&quot;&gt;genai-bench&lt;/a&gt; and &lt;a href=&quot;https://github.com/vllm-project/guidellm&quot;&gt;vllm-project/guidellm&lt;/a&gt; provide excellent support for OpenAI API-compatible inference services, simplifying benchmark deployment and maintenance costs.&lt;/p&gt;
&lt;p&gt;In terms of scenario support, text generation serves as the foundational functionality across all frameworks, while support for embedding, reranking, and multimodal scenarios varies among tools. For data generation support, most frameworks support HuggingFace datasets, file input, and synthetic data generation, but differ in additional features like trace-based replay capabilities. Regarding load generation patterns, constant and Poisson distributions represent mainstream supported modes, with some frameworks additionally supporting fluctuating loads and trace-based replay.&lt;/p&gt;
&lt;p&gt;Beyond the previously discussed content, two aspects deserve particular attention:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Harness&lt;/strong&gt;: &lt;a href=&quot;https://github.com/llm-d/llm-d-benchmark&quot;&gt;llm-d-benchmark&lt;/a&gt; from &lt;a href=&quot;https://github.com/llm-d&quot;&gt;llm-d&lt;/a&gt; integrates inference-perf, fmperf, and guidellm as benchmarking execution tools, enabling unified report generation that consolidates results from these various tools.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Goodput&lt;/strong&gt;: Goodput refers to the actually effective workload, which in benchmarking scenarios can mean the throughput of requests meeting specific SLOs. Both AIBrix Benchmark and NVIDIA genai-perf support generating Goodput metrics based on specified SLOs when creating reports. Additionally, &lt;a href=&quot;https://github.com/vllm-project/guidellm/issues/197&quot;&gt;vllm-project/guidellm#197&lt;/a&gt; mentioned that when benchmarking new models or hardware, users might aim to explore maximum concurrency under specific SLO constraints, proposing sweep-like approaches using binary search to explore possible parameter spaces.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;With the widespread deployment of LLMs across various applications, LLM inference benchmarking has become a critical component for ensuring service performance and stability. This post systematically reviews the theoretical foundations, core metrics, mainstream tools, and their comparisons in inference benchmarking, aiming to help readers understand how to use LLM inference benchmarking to evaluate LLM inference services.&lt;/p&gt;
&lt;p&gt;As model scales and application scenarios continue expanding, inference benchmarking tools and methodologies will continue evolving. We recommend that developers flexibly select appropriate benchmarking solutions based on actual business requirements, providing more precise data support for LLMOps infrastructure performance optimization, and collaborate with production environment monitoring systems to continuously improve LLM inference service performance and reliability.&lt;/p&gt;
&lt;h2&gt;Appendix&lt;/h2&gt;
&lt;h3&gt;Related Posts&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://bentoml.com/llm/inference-optimization/llm-inference-metrics&quot;&gt;Key metrics for LLM inference | LLM Inference Handbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bentoml.com/llm/inference-optimization/llm-performance-benchmarks&quot;&gt;LLM performance benchmarks | LLM Inference Handbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.nvidia.com/blog/llm-benchmarking-fundamental-concepts/&quot;&gt;LLM Inference Benchmarking: Fundamental Concepts | NVIDIA Technical Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.nvidia.com/blog/llm-performance-benchmarking-measuring-nvidia-nim-performance-with-genai-perf/&quot;&gt;LLM Inference Benchmarking Guide: NVIDIA GenAI-Perf and NIM | NVIDIA Technical Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.nvidia.com/blog/llm-inference-benchmarking-how-much-does-your-llm-inference-cost/&quot;&gt;LLM Inference Benchmarking: How Much Does Your LLM Inference Cost? | NVIDIA Technical Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.nvidia.com/blog/llm-inference-benchmarking-performance-tuning-with-tensorrt-llm/&quot;&gt;LLM Inference Benchmarking: Performance Tuning with TensorRT-LLM | NVIDIA Technical Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.nvidia.com/nim/benchmarking/llm/latest/overview.html&quot;&gt;Overview — NVIDIA NIM LLMs Benchmarking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.bentoml.com/blog/benchmarking-llm-inference-backends&quot;&gt;Benchmarking LLM Inference Backends&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.anyscale.com/blog/reproducible-performance-metrics-for-llm-inference&quot;&gt;Reproducible Performance Metrics for LLM inference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.databricks.com/blog/llm-inference-performance-engineering-best-practices&quot;&gt;LLM Inference Performance Engineering: Best Practices | Databricks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Related Projects&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/sglang/genai-bench&quot;&gt;sglang/genai-bench&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vllm-project/guidellm&quot;&gt;vllm-project/guidellm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/llm-d/llm-d-benchmark&quot;&gt;llm-d/llm-d-benchmark&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kubernetes-sigs/inference-perf&quot;&gt;kubernetes-sigs/inference-perf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vllm-project/aibrix/tree/main/benchmarks&quot;&gt;AIBrix Benchmark&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/triton-inference-server/perf_analyzer/tree/main/genai-perf&quot;&gt;NVIDIA TRI genai-perf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/huggingface/inference-benchmarker&quot;&gt;huggingface/inference-benchmarker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ray-project/llmperf&quot;&gt;ray-project/llmperf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/fw-ai/benchmark&quot;&gt;fw-ai/benchmark&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ninehills/llm-inference-benchmark&quot;&gt;ninehills/llm-inference-benchmark&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>LLM</category></item><item><title>浅谈 LLM 推理基准测试</title><link>https://rudeigerc.dev/posts/llm-inference-benchmarking/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/llm-inference-benchmarking/</guid><description>全面分析了 LLM 推理基准测试的理论基础与工程实践，介绍了核心性能指标、主流开源基准测试工具及其对比，为生产环境中的 LLM 推理服务性能评估提供实用指南。</description><pubDate>Thu, 28 Aug 2025 04:37:03 GMT</pubDate><content:encoded>&lt;h2&gt;概览&lt;/h2&gt;
&lt;p&gt;随着大语言模型（LLM）的广泛应用，LLM 的推理服务已成为现代人工智能基础设施的核心组件。在生产环境中，LLM 推理服务所面对的工作负载呈现出高度的异构性特征，不同的应用场景对延迟、吞吐量等指标反映的服务质量的要求差异巨大，如实时聊天要求较低的 TTFT 与 TPOT，RAG 的场景会相对更宽松，而批量推理则会更关注整体的端到端吞吐量。&lt;/p&gt;
&lt;p&gt;目前，推理引擎层面的工具如 vLLM 和 SGLang 各自开发了专门的基准测试工具来评估核心推理性能和优化效果。同时，同时，编排层的解决方案如 AIBrix、llm-d 等也提供了端到端的基准测试方案，用于验证复杂生产环境下的整体系统性能是否满足预期的服务等级目标（SLO）。值得注意的是，Kubernetes 的 Serving Workgroup 也提出了 &lt;a href=&quot;https://github.com/kubernetes-sigs/inference-perf&quot;&gt;kubernetes-sigs/inference-perf&lt;/a&gt; 项目，旨在为 LLM 推理服务建立标准化的性能评估框架。&lt;/p&gt;
&lt;p&gt;本文将分析 LLM 推理基准测试的理论基础与工程实践，深入理解核心原理以及当前开源社区中主要的 LLM 推理基准测试解决方案，为生产环境中的 LLM 推理服务性能评估提供实用指南。&lt;/p&gt;
&lt;h2&gt;背景&lt;/h2&gt;
&lt;h3&gt;Benchmark v.s. Inference Benchmark&lt;/h3&gt;
&lt;p&gt;为了准确理解本文的讨论范围，我们需要明确区分两类不同的 LLM 基准测试：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LLM 基准测试（LLM Benchmark）：该类测试专注于评估 LLM 模型本身的能力表现，通过标准化的任务集合（如 GLUE、MMLU、SWE-bench 等）来衡量模型在语言理解、推理、代码生成、数学计算等维度的准确性和可靠性。该过程也被称作模型评估（Evaluation），主要关注模型输出的质量而非推理过程的效率，LLM 基准测试的内容不在本文所涉及的范畴中。&lt;/li&gt;
&lt;li&gt;LLM 推理基准测试（LLM Inference Benchmark）：这类测试专注于评估 LLM 推理服务在实际部署环境中的性能表现，包括延迟、吞吐量、资源利用率、稳定性等工程指标，其关注的是如何高效地运行模型。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Goals and Non-Goals&lt;/h3&gt;
&lt;h4&gt;Goals&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;了解推理服务或硬件平台的性能边界和瓶颈特征&lt;/li&gt;
&lt;li&gt;量化评估推理服务优化措施的实际效果&lt;/li&gt;
&lt;li&gt;为生产环境的容量规划和资源配置提供数据支撑&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Non-Goals&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;评估特定场景下模型回答的准确性或质量（应使用 LLM 基准测试）&lt;/li&gt;
&lt;li&gt;提供生产环境中的实时性能监控（应通过 Prometheus、Grafana 等可观测性基础设施实现）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;指标&lt;/h2&gt;
&lt;p&gt;LLM 推理基准测试涉及多个维度的性能指标，这些指标构成了评估推理服务性能的核心。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;分类&lt;/th&gt;
&lt;th&gt;指标&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;th&gt;计算方法&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;延迟&lt;/td&gt;
&lt;td&gt;TTFT&lt;/td&gt;
&lt;td&gt;生成第一个输出Token的时间。&lt;/td&gt;
&lt;td&gt;获得第一个Token的时间 - 启动时间&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;延迟&lt;/td&gt;
&lt;td&gt;TPOT&lt;/td&gt;
&lt;td&gt;生成两个连续的Token的平均时间&lt;/td&gt;
&lt;td&gt;(端到端延迟 - TTFT) / (输出Token - 1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;延迟&lt;/td&gt;
&lt;td&gt;端到端延迟&lt;/td&gt;
&lt;td&gt;端到端的延迟，表示从提交请求到获取完整回答的时间。&lt;/td&gt;
&lt;td&gt;结束时间 - 开始时间&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;延迟&lt;/td&gt;
&lt;td&gt;输出延迟&lt;/td&gt;
&lt;td&gt;输出的延迟，表示从提交请求到获取第一个Token的时间。&lt;/td&gt;
&lt;td&gt;端到端延迟 - TTFT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;吞吐量&lt;/td&gt;
&lt;td&gt;输入吞吐量&lt;/td&gt;
&lt;td&gt;输入过程的吞吐量。&lt;/td&gt;
&lt;td&gt;输入Token / TTFT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;吞吐量&lt;/td&gt;
&lt;td&gt;输出吞吐量&lt;/td&gt;
&lt;td&gt;输出过程的吞吐量。&lt;/td&gt;
&lt;td&gt;(输出Token - 1) / 输出延迟&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;请求&lt;/td&gt;
&lt;td&gt;错误率&lt;/td&gt;
&lt;td&gt;请求发生错误的概率。&lt;/td&gt;
&lt;td&gt;错误请求数量 / 请求数量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;请求&lt;/td&gt;
&lt;td&gt;单位时间请求数&lt;/td&gt;
&lt;td&gt;单位时间内处理请求的数量。&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;推理基准测试框架&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://rudeigerc.dev/_astro/llm-inference-benchmark-overview.CVXU7Pt1_Z2cTLmA.webp&quot; alt=&quot;LLM 推理基准测试框架概览&quot; /&gt;&lt;/p&gt;
&lt;p&gt;LLM 推理服务的基准测试框架一般由以下几个部分构成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Data Generator：基准测试中使用的数据集；&lt;/li&gt;
&lt;li&gt;Load Generator：基准测试中的工作负载；&lt;/li&gt;
&lt;li&gt;Client：用于向推理服务发送请求并收集指标的客户端；&lt;/li&gt;
&lt;li&gt;Report Generator：根据收集的指标导出或生成报告。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;数据首先由 Data Generator 生成或加载，然后 Load Generator 根据预设的负载模式控制请求的发送时序，Client 负责实际的请求发送和响应处理，最后 Report Generator 汇总分析收集到的性能数据。&lt;/p&gt;
&lt;h3&gt;Data Generator&lt;/h3&gt;
&lt;p&gt;数据生成器负责为基准测试提供输入数据集，其质量直接影响测试结果的代表性和实用价值。不同的数据来源适用于不同的测试目标和应用场景。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Public Datasets：使用公开的数据集，适用于通用的性能评估，但可能与生产场景的工作负载存在差异。
&lt;ul&gt;
&lt;li&gt;ShareGPT：包含大量真实的用户与ChatGPT的对话记录。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Synthetic Data：人工生成的合成数据，根据文件与指定的分布生成特定输入与输出长度的数据集。
&lt;ul&gt;
&lt;li&gt;Constant：生成输入输出长度完全一致的请求，主要用于测试系统在特定配置下的稳定性能。&lt;/li&gt;
&lt;li&gt;Normal Distribution：根据指定的均值和标准差生成符合正态分布的长度序列。&lt;/li&gt;
&lt;li&gt;Uniform Distribution：在指定的最小值和最大值之间均匀生成长度序列。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Logs
&lt;ul&gt;
&lt;li&gt;基于推理服务的真实数据作为数据源，便于重放生产环境中特定时间段的数据分布。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Traces
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kvcache-ai/Mooncake/tree/main/FAST25-release/traces&quot;&gt;Mooncake&lt;/a&gt;. &lt;a href=&quot;https://www.usenix.org/conference/fast25/presentation/qin&quot;&gt;Mooncake: Trading More Storage for Less Computation — A KVCache-centric Architecture for Serving LLM Chatbot&lt;/a&gt;, FAST 2025&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;{&quot;timestamp&quot;: 0, &quot;input_length&quot;: 6758, &quot;output_length&quot;: 500, &quot;hash_ids&quot;: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]}
{&quot;timestamp&quot;: 0, &quot;input_length&quot;: 7322, &quot;output_length&quot;: 490, &quot;hash_ids&quot;: [0, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27]}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Azure/AzurePublicDataset/blob/master/data/AzureLLMInferenceTrace_conv.csv&quot;&gt;AzurePublicDataset&lt;/a&gt;. &lt;a href=&quot;https://arxiv.org/abs/2408.00741&quot;&gt;DynamoLLM: Designing LLM Inference Clusters for Performance and Energy Efficiency&lt;/a&gt;, HPCA 2025&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;TIMESTAMP,ContextTokens,GeneratedTokens
2023-11-16 18:15:46.6805900,374,44
2023-11-16 18:15:50.9951690,396,109
2023-11-16 18:15:51.2224670,879,55
2023-11-16 18:15:51.3910170,91,16
2023-11-16 18:15:52.5732450,91,16
2023-11-16 18:15:52.9921190,381,84
2023-11-16 18:15:54.4260870,1313,142
2023-11-16 18:15:54.9320210,388,84
2023-11-16 18:15:55.0176690,242,14
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/alibaba-edu/qwen-bailian-usagetraces-anon&quot;&gt;Aliyun Bailian&lt;/a&gt;. &lt;a href=&quot;https://arxiv.org/abs/2506.02634&quot;&gt;KVCache Cache in the Wild: Characterizing and Optimizing KVCache Cache at a Large Cloud Provider&lt;/a&gt;, USENIX ATC 2025&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;chat_id&quot;: 159,                                   // Randomized chat identifier
  &quot;parent_chat_id&quot;: 55,                             // -1 for root requests
  &quot;timestamp&quot;: 61.114,                              // Seconds since request arrive
  &quot;input_length&quot;: 521,                              // Input token count
  &quot;output_length&quot;: 132,                             // Output token count
  &quot;type&quot;: &quot;text&quot;,                                   // Request type: text/search/image/file
  &quot;turn&quot;: 2,                                        // Conversation turn number
  &quot;hash_ids&quot;: [1089, 1090, 1091, 6326, ..., 13148]  // Salted SipHash blocks (16 tokens per block)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Load Generator&lt;/h3&gt;
&lt;p&gt;负载生成器是基准测试框架中负责控制请求发送时序的组件，需要模拟各种负载特征，其会影响基准测试结果的准确性与实用性。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Distribution
&lt;ul&gt;
&lt;li&gt;Constant：按固定时间间隔发送请求，测试结果稳定但无法反应工作负载的异构性与突发性。&lt;/li&gt;
&lt;li&gt;Poisson：根据泊松分布发送请求，更接近真实用户行为的随机请求到达模式，能够更好地模拟异构的工作负载。&lt;/li&gt;
&lt;li&gt;Sweep：在指定的请求速率区间内按照预设步长逐步提升负载强度，便于寻找系统的性能边界。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Traces
&lt;ul&gt;
&lt;li&gt;与 Dataset 相同，如果 Trace 中提供请求的时间戳的话，也可根据时间戳形成工作负载。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;除了前述的几种分布以外，一些基准测试框架还引入了波动机制（Fluctuation），在基础的工作负载上叠加波动，能够更加真实地反映生产环境中的不确定性。&lt;/p&gt;
&lt;h3&gt;Client&lt;/h3&gt;
&lt;p&gt;基准测试框架需要基于客户端发送请求，并根据返回的数据计算和收集特定的指标。由于主流的 LLM 推理框架如 vLLM 与 SGLang 等都实现了兼容 OpenAI API 的接口，大部分的 LLM 推理基准测试框架普遍都采用兼容 OpenAI API 的客户端实现，简化了基准测试框架的开发与维护成本。&lt;/p&gt;
&lt;h4&gt;OpenAI&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// https://platform.openai.com/docs/api-reference/chat/create
{
  &quot;id&quot;: &quot;chatcmpl-B9MBs8CjcvOU2jLn4n570S5qMJKcT&quot;,
  &quot;object&quot;: &quot;chat.completion&quot;,
  &quot;created&quot;: 1741569952,
  &quot;model&quot;: &quot;gpt-4.1-2025-04-14&quot;,
  &quot;choices&quot;: [
    {
      &quot;index&quot;: 0,
      &quot;message&quot;: {
        &quot;role&quot;: &quot;assistant&quot;,
        &quot;content&quot;: &quot;Hello! How can I assist you today?&quot;,
        &quot;refusal&quot;: null,
        &quot;annotations&quot;: []
      },
      &quot;logprobs&quot;: null,
      &quot;finish_reason&quot;: &quot;stop&quot;
    }
  ],
  &quot;usage&quot;: {
    &quot;prompt_tokens&quot;: 19,
    &quot;completion_tokens&quot;: 10,
    &quot;total_tokens&quot;: 29,
    &quot;prompt_tokens_details&quot;: {
      &quot;cached_tokens&quot;: 0,
      &quot;audio_tokens&quot;: 0
    },
    &quot;completion_tokens_details&quot;: {
      &quot;reasoning_tokens&quot;: 0,
      &quot;audio_tokens&quot;: 0,
      &quot;accepted_prediction_tokens&quot;: 0,
      &quot;rejected_prediction_tokens&quot;: 0
    }
  },
  &quot;service_tier&quot;: &quot;default&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;针对兼容 OpenAI API 的推理服务，在其接口定义中提供了 usage 来获得详细的 Token 使用统计信息，便于计算准确的关键指标。当推理服务没有返回 usage的时候，可以在基准测试的客户端使用模型对应的 Tokenizer 根据文本内容近似计算 Token 的数量，确保计算结果的一致性。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://platform.openai.com/docs/api-reference/chat_streaming/streaming
{&quot;id&quot;:&quot;chatcmpl-123&quot;,&quot;object&quot;:&quot;chat.completion.chunk&quot;,&quot;created&quot;:1694268190,&quot;model&quot;:&quot;gpt-4o-mini&quot;, &quot;system_fingerprint&quot;: &quot;fp_44709d6fcb&quot;, &quot;choices&quot;:[{&quot;index&quot;:0,&quot;delta&quot;:{&quot;role&quot;:&quot;assistant&quot;,&quot;content&quot;:&quot;&quot;},&quot;logprobs&quot;:null,&quot;finish_reason&quot;:null}]}
{&quot;id&quot;:&quot;chatcmpl-123&quot;,&quot;object&quot;:&quot;chat.completion.chunk&quot;,&quot;created&quot;:1694268190,&quot;model&quot;:&quot;gpt-4o-mini&quot;, &quot;system_fingerprint&quot;: &quot;fp_44709d6fcb&quot;, &quot;choices&quot;:[{&quot;index&quot;:0,&quot;delta&quot;:{&quot;content&quot;:&quot;Hello&quot;},&quot;logprobs&quot;:null,&quot;finish_reason&quot;:null}]}
....
{&quot;id&quot;:&quot;chatcmpl-123&quot;,&quot;object&quot;:&quot;chat.completion.chunk&quot;,&quot;created&quot;:1694268190,&quot;model&quot;:&quot;gpt-4o-mini&quot;, &quot;system_fingerprint&quot;: &quot;fp_44709d6fcb&quot;, &quot;choices&quot;:[{&quot;index&quot;:0,&quot;delta&quot;:{},&quot;logprobs&quot;:null,&quot;finish_reason&quot;:&quot;stop&quot;}]}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在使用流式的 API 的时候，推理服务会逐段发送生成的文本片段，并使用最终的分片标记生成的结束，并提供 Token 使用统计信息（根据推理服务的实现可能需要单独处理）。&lt;/p&gt;
&lt;h3&gt;Report Generator&lt;/h3&gt;
&lt;p&gt;报告生成器汇总分析收集到的性能数据，生成可视化的报告，帮助用户理解系统的性能特征和潜在问题。&lt;/p&gt;
&lt;h2&gt;对比&lt;/h2&gt;
&lt;h3&gt;框架&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/sglang/genai-bench&quot;&gt;sglang/genai-bench&lt;/a&gt;：genai-bench 是 SGLang 社区开源的用于用于 LLM 推理服务的全面 Token 级别性能评估基准测试工具，它提供了详细的模型服务性能洞察、具备用户友好的 CLI 工具和实时的 UI 界面用于进度监控。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vllm-project/guidellm&quot;&gt;vllm-project/guidellm&lt;/a&gt;：guidellm 是由 Neural Magic（被 Red Hat 收购）开源的基准测试工具，后加入 vLLM 社区，其能够通过模拟真实的推理工作负载，评估在不同硬件配置上部署 LLM 的性能与资源需求。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kubernetes-sigs/inference-perf&quot;&gt;kubernetes-sigs/inference-perf&lt;/a&gt;：inference-perf 是一个源于 Kubernetes Serving Workgroup 的专门用于 GenAI 推理性能基准测试的工具，该工具具有高度的可扩展性，能够支持大规模生产环境推理部署的基准测试。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/fmperf-project/fmperf&quot;&gt;fmperf-project/fmperf&lt;/a&gt;：fmperf 是一个 IBM 开源的基于 Python 的云原生基准测试工具，专门用于评估 LLM 推理服务框架的性能和能耗。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vllm-project/aibrix/tree/main/benchmarks&quot;&gt;AIBrix Benchmark&lt;/a&gt;：AIBrix 项目提供的基准测试框架，专门用于评估 AIBrix 云原生 LLM 推理基础设施的性能，也可用于单独的 LLM 推理服务。&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/triton-inference-server/perf_analyzer/tree/main/genai-perf&quot;&gt;NVIDIA TRI genai-perf&lt;/a&gt;：genai-perf 是 NVIDIA Triton Inference Server 的 GenAI 性能分析器，专门用于测量 LLM 推理服务的吞吐量与延迟等核心指标。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;对比概览&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Data Generation：支持的数据生成的方式；&lt;/li&gt;
&lt;li&gt;Load Generation：支持的负载生成的方式；&lt;/li&gt;
&lt;li&gt;Scenarios：支持的场景，如文本、RAG 等；&lt;/li&gt;
&lt;li&gt;Supported Frameworks：支持的推理框架；&lt;/li&gt;
&lt;li&gt;Metrics：支持的指标。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://rudeigerc.dev/_astro/llm-inference-benchmark-comparison.CU4zZQQO_6fIUI.webp&quot; alt=&quot;LLM 推理基准测试框架对比&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;小结&lt;/h3&gt;
&lt;p&gt;现有的 LLM 推理基准测试解决方案在核心指标采集方面已形成相对成熟的标准，TTFT、TPOT 等关键指标的定义和计算方法基本达成共识。&lt;a href=&quot;https://github.com/sglang/genai-bench&quot;&gt;sglang/genai-bench&lt;/a&gt; 与 &lt;a href=&quot;https://github.com/vllm-project/guidellm&quot;&gt;vllm-project/guidellm&lt;/a&gt; 等主流工具对兼容 OpenAI API 的推理服务提供了良好支持，简化了基准测试的部署和维护成本。&lt;/p&gt;
&lt;p&gt;场景支持方面，文本生成是所有框架的基础功能，Embedding、Reranking 与多模态等场景的支持程度不一。数据生成支持方面，大多数框架都支持 HuggingFace 数据集、文件输入和合成数据生成，但在附加功能如基于 Trace 的重放方面存在差异。负载生成模式上，恒定与泊松分布是主流支持的模式，部分框架还支持带波动的负载和基于 Trace 的重放。&lt;/p&gt;
&lt;p&gt;除了前述内容以外，以下两点笔者认为相当值得关注：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Harness&lt;/strong&gt;：&lt;a href=&quot;https://github.com/llm-d&quot;&gt;llm-d&lt;/a&gt; 的 &lt;a href=&quot;https://github.com/llm-d/llm-d-benchmark&quot;&gt;llm-d-benchmark&lt;/a&gt; 集成了 inference-perf、fmperf 与 guidellm 作为实际运行基准测试的工具，并且可以统合前述工具的实验报告生成统一的报告。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Goodput&lt;/strong&gt;：Goodput 指的是实际有效的工作负载，在基准测试的场景中可以指满足特定 SLO 的请求的吞吐量。AIBrix Benchmark 与 NVIDIA genai-perf 都支持在生成报告的时候根据指定的 SLO 生成 Goodput。此外，&lt;a href=&quot;https://github.com/vllm-project/guidellm/issues/197&quot;&gt;vllm-project/guidellm#197&lt;/a&gt; 中提到了在对新的模型或硬件基准测试的时候，用户的目标可能是探究在满足特定 SLO 的前提下的最大并发数，因此提出了类似 Sweep 的方式基于二分搜索对可能的参数空间进行搜索。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;随着大语言模型在各类应用中的持续落地，LLM 推理基准测试已成为保障服务性能和稳定性的关键环节。本文系统梳理了推理基准测试的理论基础、核心指标、主流工具及其对比，旨在帮助读者理解如何使用 LLM 推理基准测试评估 LLM 推理服务。随着模型规模和应用场景的不断扩展，推理基准测试工具和方法也将持续演进。建议开发者结合实际业务需求，灵活选用合适的基准测试方案，为 LLMOps 基础设施的性能优化提供更精确的数据支撑，并与生产环境监控体系协同，持续提升 LLM 推理服务的性能和可靠性。&lt;/p&gt;
&lt;h2&gt;附录&lt;/h2&gt;
&lt;h3&gt;相关文章&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://bentoml.com/llm/inference-optimization/llm-inference-metrics&quot;&gt;Key metrics for LLM inference | LLM Inference Handbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://bentoml.com/llm/inference-optimization/llm-performance-benchmarks&quot;&gt;LLM performance benchmarks | LLM Inference Handbook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.nvidia.com/blog/llm-benchmarking-fundamental-concepts/&quot;&gt;LLM Inference Benchmarking: Fundamental Concepts | NVIDIA Technical Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.nvidia.com/blog/llm-performance-benchmarking-measuring-nvidia-nim-performance-with-genai-perf/&quot;&gt;LLM Inference Benchmarking Guide: NVIDIA GenAI-Perf and NIM | NVIDIA Technical Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.nvidia.com/blog/llm-inference-benchmarking-how-much-does-your-llm-inference-cost/&quot;&gt;LLM Inference Benchmarking: How Much Does Your LLM Inference Cost? | NVIDIA Technical Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.nvidia.com/blog/llm-inference-benchmarking-performance-tuning-with-tensorrt-llm/&quot;&gt;LLM Inference Benchmarking: Performance Tuning with TensorRT-LLM | NVIDIA Technical Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.nvidia.com/nim/benchmarking/llm/latest/overview.html&quot;&gt;Overview — NVIDIA NIM LLMs Benchmarking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.bentoml.com/blog/benchmarking-llm-inference-backends&quot;&gt;Benchmarking LLM Inference Backends&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.anyscale.com/blog/reproducible-performance-metrics-for-llm-inference&quot;&gt;Reproducible Performance Metrics for LLM inference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.databricks.com/blog/llm-inference-performance-engineering-best-practices&quot;&gt;LLM Inference Performance Engineering: Best Practices | Databricks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;相关项目&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/sglang/genai-bench&quot;&gt;sglang/genai-bench&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vllm-project/guidellm&quot;&gt;vllm-project/guidellm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/llm-d/llm-d-benchmark&quot;&gt;llm-d/llm-d-benchmark&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kubernetes-sigs/inference-perf&quot;&gt;kubernetes-sigs/inference-perf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vllm-project/aibrix/tree/main/benchmarks&quot;&gt;AIBrix Benchmark&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/triton-inference-server/perf_analyzer/tree/main/genai-perf&quot;&gt;NVIDIA TRI genai-perf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/huggingface/inference-benchmarker&quot;&gt;huggingface/inference-benchmarker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ray-project/llmperf&quot;&gt;ray-project/llmperf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/fw-ai/benchmark&quot;&gt;fw-ai/benchmark&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ninehills/llm-inference-benchmark&quot;&gt;ninehills/llm-inference-benchmark&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>LLM</category></item><item><title>“大模型”技术专栏 | 浅谈基于 Kubernetes 的 LLM 分布式推理框架架构：概览</title><link>https://ai.heywhale.com/article/833.html</link><guid isPermaLink="true">https://ai.heywhale.com/article/833.html</guid><description>本文围绕基于 Kubernetes 的大语言模型分布式推理框架架构进行介绍，包括目前 Kubernetes 社区主流的分布式推理解决方案以及其集成的学术界的相关工作，旨在分享目前基于 Kubernetes 的主流解决方案所解决的问题以及未来可能的发展方向。</description><pubDate>Thu, 14 Aug 2025 16:00:00 GMT</pubDate></item><item><title>Kubernetes-Based LLM Inference Architectures: An Overview</title><link>https://rudeigerc.dev/posts/kubernetes-based-llm-inference-architectures-an-overview/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/kubernetes-based-llm-inference-architectures-an-overview/</guid><description>As Large Language Models grow beyond single-machine capabilities, Kubernetes-based distributed inference has become essential for production deployments. This post explores the architecture of Kubernetes-based LLM inference frameworks, including features such as PD disaggregation, load balancing, KV cache management, and autoscaling.</description><pubDate>Thu, 14 Aug 2025 14:56:56 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;This post is the English transcript of my previous post. You can also read the &lt;a href=&quot;https://mp.weixin.qq.com/s/5Q2Rjg6YKs7V9kOL41eACQ&quot;&gt;Chinese version&lt;/a&gt; of this post on WeChat.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;As Large Language Models (LLMs) become widely deployed in production environments, efficient inference deployment has become a core challenge facing the industry. To address this challenge, both industry and academia are actively exploring various optimization solutions, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Multi-dimensional parallelism techniques:&lt;/strong&gt; Data Parallelism, Tensor Parallelism, Pipeline Parallelism, Expert Parallelism, etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Batch processing optimization:&lt;/strong&gt; Continuous Batching&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These technologies have significant optimization effects on LLM inference performance. However, with the continuous growth of model scale and the complexity of application scenarios, traditional single-machine deployment methods are no longer applicable, especially with the emergence of large-scale MoE (Mixture of Experts) models such as DeepSeek V3/R1 and Kimi K2. The compute resource requirements of these models have exceeded the capacity of single machines, resulting in new challenges for LLM inference.&lt;/p&gt;
&lt;p&gt;In this post, we will introduce the architecture of Kubernetes-based large language model distributed inference frameworks, including the current mainstream distributed inference solutions in the Kubernetes community and their integration of related academic work, aiming to share the problems solved by current mainstream Kubernetes-based solutions and possible future development directions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Please note that this post mainly focuses on the orchestration layer architectural design and does not involve specific optimization implementations within inference engines like vLLM and SGLang.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;Before introducing Kubernetes-based LLM distributed inference frameworks, we need to have a preliminary understanding of the LLM inference process.&lt;/p&gt;
&lt;h3&gt;LLM Inference&lt;/h3&gt;
&lt;h4&gt;Prefill and Decode&lt;/h4&gt;
&lt;p&gt;The inference of Transformer-based LLMs is mainly divided into two phases: &lt;strong&gt;Prefill&lt;/strong&gt; and &lt;strong&gt;Decode&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Prefill&lt;/strong&gt;: The prefill phase is the first step of the inference process. Its core task is to process the input prompts. In this phase, the model processes all tokens in the input prompt in parallel, computing the attention states of the entire input sequence at once. This process generates a set of key intermediate results, namely &lt;em&gt;key&lt;/em&gt; and &lt;em&gt;value&lt;/em&gt;, and stores them in the KV Cache. Since the prefill phase involves extensive matrix multiplication operations on the entire input sequence, it is a &lt;strong&gt;compute-bound&lt;/strong&gt; process.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Decode&lt;/strong&gt;: When the prefill phase is complete and generates the initial KV Cache, the process enters the decode phase and begins generating output tokens one by one. This is an auto-regressive process, meaning that for each newly generated token, it needs to be used as input along with all previous context (including the original prompt and generated tokens) to predict the next token. Unlike prefill, the decode phase is serial and cannot be processed in parallel. When generating each token, the main performance bottleneck lies in loading and reading massive model weight parameters from HBM, making this a &lt;strong&gt;memory-bound&lt;/strong&gt; process.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Metrics for LLM Inference&lt;/h3&gt;
&lt;p&gt;This section introduces key metrics for measuring LLM inference performance across different optimization scenarios:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Latency Metrics:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Time to First Token (TTFT)&lt;/strong&gt;: The time required to generate the first token after a request arrives, which is a metric for measuring prefill phase performance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Time Per Output Token (TPOT)&lt;/strong&gt;: The average time required to generate one output token, which is a metric for measuring decode phase performance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Latency (E2E Latency)&lt;/strong&gt;: End-to-end latency. &lt;code&gt;latency = TTFT + TPOT × number of tokens&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Throughput Metrics:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Throughput (Tokens Per Second)&lt;/strong&gt;: The number of tokens generated per unit time, i.e., end-to-end throughput.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Requests per second (RPS)&lt;/strong&gt;: The number of successful requests per unit time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition to the aforementioned common metrics widely used, there are some additional metrics, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Normalized Time Per Output Token (NTPOT)&lt;/strong&gt;: Normalized TPOT, calculated as NTPOT = latency / number of tokens.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inter-Token Latency (ITL)&lt;/strong&gt;: The latency between two token generations, differing from TPOT in that it measures the discrete values of time between token generations.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Review: Single-Machine LLM Deployment on Kubernetes&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://rudeigerc.dev/_astro/single-machine-llm-inference.CQxhJc8__Z15tR5o.webp&quot; alt=&quot;Single-Machine LLM Deployment on Kubernetes&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The diagram shows a common solution for deploying vLLM-based single-machine inference services in Kubernetes clusters, with autoscaling based on &lt;a href=&quot;https://keda.sh/&quot;&gt;KEDA&lt;/a&gt; (Pod-level autoscaling) and &lt;a href=&quot;https://karpenter.sh/&quot;&gt;Karpenter&lt;/a&gt; (node-level autoscaling), and Gateway handling routing and load balancing at the gateway level. This was the mainstream solution for deploying LLMs at the time. The models then required only a single machine with multi-GPU to meet inference needs, while multi-machine, multi-GPU deployment based on vLLM and Ray would cause significant performance degradation in decode phase tensor parallelism due to network infrastructure differences and limitations (such as network bandwidth becoming a performance bottleneck on 10Gbps Ethernet infrastructure). Therefore, adopting the aforementioned solution was actually a relatively reasonable choice, allowing the use of multi-dimensional autoscaling mechanisms to control deployment scale based on traffic to achieve better inference performance.&lt;/p&gt;
&lt;p&gt;However, with the emergence of MoE architecture models such as DeepSeek V3/R1 and Kimi K2, the deployment architecture mentioned above faces challenges. Larger parameters and sequence lengths, along with more complex usage scenarios, make single-machine GPU deployment methods no longer applicable. Communication inter and intra nodeswith different parallelism methods (depending on network topology) introduces non-negligible latency, causing significant degradation in multiple key metrics and affecting inference service quality.&lt;/p&gt;
&lt;p&gt;Therefore, engineers are turning to distributed inference. Kubernetes-based distributed inference solutions have experienced rapid development from late 2024 to the present, with major companies open-sourcing their proposed solutions in the community. How to utilize cluster orchestration capabilities to enable underlying inference frameworks to achieve better performance, such as service quality improvement and cost reduction, are goals that distributed inference frameworks need to achieve. The following text will provide an overall introduction to Kubernetes-based LLM distributed inference frameworks and how these frameworks organically combine industry experience with academic achievements to realize more efficient LLM inference.&lt;/p&gt;
&lt;h2&gt;Kubernetes-based LLM Distributed Inference Frameworks&lt;/h2&gt;
&lt;p&gt;Currently, the main Kubernetes-based LLM distributed inference frameworks or solutions in the open-source community include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://aibrix.readthedocs.io/&quot;&gt;AIBrix&lt;/a&gt; (vLLM / ByteDance)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.nvidia.com/dynamo&quot;&gt;Dynamo&lt;/a&gt; (NVIDIA)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://llm-d.ai/&quot;&gt;llm-d&lt;/a&gt; (Red Hat / Google / IBM)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/sgl-project/ome&quot;&gt;OME&lt;/a&gt; (SGLang / Oracle)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://rudeigerc.dev/_astro/llm-distributed-inference-architecture.B3N2rMNY_2gfzw7.webp&quot; alt=&quot;LLM Distributed Inference Framework Architecture&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Although the aforementioned projects are led by different companies, their focus points are similar. The diagram shows a relatively universal overview of LLM-based distributed inference frameworks, mainly consisting of the following components:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AI Gateway&lt;/strong&gt;: Provides OpenAI API-compatible interface API Server and Router responsible for request routing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inference Pool&lt;/strong&gt;: Responsible for workload deployment.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Autoscaler&lt;/strong&gt;: Responsible for autoscaling of workload nodes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Currently, Kubernetes-based distributed inference frameworks mainly focus on the following topics, which will be introduced separately below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PD Disaggregation&lt;/li&gt;
&lt;li&gt;Load Balancing&lt;/li&gt;
&lt;li&gt;KV Cache Management&lt;/li&gt;
&lt;li&gt;Auto-scaling&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Prefill-Decode Disaggregation&lt;/h3&gt;
&lt;p&gt;The process of LLM inference is divided into two phases, where prefill is compute-intensive and decode is memory-intensive, with completely different resource requirements. Generally, LLM inference engines adopt continuous batching to aggregate multiple requests for processing, which creates serious resource conflicts and efficiency bottlenecks. When executing compute-intensive prefill tasks, the system cannot simultaneously satisfy the high-frequency memory bandwidth requirements of decode tasks. On the other hand, when executing memory-intensive decode tasks, large amounts of computational resources remain idle. Additionally, this mixed execution mode causes blocking, where a compute-heavy prefill request blocks many subsequent decode steps that could be completed quickly, increasing waiting latency for other users and preventing continuous batching advantages from being fully utilized.&lt;/p&gt;
&lt;p&gt;PD Disaggregation (Prefill-Decode Disaggregation) is precisely an architectural optimization solution proposed to solve the above problems. This technology was initially proposed by academic works like Splitwise and DistServe. The core idea is to physically separate the prefill and decode phases, running them on different worker nodes. Engineers could adjust resource configurations for both types of services based on their respective resource usage characteristics to optimize different key metrics while avoiding mutual interference between the two phases&apos; workloads. Additionally, in specific work scenarios, heterogeneous hardware resources can even be applied to run prefill and decode workloads separately, making optimized use of different computational resource capabilities.&lt;/p&gt;
&lt;p&gt;However, &lt;strong&gt;PD disaggregation is not a silver bullet&lt;/strong&gt;. With PD disaggregation architecture, KV Cache from the prefill phase needs to be transmitted to the decode phase, introducing additional data transmission overhead. Considering the size of KV Cache, frequent data transmission between different services may bring significant latency, potentially offsetting the performance improvements brought by PD disaggregation. Additionally, PD disaggregation requires additional orchestration and scheduling logic to manage collaboration between prefill and decode services, as well as inter-service KV Cache management, significantly increasing system complexity. Therefore, PD disaggregation is more suitable for large-scale deployment inference scenarios, especially when requests are sufficiently heterogeneous (i.e., request patterns and resource requirements are highly differentiated), bringing better benefits. In edge scenarios like embodied intelligence, the benefits introduced may be limited or even negative. Currently, projects like AIBrix, Dynamo, and llm-d all provide PD disaggregation support based on different implementations of inference engines.&lt;/p&gt;
&lt;h3&gt;Routing and Load Balancing&lt;/h3&gt;
&lt;p&gt;In distributed inference scenarios, the Gateway needs to forward user requests to specific instances, which are model replicas running on different GPUs or server nodes. However, LLM load balancing is more complex than traditional stateless services, with core challenges stemming from the statefulness and heterogeneity of the inference process itself. The core of LLM inference is KV Cache, which stores keys and values computed by the model during the Prefill phase. Since various requests&apos; prompts may have overlapping prefixes, the shared prefixes can be reused by multiple requests, significantly improving inference efficiency. Therefore, load balancing needs to consider how to efficiently utilize these caches rather than simply randomly distributing requests to different instances. Projects like Dynamo and AIBrix extend load balancing functionality through plugin-based approaches, supporting flexible configuration of multiple load balancing algorithms at the gateway level.&lt;/p&gt;
&lt;h4&gt;Prefix-Aware&lt;/h4&gt;
&lt;p&gt;Prefix Caching is a technique that can cache and reuse KV Cache from request prefix portions. When multiple requests share the same prefix, the system can avoid repeated computation by directly reusing cached Keys and Values, significantly reducing TTFT and improving overall throughput. Inference engines such as vLLM and SGLang have implemented prefix caching functionality. In distributed inference environments, Prefix-aware load balancing strategies prioritize routing requests with similar prefixes to the same instance to maximize cache hit rates. This strategy requires load balancers to maintain cache state information for each instance and route based on request prefix characteristics.&lt;/p&gt;
&lt;h4&gt;Fairness&lt;/h4&gt;
&lt;p&gt;Fair scheduling of LLM instances is also an important aspect of load balancing, especially in multi-tenant environments, where fairness ensures all users receive relatively consistent service quality rather than experiencing delays due to certain instances being overloaded.&lt;/p&gt;
&lt;p&gt;AIBrix implements Virtual Token Counter (VTC) Fair Queuing scheduling strategy based on &lt;a href=&quot;https://www.usenix.org/conference/osdi24/presentation/sheng&quot;&gt;Sheng et al.&lt;/a&gt; VTC maintains a virtual Token counter for each client, achieving fair scheduling by tracking the amount of service each client has received, prioritizing clients with the smallest counter values.&lt;/p&gt;
&lt;h4&gt;Example: Ray Serve LLM&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;Source: &lt;a href=&quot;https://github.com/ray-project/ray/blob/master/python/ray/llm/_internal/serve/request_router/prefix_aware/prefix_aware_router.py&quot;&gt;Prefix Aware Router&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The Power of Two Choices is a classic algorithm in the load balancing field. Its main idea is quite simple: randomly select two from all available options, then choose the better one based on some criterion (in load balancing scenarios, the one with lower load). From a mathematical expectation perspective, this algorithm can reduce the worst-case scenario from $$O(log(n)/log(log(n)))$$ to $$O(log(log(n)))$$.&lt;/p&gt;
&lt;p&gt;Ray Serve LLM currently combines Power of Two Choices with Prefix-Aware:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When load is balanced (i.e., differences between Queues are less than a certain threshold), it selects the instance with the highest prefix match to the input;&lt;/li&gt;
&lt;li&gt;When load balancing and match rate is below 10%, it selects the instance with the lowest load;&lt;/li&gt;
&lt;li&gt;When load is unbalanced, it uses Power of Two Choices for selection.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This hybrid load balancing strategy can maintain effective KV Cache utilization while balancing loads across different instances as much as possible, thereby improving overall inference performance.&lt;/p&gt;
&lt;h3&gt;KV Cache Management&lt;/h3&gt;
&lt;p&gt;As mentioned earlier, KV Cache is designed to cache Key and Value values computed from previous tokens during the auto-regressive process, stored in GPU memory for use in subsequent steps. Through KV Cache, the inference process doesn&apos;t need to repeatedly compute KV values for previous tokens, significantly accelerating the inference process. However, as context length increases, KV Cache size grows linearly. Especially in large-scale inference scenarios, GPU memory is quickly consumed, making it impossible for inference services to utilize KV Cache capabilities, becoming a bottleneck in the inference process.&lt;/p&gt;
&lt;h4&gt;KV Cache Offloading&lt;/h4&gt;
&lt;p&gt;KV Cache Offloading refers to the process of unloading KV Cache from GPU memory to CPU memory or external storage, proposed to solve the aforementioned problem of KV Cache occupying GPU memory. When LLMs need to access offloaded KV Cache, these Blocks are reloaded into GPU memory on demand. AIBrix provides a multi-tier KV Cache caching framework, using DRAM L1 Cache by default, and can use L2 Cache (distributed external storage) when KV Cache sharing is needed. Frameworks like Dynamo and llm-d also support using frameworks like LMCache to offload infrequently used KV Cache to CPU memory and external storage.&lt;/p&gt;
&lt;h4&gt;KV Cache Sharing&lt;/h4&gt;
&lt;p&gt;With the introduction of KV Cache Offloading, how to share KV Cache among different LLM inference instances is also a widely researched issue.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Centralized&lt;/strong&gt;: Managing different instances&apos; KV Cache through a centralized KV Cache pool. The advantage is maximizing KV Cache sharing and better utilizing Prefix Caching capabilities, but it may become a single point of performance bottleneck in high-concurrency scenarios.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Peer-to-Peer&lt;/strong&gt;: Directly transmitting KV Cache between different instances through P2P communication mechanisms, avoiding centralized storage while providing better fault tolerance and dynamic scaling support.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Autoscaling&lt;/h3&gt;
&lt;p&gt;Currently, the main autoscaling tools widely used in the Kubernetes ecosystem include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HPA (Horizontal Pod Autoscaler)&lt;/strong&gt;: Kubernetes horizontal scaler&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KPA (Knative Pod Autoscaler)&lt;/strong&gt;: Knative horizontal scaler&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KEDA (Kubernetes Event-driven Autoscaling)&lt;/strong&gt;: Event-driven service scaler supporting zero-to-one deployment&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For autoscaling of LLM inference services, the key lies in determining metrics that trigger autoscaling. AIBrix speakers shared at vLLM Meetup Beijing that unlike traditional microservices auto-scaling, LLM inference request QPS may not be positively correlated with generated latency, and GPU metrics like SM Active may not reflect metric changes in a timely manner. Therefore, how to perform reliable auto-scaling based on LLM inference characteristics remains an issue requiring exploration.&lt;/p&gt;
&lt;p&gt;In the long term, I believe that autoscaling of LLM inference service may gradually evolve from &lt;em&gt;reactive&lt;/em&gt; to &lt;em&gt;proactive&lt;/em&gt; or even &lt;em&gt;predictive&lt;/em&gt; forms (such as time series analysis and reinforcement learning-based prediction).&lt;/p&gt;
&lt;h2&gt;Discussion&lt;/h2&gt;
&lt;h3&gt;Inference Engines&lt;/h3&gt;
&lt;p&gt;I began using vLLM to deploy LLMs at the end of 2023. Among the mainstream inference frameworks at the time, vLLM based on &lt;a href=&quot;https://arxiv.org/abs/2309.06180&quot;&gt;PagedAttention&lt;/a&gt; was at the forefront in terms of both performance and usability. Contemporary inference engines like Microsoft&apos;s DeepSpeed-MII and NVIDIA&apos;s TensorRT-LLM were not competitive opponents. It wasn&apos;t until the subsequent emergence of SGLang based on &lt;a href=&quot;https://arxiv.org/abs/2312.07104&quot;&gt;RadixAttention&lt;/a&gt; from UC Berkeley Sky Computing Lab that vLLM&apos;s monopolistic position was challenged. Currently, both vLLM and SGLang communities are developing rapidly.&lt;/p&gt;
&lt;p&gt;Projects like AIBrix and llm-d use vLLM as their first-class integrated inference engine. Dynamo additionally supports NVIDIA&apos;s TensorRT-LLM on top of the former two, while OME, as a framework open-sourced by the SGLang community, provides first-class support for SGLang.&lt;/p&gt;
&lt;p&gt;Orchestration frameworks tend to be compatible with more types of underlying inference engines, such as using &lt;a href=&quot;https://lws.sigs.k8s.io/&quot;&gt;LWS&lt;/a&gt; as an abstraction for stateful services (like llm-d and OME). LWS is a Kubernetes operator proposed by Kubernetes SIGs for encapsulating leader-worker StatefulSets, providing a standardized paradigm for running complex stateful applications like LLM inference on Kubernetes.&lt;/p&gt;
&lt;h3&gt;Kubernetes-based Distributed Inference&lt;/h3&gt;
&lt;p&gt;From the design of current frameworks like AIBrix and llm-d, moving load balancing and KV Cache management functionalities from the inference engine level up to the cluster orchestration level is the current mainstream trend. The orchestration side can better integrate with existing cluster resource management systems (like Kubernetes) ecosystems, avoiding duplicating functionality that already exists in the cluster ecosystem. Inference engines can focus on internal inference process optimization, only needing to expose specific abstractions and interfaces for orchestration-side integration.&lt;/p&gt;
&lt;p&gt;On the other hand, with the rise of LLMs, many projects in the Kubernetes ecosystem that previously focused on traditional MLOps are trying to move toward the LLM ecosystem. For example, Kubeflow sub-projects Trainer (Training Operator) and KServe are introducing first-class LLM support in their new versions, attempting to move toward LLMOps. This allows platform-side users to transition from MLOps to LLMOps at lower cost and integrate more conveniently with LLM ecosystems. Recently, the SGLang community also open-sourced OME in collaboration with Oracle. Besides inference service integration, OME also focuses on model management capabilities. Additionally, beyond the Kubernetes ecosystem, Ray recently also provides first-class support for LLM-related functionality based on Ray Data and Ray Serve.&lt;/p&gt;
&lt;h3&gt;From Distributed Inference to Disaggregated Inference&lt;/h3&gt;
&lt;p&gt;Besides the PD disaggregation and KV Cache management mentioned earlier, current infrastructure-layer-based Disaggregated Inference is also a widely discussed topic, involving modular separation of various inference modules for disaggregated deployment, with data transmission between them through protocols. Current related work in academia and industry is shown below, demonstrating possible future development directions for large-scale inference infrastructure:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prefill / Decode Disaggregation:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Splitwise: Efficient generative LLM inference using phase splitting, https://arxiv.org/abs/2311.18677&lt;/li&gt;
&lt;li&gt;DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving, https://arxiv.org/abs/2401.09670&lt;/li&gt;
&lt;li&gt;Mooncake: A KVCache-centric Disaggregated Architecture for LLM Serving, https://arxiv.org/abs/2407.00079&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;MoE Attention / FFN Disaggregation:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MegaScale-Infer: Serving Mixture-of-Experts at Scale with Disaggregated Expert Parallelism, https://arxiv.org/abs/2504.02263&lt;/li&gt;
&lt;li&gt;Step-3 is Large yet Affordable: Model-system Co-design for Cost-effective Decoding, https://arxiv.org/abs/2507.19427&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;MM Encode Disaggregation:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Efficiently Serving Large Multimodal Models Using EPD Disaggregation, https://arxiv.org/abs/2501.05460&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Takeaways&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PD disaggregation is the future of large-scale inference, but not a silver bullet:&lt;/strong&gt; Separating compute-heavy prefill from memory-heavy decode improves resource utilization but adds KV Cache transmission overhead and orchestration complexity. Best for large-scale, heterogeneous workloads.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stateful routing and load balancing is critical:&lt;/strong&gt; Unlike traditional stateless services, LLM routing must consider KV Cache state. Strategies like prefix-aware routing and fairness mechanisms maximize cache utilization.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-tier KV Cache management enables scale:&lt;/strong&gt; As context lengths grow, GPU memory becomes the bottleneck. Multi-tier caching strategies with intelligent offloading are essential, along with choosing between centralized vs peer-to-peer sharing models based on scale and fault tolerance requirements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The future points toward disaggregated inference:&lt;/strong&gt; Some intelligence is shifting from inference engines to orchestration frameworks, enabling better integration while allowing engines to focus on core optimization and full component disaggregation.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Kubernetes-based distributed inference frameworks are currently still in a phase of diverse approaches, with various companies proposing their respective solutions, such as ByteDance&apos;s AIBrix, Red Hat, Google, and IBM&apos;s llm-d, and NVIDIA&apos;s Dynamo. These companies are also actively collaborating closely with inference engine communities including vLLM and SGLang, hoping inference engines provide necessary interfaces and abstractions for upper-layer orchestration frameworks to integrate more conveniently with inference engines, achieving better inference performance at lower cost and improving deployment cost-effectiveness.&lt;/p&gt;
&lt;p&gt;Distributed inference frameworks are continuously absorbing solutions proposed by academia, integrating functions like PD disaggregation, LLM-specific load balancing strategies, and more dynamic KV Cache management. The close collaboration between academia and industry is believed by the author to be the norm in this field for the foreseeable future. Therefore, engineers responsible for inference need to pay more attention to frontier academic achievements and open-source community dynamics to propose better solutions based on their own scenarios and requirements.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;NVIDIA. &lt;a href=&quot;https://docs.nvidia.com/nim/benchmarking/llm/latest/index.html&quot;&gt;A Comprehensive Guide to NIM LLM Latency-Throughput Benchmarking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Austin et al. &lt;a href=&quot;https://jax-ml.github.io/scaling-book/inference/&quot;&gt;All About Transformer Inference | How To Scale Your Model&lt;/a&gt;, Google DeepMind. (2025)&lt;/li&gt;
&lt;li&gt;Bo Jiang and Sherlock Xu. &lt;a href=&quot;https://bentoml.com/blog/the-shift-to-distributed-llm-inference&quot;&gt;The Shift to Distributed LLM Inference: 3 Key Technologies Breaking Single-Node Bottlenecks&lt;/a&gt; (2025)&lt;/li&gt;
&lt;li&gt;Fog Dong and Sherlock Xu. &lt;a href=&quot;https://www.bentoml.com/blog/cold-starting-llms-on-kubernetes-in-under-30-seconds&quot;&gt;25x Faster Cold Starts for LLMs on Kubernetes&lt;/a&gt; (2025)&lt;/li&gt;
&lt;li&gt;Yu et al. Orca: A Distributed Serving System for Transformer-Based Generative Models, OSDI 2022. https://www.usenix.org/conference/osdi22/presentation/yu&lt;/li&gt;
&lt;li&gt;Patel et al. Splitwise: Efficient Generative LLM Inference Using Phase Splitting. arXiv:2311.18677 (2024)&lt;/li&gt;
&lt;li&gt;Zhong et. al. DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving, OSDI 2024. https://www.usenix.org/conference/osdi24/presentation/zhong-yinmin&lt;/li&gt;
&lt;li&gt;Qin et al. Mooncake: Trading More Storage for Less Computation — A KV Cache-centric Architecture for Serving LLM Chatbot, FAST 2025, https://www.usenix.org/conference/fast25/presentation/qin&lt;/li&gt;
&lt;li&gt;Qin et al. Mooncake: A KV Cache-centric Disaggregated Architecture for LLM Serving. arXiv:2407.00079 (2024)&lt;/li&gt;
&lt;li&gt;Sheng et al. Fairness in Serving Large Language Models, OSDI 2024. https://www.usenix.org/conference/osdi24/presentation/sheng&lt;/li&gt;
&lt;li&gt;Liu et al. CacheGen: KV Cache Compression and Streaming for Fast Large Language Model Serving. SIGCOMM 2024. &lt;a href=&quot;https://dl.acm.org/doi/10.1145/3651890.3672274&quot;&gt;10.1145/3651890.3672274&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Yao et al. CacheBlend: Fast Large Language Model Serving for RAG with Cached Knowledge Fusion. EuroSys 2025. &lt;a href=&quot;https://dl.acm.org/doi/10.1145/3689031.3696098&quot;&gt;10.1145/3689031.3696098&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Srivatsa et al. Preble: Efficient Distributed Prompt Scheduling for LLM Serving. ICLR 2025. &lt;a href=&quot;https://arxiv.org/abs/2407.00023&quot;&gt;arXiv:2407.00023&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Lou et al. Towards Swift Serverless LLM Cold Starts with ParaServe. &lt;a href=&quot;https://arxiv.org/abs/2502.15524&quot;&gt;arXiv:2502.15524v1&lt;/a&gt; (2025)&lt;/li&gt;
&lt;li&gt;Zhu et al. MegaScale-Infer: Serving Mixture-of-Experts at Scale with Disaggregated Expert Parallelism. &lt;a href=&quot;https://arxiv.org/abs/2504.02263&quot;&gt;arxiv:2504.02263&lt;/a&gt; (2025)&lt;/li&gt;
&lt;li&gt;Wang et al. Step-3 is Large yet Affordable: Model-system Co-design for Cost-effective Decoding. &lt;a href=&quot;https://arxiv.org/abs/2507.19427&quot;&gt;arxiv:2507.19427&lt;/a&gt; (2025)&lt;/li&gt;
&lt;li&gt;Singh et al. Efficiently Serving Large Multimodal Models Using EPD Disaggregation. &lt;a href=&quot;https://arxiv.org/abs/2501.05460&quot;&gt;arxiv:2501.05460&lt;/a&gt; (2024)&lt;/li&gt;
&lt;li&gt;Zhu et al. PolyServe: Efficient Multi-SLO Serving at Scale. &lt;a href=&quot;http://arxiv.org/abs/2507.17769&quot;&gt;arxiv:2507.17769&lt;/a&gt; (2025)&lt;/li&gt;
&lt;li&gt;Se7en. &lt;a href=&quot;https://cr7258.github.io/courses/ai-infra/AI%20Infra%20%E6%95%99%E7%A8%8B/03-prefix-caching&quot;&gt;Prefix Caching Explained: Implementing Efficient Cross-Request Reuse of KV Cache&lt;/a&gt; | Se7en&apos;s Architecture Notes (2025) (Chinese)&lt;/li&gt;
&lt;li&gt;Ce Gao. &lt;a href=&quot;https://gaocegege.com/Blog/genai/openmodelz-journey-cn&quot;&gt;Practices of Autoscaling LLM in Kubernetes&lt;/a&gt; (2024) (Chinese)&lt;/li&gt;
&lt;li&gt;The AIBrix Team. &lt;a href=&quot;https://aibrix.github.io/posts/2025-05-21-v0.3.0-release/&quot;&gt;AIBrix v0.3.0 Release: KVCache Offloading, Prefix Cache, Fairness Routing, and Benchmarking Tools&lt;/a&gt; (2025)&lt;/li&gt;
&lt;li&gt;BentoML. &lt;a href=&quot;https://bentoml.com/llm/&quot;&gt;LLM Inference Handbook&lt;/a&gt; (2025)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.google.com/document/d/1inTneLEZTv3rDEBB9KLOB9K6oMq8c3jkogARJqdt_58/preview&quot;&gt;[PUBLIC] llm-d Autoscaling Northstar - Google Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.google.com/document/d/1FNN5snmipaTxEA1FGEeSH7Z_kEqskouKD1XYhVyTHr8/preview&quot;&gt;[PUBLIC] llm-d Disaggregated Serving Northstar - Google Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Kubernetes</category><category>LLM</category></item><item><title>基于 MLflow 插件机制实现定制化的实验跟踪</title><link>https://rudeigerc.dev/posts/customized-experiment-tracking-with-mlflow-plugin/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/customized-experiment-tracking-with-mlflow-plugin/</guid><description>通过对 MLflow 的 Autologging 功能的深入了解，以及以 Google Cloud 的 Vertex AI Experiments 为例，本文展示了如何基于 MLflow 的插件机制集成自定义的实验跟踪。</description><pubDate>Sun, 10 Aug 2025 03:58:10 GMT</pubDate><content:encoded>&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;在 MLOps 的工程实践中，实验跟踪是机器学习训练中的重要组成部分。在前一篇&lt;a href=&quot;https://rudeigerc.dev/posts/customized-training-callbacks-with-llama-factory/&quot;&gt;文章&lt;/a&gt;中，笔者介绍了如何基于 Transformers Trainer 实现自定义的 Training Callback 来实现定制化的实验跟踪。&lt;/p&gt;
&lt;p&gt;然而，当需要将自定义的实验跟踪系统对接不同的 LLM 微调框架，或是集成传统的机器学习框架（如 PyTorch、XGBoost 与 LightGBM）时，我们需要根据不同框架的抽象层次实现特定的接口，并通过框架特定的方式在训练过程中指定使用自定义的 Logger 或 Tracker。这种方式容易引入额外的开发以及维护成本，特别是在需要支持多个框架的场景下。MLflow 作为业界广泛采用的机器学习实验管理平台，提供了 Autologging 功能和插件机制。Google Cloud 的 Vertex AI Experiments 便基于前述机制通过 MLflow 的 Python SDK 提供 Autologging 集成，无需针对每个框架单独实现接口，而是复用 MLflow 的现有实现。&lt;/p&gt;
&lt;p&gt;本文将以 Google Cloud 的 Vertex AI Experiments 为例，深入介绍如何基于 MLflow 的插件机制对接外部实验跟踪系统，并实现 Autologging 集成。此外，本文还会展示如何自行实现一个简单的 MLflow 插件，并将其与微调框架集成，以便在支持 MLflow 的微调框架中将实验跟踪数据代理到外部系统。&lt;/p&gt;
&lt;h2&gt;背景&lt;/h2&gt;
&lt;h3&gt;MLflow 的 Autologging&lt;/h3&gt;
&lt;p&gt;MLflow 是 Databricks 开源的机器学习模型管理与实验跟踪的平台，提供了丰富的功能来跟踪实验、管理模型和性能评估，其 Autologging 功能可以自动记录特定机器学习框架中的训练过程中的参数、指标和模型等信息，用户不需要过多手动介入即可实现模型元数据管理与实验跟踪。&lt;code&gt;mlflow.utils.autologging_utils&lt;/code&gt; 中的 &lt;code&gt;safe_patch&lt;/code&gt; 函数是 MLflow Tracking 中 Autologging 功能的核心，其主要功能在于将 MLflow 自定义实现的框架集成的部分注入到训练框架中的特定函数，从而实现无缝的集成。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def safe_patch(
    autologging_integration,
    destination,
    function_name,
    patch_function,
    manage_run=False,
    extra_tags=None,
)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;autologging_integration&lt;/code&gt;：要应用补丁的 Autologging 集成。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;destination&lt;/code&gt;：适用补丁的 Python 类。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;function_name&lt;/code&gt;：适用补丁的 &lt;code&gt;destination&lt;/code&gt; 类中的函数名称。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;patch_function&lt;/code&gt;：被适用的补丁函数。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;manage_run&lt;/code&gt;：是否自动管理 MLflow 的 Run。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;extra_tags&lt;/code&gt;：要添加到被管理的 MLflow Run 的额外标签。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以 &lt;a href=&quot;https://github.com/mlflow/mlflow/tree/master/mlflow/pytorch&quot;&gt;Pytorch Lightning&lt;/a&gt; 为例，在 &lt;code&gt;autolog&lt;/code&gt; 函数中会调用 &lt;code&gt;safe_patch(FLAVOR_NAME, pl.Trainer, &quot;fit&quot;, patched_fit, manage_run=True, extra_tags=extra_tags)&lt;/code&gt;，其中 &lt;code&gt;FLAVOR_NAME&lt;/code&gt; 是 &lt;code&gt;pytorch&lt;/code&gt;，&lt;code&gt;patched_fit&lt;/code&gt; 是 &lt;code&gt;pytorch_lightning.Trainer.fit&lt;/code&gt; 的 Wrapper 函数，涵盖了对训练过程中参数、指标与模型的自动化记录，其主要基于 PyTorch Lighting 的 &lt;a href=&quot;https://lightning.ai/docs/pytorch/stable/extensions/callbacks.html&quot;&gt;Callback&lt;/a&gt; 机制来实现。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from lightning.pytorch.callbacks import Callback

class MyPrintingCallback(Callback):
    def on_train_start(self, trainer, pl_module):
        print(&quot;Training is starting&quot;)

    def on_train_end(self, trainer, pl_module):
        print(&quot;Training is ending&quot;)

trainer = Trainer(callbacks=[MyPrintingCallback()])
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Vertex AI Experiments&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://cloud.google.com/vertex-ai/docs/experiments/intro-vertex-ai-experiments&quot;&gt;Vertex AI Experiments&lt;/a&gt; 是 Google Cloud 的 Vertex AI 平台提供的实验跟踪系统，可以跟踪与分析不同的训练任务中的元数据、超参数以及训练过程中的指标等数据，根据这些数据，用户可以更便利地进行实验管理从而确定将来的实验路径。Vertex AI Experiments 将一个&lt;strong&gt;实验（Experiment）&lt;strong&gt;作为实验管理的基本单位，一个实验可包含多个&lt;/strong&gt;流水线运行作业（PipelineJob Runs）&lt;strong&gt;与多个&lt;/strong&gt;实验运行作业（Experiment Runs）&lt;/strong&gt;。一个 Experiment Run 中包含超参数、指标、PipelineJob 与 Artifact 等数据。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;实验（Experiment）：实验管理的基本单位。&lt;/li&gt;
&lt;li&gt;实验运行作业（Experiment Run）：一个实验的具体一次运行，包含该次运行的超参数、指标等信息，用于监控机器学习训练任务的过程。Experiment Run 创建的时候需指定具体的 Experiment。&lt;/li&gt;
&lt;li&gt;流水线运行作业（Pipeline Job Run）：流水线作业 &lt;code&gt;PipelineJob&lt;/code&gt; 的运行实体，流水线运行作业由流水线作业的参数创建。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在下文中，本文将基于 Google Cloud 的 &lt;a href=&quot;https://github.com/googleapis/python-aiplatform&quot;&gt;AI Platform Python SDK&lt;/a&gt; 来介绍 Google Cloud 如何基于 MLflow 的插件机制与 Vertex AI Experiments 进行集成。&lt;/p&gt;
&lt;h2&gt;自定义的 MLflow Tracking 插件&lt;/h2&gt;
&lt;p&gt;要实现自定义的 MLflow Tracking 插件，需要继承 &lt;code&gt;mlflow.tracking.AbstractStore&lt;/code&gt; 类，并实现其抽象方法。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class _VertexMlflowTracking(abstract_store.AbstractStore):

    def __init__(self, store_uri: str | None, artifact_uri: str | None):
        pass

    @override
    def create_run(
        self,
        experiment_id: str,
        user_id: str,
        start_time: int,
        tags: list[&quot;RunTag&quot;],
        run_name: str,
    ) -&amp;gt; Run:
        pass

    @override
    def update_run_info(
        self,
        run_id: str,
        run_status: RunStatus,
        end_time: int,
        run_name: str,
    ) -&amp;gt; RunInfo:
        pass

    @override
    def log_batch(
        self,
        run_id: str,
        metrics: list[&quot;Metric&quot;],
        params: list[&quot;Param&quot;],
        tags: list[&quot;RunTag&quot;],
    ) -&amp;gt; None:
        pass

    @override
    def get_run(self, run_id: str) -&amp;gt; Run:
        pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果只需要集成基本的实验跟踪功能，只需要实现前述的几个接口即可。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;create_run&lt;/code&gt;：创建一个新的实验运行。Vertex 插件基于特定 Vertex AI 的 Experiment 创建 Experiment Run，并创建对应的 MLflow Run。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;update_run_info&lt;/code&gt;：更新实验运行的信息。Vertex 插件根据传入的 MLflow Run 状态结束或者更新对应的 Experiment Run 的状态。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;log_batch&lt;/code&gt;：记录一批实验数据。根据传入的 &lt;code&gt;metrics&lt;/code&gt; 与 &lt;code&gt;params&lt;/code&gt;，更新超参数与指标或时间序列指标到对应的 Experiment Run 中。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get_run&lt;/code&gt;：获取实验运行的详细信息。根据传入的 &lt;code&gt;run_id&lt;/code&gt; 从 &lt;code&gt;run_id&lt;/code&gt; 与 Experiment Run 的 Mapping 中构建并返回对应的 MLflow Run。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Autologging 集成&lt;/h2&gt;
&lt;p&gt;MLflow Tracking 实现了 Autologging 的功能，针对 PyTorch Lightning、XGBoost 与 LightGBM 等框架，只需要调用 &lt;code&gt;mlflow.autolog()&lt;/code&gt; 即可实现自动化的实验跟踪。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def _initialize_mlflow_plugin():
    &quot;&quot;&quot;Invokes the Vertex MLFlow plugin.

    Adding our log filter to MLFlow before calling mlflow.autolog() with
    silent=False will only surface warning logs when the installed ML
    framework version used for autologging is not supported by MLFlow.
    &quot;&quot;&quot;

    import mlflow
    from mlflow.tracking._tracking_service import utils as mlflow_tracking_utils
    from google.cloud.aiplatform._mlflow_plugin._vertex_mlflow_tracking import (
        _VertexMlflowTracking,
    )

    # Only show MLFlow warning logs for ML framework version mismatches
    logging.getLogger(&quot;mlflow&quot;).setLevel(logging.WARNING)
    logging.getLogger(&quot;mlflow.tracking.fluent&quot;).disabled = True
    logging.getLogger(&quot;mlflow.utils.autologging_utils&quot;).addFilter(
        _MLFlowLogFilter()
    )

    mlflow_tracking_utils._tracking_store_registry.register(
        &quot;vertex-mlflow-plugin&quot;, _VertexMlflowTracking
    )

    mlflow.set_tracking_uri(&quot;vertex-mlflow-plugin://&quot;)

    mlflow.autolog(
        log_input_examples=False,
        log_model_signatures=False,
        log_models=False,
        silent=False,  # using False to show unsupported framework version warnings with _MLFlowLogFilter
    )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Vertex AI 的 Python SDK 对其进行了简单的封装，用户可以分别使用 &lt;code&gt;aiplatform.autolog()&lt;/code&gt; 与 &lt;code&gt;aiplatform.autolog(disable=True)&lt;/code&gt; 来控制自动化实验跟踪的开启与关闭，这与 MLflow 的用法一致。在调用 &lt;code&gt;aiplatform.autolog()&lt;/code&gt; 时，SDK 会自动注册并设置使用 &lt;code&gt;vertex-mlflow-plugin&lt;/code&gt; 插件，随后便开启 MLflow 的 Autologging 功能。MLflow 在调用 Tracking 相关 API 的时候，会根据当前的 Tracking URI 自动选择使用对应的插件。&lt;/p&gt;
&lt;h2&gt;Demo: trackio-mlflow&lt;/h2&gt;
&lt;p&gt;在了解了 MLflow 的插件机制和 Autologging 集成之后，笔者实现了一个简单的插件 &lt;a href=&quot;https://github.com/rudeigerc/trackio-mlflow&quot;&gt;rudeigerc/trackio-mlflow&lt;/a&gt;，它是一个用于将基于 MLflow 的实验跟踪数据发送到 &lt;a href=&quot;https://trackio.com/&quot;&gt;TrackIO&lt;/a&gt; 的 Demo 插件。Trackio 是 Gradio（Hugging Face）开源的实验跟踪工具，它提供了轻量级的 API 来记录实验数据，并可将其与 Hugging Face Space 集成。在该插件中，笔者实现了前述的几个接口，可通过 &lt;code&gt;pip&lt;/code&gt; 安装此插件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install git+https://github.com/rudeigerc/trackio-mlflow.git
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;注册 MLflow 插件&lt;/h3&gt;
&lt;p&gt;MLflow 提供了基于 Entrypoint 的插件注册机制，可以通过在 &lt;code&gt;pyproject.toml&lt;/code&gt; 文件中添加以下 Entrypoint 来注册自定义插件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[project.entry-points.&quot;mlflow.tracking_store&quot;]
trackio = &quot;trackio_mlflow.store:TrackioStore&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此处 Entrypoint 的作用与 Vertex AI Experiments 中的 &lt;code&gt;mlflow_tracking_utils._tracking_store_registry.register&lt;/code&gt; 作用相同，会将 &lt;code&gt;trackio&lt;/code&gt; 注册为一个新的 MLflow 插件的 Scheme。如果需要使用插件，只需要显式调用 &lt;code&gt;mlflow.set_tracking_uri(&quot;trackio://&quot;)&lt;/code&gt;，或是将环境变量 &lt;code&gt;MLFLOW_TRACKING_URI&lt;/code&gt; 设置为 &lt;code&gt;trackio://&lt;/code&gt; 即可，在 MLflow 调用相应函数的时候即会调用插件中实现的函数。&lt;/p&gt;
&lt;h3&gt;集成 Torchtune&lt;/h3&gt;
&lt;p&gt;为了验证插件在支持 MLflow 的框架中的效果，笔者此处使用 &lt;a href=&quot;https://github.com/kubeflow/trainer&quot;&gt;Kubeflow Trainer&lt;/a&gt; 与 &lt;a href=&quot;https://github.com/pytorch/torchtune&quot;&gt;Torchtune&lt;/a&gt; 运行基于 Gemma 2 的 LoRA 微调任务。Torchtune 默认支持基于 Disk、Wandb、Tensorboard 与 MLflow 的实验跟踪，此处指定使用 &lt;code&gt;MLFlowLogger&lt;/code&gt; 进行实验跟踪，并将 &lt;code&gt;tracking_uri&lt;/code&gt; 设置为 &lt;code&gt;trackio://&lt;/code&gt; 即可。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tune run \
  lora_finetune_single_device \
  ... \
  metric_logger._component_=torchtune.training.metric_logging.MLFlowLogger \
  metric_logger.tracking_uri=trackio://
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
由于 Torchtune 在配置中默认使用的 Metric Logger 是 &lt;code&gt;DiskLogger&lt;/code&gt;，且有参数 &lt;code&gt;log_dir&lt;/code&gt;，在直接使用 &lt;code&gt;MLFlowLogger&lt;/code&gt; 时会导致以下错误：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TypeError: MLFlowLogger.init() got an unexpected keyword argument &apos;log_dir&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;笔者此处直接在搭建镜像的过程中使用 &lt;code&gt;sed -i &apos;/log_dir: \${output_dir}\/logs/d&apos; &amp;lt;path-to-site-packages&amp;gt;/recipes/configs/gemma2/2B_lora_single_device.yaml&lt;/code&gt; 在对应的配置中删除了此项。由于 Torchtune 目前处于 Sunset 的阶段，如果需要较为优雅地规避这个问题的话可使用自行编写的 Config。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;为了在实验过程中观测实验的指标，笔者此处使用 Kubernetes 的 &lt;a href=&quot;https://kubernetes.io/docs/concepts/workloads/pods/sidecar-containers/&quot;&gt;Sidecar Container&lt;/a&gt; 单独运行了 Trackio 的 Dashboard，并与训练任务的容器共享了 &lt;code&gt;~/.cache/huggingface/trackio&lt;/code&gt; 目录，Trackio 默认将数据记录在该路径下的 SQLite 文件中。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;initContainers:
  - name: trackio
    restartPolicy: Always
    image: ghcr.io/rudeigerc/trackio:0.2.2
    command:
      - trackio
    args:
      - show
    ports:
      - containerPort: 7860
        name: trackio
    volumeMounts:
      - name: trackio
        mountPath: /root/.cache/huggingface/trackio
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Trackio 的 Service：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: trackio
spec:
  type: ClusterIP
  selector:
    jobset.sigs.k8s.io/jobset-name: tune-gemma2-lora-with-alpaca
  ports:
  - name: trackio
    port: 7860
    targetPort: 7860
    protocol: TCP
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
如果不是在 Kubernetes 中运行微调任务，可直接运行 &lt;code&gt;tune run &amp;lt;args&amp;gt;&lt;/code&gt; 并使用 &lt;code&gt;trackio show&lt;/code&gt; 后通过 &lt;code&gt;http://localhost:7860&lt;/code&gt; 查看 Trackio 的 Dashboard 即可。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;通过配置对应的 Ingress 或是直接使用 &lt;code&gt;kubectl port-forward&lt;/code&gt;，可通过本地浏览器访问 Trackio 的 Dashboard。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://rudeigerc.dev/_astro/trackio.C942R0x1_ZBB3eG.webp&quot; alt=&quot;Trackio 中的 Torchtune 指标&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;通过对 MLflow 的 Autologging 功能的深入了解，以及以 Google Cloud 的 Vertex AI Experiments 为例，本文展示了如何基于 MLflow 的插件机制集成自定义的实验跟踪。相比于针对不同框架分别实现特定的接口，基于 MLflow 插件机制的方案具有更好的通用性和可维护性，能够以统一的方式支持多种机器学习框架的实验跟踪需求。通过实现 &lt;code&gt;trackio-mlflow&lt;/code&gt; 插件的示例，可以看到前述方案不仅能够有效地将实验数据代理到外部系统，还能够与现有的机器学习框架无缝集成，为机器学习工程师提供了更加灵活的实验管理解决方案。&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://mlflow.org/docs/latest/ml/tracking/autolog/&quot;&gt;Automatic Logging with MLflow Tracking | MLflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cloud.google.com/vertex-ai/docs/experiments/autolog-data&quot;&gt;Autolog data to an experiment run | Vertex AI | Google Cloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/googleapis/python-aiplatform/tree/main/google/cloud/aiplatform/_mlflow_plugin&quot;&gt;python-aiplatform/google/cloud/aiplatform/_mlflow_plugin at main · googleapis/python-aiplatform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.pytorch.org/torchtune/stable/generated/torchtune.training.metric_logging.MLFlowLogger.html&quot;&gt;MLFlowLogger — torchtune 0.6 documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>MLOps</category></item><item><title>在 GitLab Runner 上使用 Kaniko 构建多架构镜像</title><link>https://rudeigerc.dev/posts/build-multi-arch-images-on-gitlab-runners/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/build-multi-arch-images-on-gitlab-runners/</guid><description>本文介绍如何在 GitLab Runner 环境中使用 Kaniko 构建多架构容器镜像，涵盖基于 Kaniko 的 DevOps 流水线配置，以及通过缓存机制优化构建效率的实践方案。</description><pubDate>Wed, 06 Aug 2025 04:52:19 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;[!CAUTION]
&lt;a href=&quot;https://github.com/GoogleContainerTools/kaniko&quot;&gt;GoogleContainerTools/kaniko&lt;/a&gt; 已于 2025-06-03 归档，不再进行开发和维护，目前由 Chainguard 维护了 &lt;a href=&quot;https://www.chainguard.dev/unchained/fork-yeah-were-bringing-kaniko-back&quot;&gt;Fork&lt;/a&gt; 的版本 &lt;a href=&quot;https://github.com/chainguard-dev/kaniko&quot;&gt;chainguard-dev/kaniko&lt;/a&gt;。本文在行文中仍然会使用 &lt;code&gt;kaniko&lt;/code&gt; 来作为镜像构建的工具，但是会在本文的附录中提供使用其它工具构建的示例。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;在云原生的时代，容器化应用的部署已经成为主流，而随着多架构处理器的普及，构建支持多种架构的容器镜像已经成为现代 DevOps 流程中的重要环节，开发团队需要确保他们的应用程序能够在不同的处理器架构上无缝运行。
多架构镜像是一种支持多种架构的镜像，其存储的是描述不同架构镜像的清单文件。基于多架构镜像，可以在不同的架构上运行相同的工作负载，不用单独维护不同架构的部署清单。&lt;/p&gt;
&lt;p&gt;然而，现有的镜像构建工具如 Docker Buildx、Buildah 等通常需要特权容器来构建多架构镜像，这在特定环境下可能会带来安全隐患，因此用户期望能够在非特权容器中构建多架构镜像。
本文将以 Kaniko 作为镜像构建的工具，展示如何在 GitLab Runner 上使用非特权容器构建多架构镜像。&lt;/p&gt;
&lt;h2&gt;背景&lt;/h2&gt;
&lt;h3&gt;多架构镜像&lt;/h3&gt;
&lt;p&gt;多架构镜像本质上是描述不同架构镜像的清单文件，它包含了指向不同架构镜像的索引，一般多架构镜像会兼容 &lt;a href=&quot;https://specs.opencontainers.org/image-spec/manifest/&quot;&gt;Open Container Initiative (OCI) v1&lt;/a&gt; 或 &lt;a href=&quot;https://distribution.github.io/distribution/spec/manifest-v2-2/&quot;&gt;Docker Image Manifest V2 Schema 2&lt;/a&gt; 镜像格式。举例来说，使用 &lt;code&gt;docker manifest inspect golang:1.24&lt;/code&gt; 可以看到如下结果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;schemaVersion&quot;: 2,
  &quot;mediaType&quot;: &quot;application/vnd.oci.image.index.v1+json&quot;,
  &quot;manifests&quot;: [
    {
      &quot;mediaType&quot;: &quot;application/vnd.oci.image.manifest.v1+json&quot;,
      &quot;size&quot;: 2322,
      &quot;digest&quot;: &quot;sha256:9aba206b3974f93f7056304c991c9cc1f843c939159d9305571ab9766c9ccdf6&quot;,
      &quot;platform&quot;: {
        &quot;architecture&quot;: &quot;amd64&quot;,
        &quot;os&quot;: &quot;linux&quot;
      }
    },
    {
      &quot;mediaType&quot;: &quot;application/vnd.oci.image.manifest.v1+json&quot;,
      &quot;size&quot;: 2324,
      &quot;digest&quot;: &quot;sha256:2329251924279916c3d1b727809f6eb18715ba110fc69863872e3bc65b29bbe4&quot;,
      &quot;platform&quot;: {
        &quot;architecture&quot;: &quot;arm&quot;,
        &quot;os&quot;: &quot;linux&quot;,
        &quot;variant&quot;: &quot;v7&quot;
      }
    },
    ...
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在拉取镜像时，客户端会根据当前系统架构选择合适的镜像拉取，使得用户可以在跨不同架构无缝地部署相同的工作负载。&lt;/p&gt;
&lt;h3&gt;多架构镜像的构建&lt;/h3&gt;
&lt;p&gt;如果需要构建镜像，有以下几种工具供选择：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/docker/buildx&quot;&gt;Docker Buildx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/moby/buildkit&quot;&gt;moby/buildkit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/containers/buildah&quot;&gt;containers/buildah&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/containers/podman&quot;&gt;containers/podman&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/GoogleContainerTools/kaniko&quot;&gt;GoogleContainerTools/kaniko&lt;/a&gt; (Archived)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;前述四种工具都支持 &lt;a href=&quot;https://www.qemu.org/&quot;&gt;QEMU&lt;/a&gt; 来模拟不同架构的环境，从而实现跨架构的镜像构建。Kaniko 是 Google 开源的容器镜像构建工具，无需 Daemon 或者特权容器就可以构建容器镜像，但不支持跨架构构建，需要在对应架构的机器上运行才能构建该架构的镜像。其通过直接解析 Dockerfile 中的指令，并在用户空间中执行这些指令逐步构建镜像层，随后将构建好的镜像推送到指定的镜像仓库中。&lt;/p&gt;
&lt;h2&gt;准备工作&lt;/h2&gt;
&lt;p&gt;下文假设已经在所需架构的机器上安装了 GitLab Runner，此处我们使用 x86_64 和 ARM64 架构的 Runner 进行演示，并且分别设置了 &lt;code&gt;amd64&lt;/code&gt; 与 &lt;code&gt;arm64&lt;/code&gt; 的 &lt;a href=&quot;https://docs.gitlab.com/ci/yaml/#tags&quot;&gt;Tag&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;如果仅有在单架构上运行的 GitLab Runner，需要使用其它工具基于 QEMU 来提供多架构支持，如 Docker Buildx、BuildKit 或 Buildah 等，详见&lt;a href=&quot;#%E9%99%84%E5%BD%95&quot;&gt;附录&lt;/a&gt;部分。&lt;/p&gt;
&lt;h2&gt;构建流水线&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;FROM golang:1.24 AS builder
ARG TARGETOS
ARG TARGETARCH

WORKDIR /workspace

# Copy the go source
COPY cmd/ cmd/
COPY pkg/ pkg/

# Build
# the GOARCH has not a default value to allow the binary be built according to the host where the command
# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,
# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o server main.go

# Use distroless as minimal base image to package the binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
FROM gcr.io/distroless/static:nonroot
WORKDIR /
COPY --from=builder /workspace/server .
USER 65532:65532

ENTRYPOINT [&quot;/server&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是一个标准的构建 Go 应用的 Dockerfile，使用了多阶段的构建方式并使用 &lt;a href=&quot;https://github.com/GoogleContainerTools/distroless&quot;&gt;Distroless&lt;/a&gt; 作为基础镜像。它会接受 &lt;code&gt;TARGETOS&lt;/code&gt; 和 &lt;code&gt;TARGETARCH&lt;/code&gt; 两个参数来指定目标操作系统和架构，在使用 Buildkit 作为镜像的构建后端的时候会自动传递这两个参数[^dockerfile#automatic-platform-args]，并将其传入 &lt;code&gt;go build&lt;/code&gt; 命令中的 &lt;code&gt;GOOS&lt;/code&gt; 与 &lt;code&gt;GOARCH&lt;/code&gt; 环境变量中。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart LR
  lint-amd64[&quot;Lint (amd64)&quot;]
  lint-arm64[&quot;Lint (arm64)&quot;]
  test-amd64[&quot;Test (amd64)&quot;]
  test-arm64[&quot;Test (arm64)&quot;]
  build-amd64[&quot;Build (amd64)&quot;]
  build-arm64[&quot;Build (arm64)&quot;]
  manifest[Manifest]

  subgraph lint
    lint-amd64
    lint-arm64
  end

  subgraph test
    test-amd64
    test-arm64
  end

  subgraph build
    build-amd64
    build-arm64
  end

  lint-amd64 --&amp;gt; test-amd64 --&amp;gt; build-amd64 --&amp;gt; manifest
  lint-arm64 --&amp;gt; test-arm64 --&amp;gt; build-arm64 --&amp;gt; manifest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上图是一个简单的示意图，展示了在 GitLab Runner 上运行 DevOps Pipeline 的基本流程。&lt;/p&gt;
&lt;h3&gt;使用 Kaniko 构建镜像&lt;/h3&gt;
&lt;p&gt;为了支持在不同架构上构建镜像，我们定义了 &lt;code&gt;matrix&lt;/code&gt; 变量来指定不同的系统架构。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;build:
  stage: build
  image:
    name: gcr.io/kaniko-project/executor:v1.24.0-debug
    entrypoint: [&quot;&quot;]
  variables:
    TARGETARCH: ${GOARCH}
    TARGETOS: ${GOOS}
  before_script:
    - mkdir -p /kaniko/.docker
    - echo &quot;{\&quot;auths\&quot;:{\&quot;$CI_REGISTRY\&quot;:{\&quot;username\&quot;:\&quot;$CI_REGISTRY_USER\&quot;,\&quot;password\&quot;:\&quot;$CI_REGISTRY_PASSWORD\&quot;}}}&quot; &amp;gt; /kaniko/.docker/config.json
  parallel:
    matrix:
      - GOOS: linux
        GOARCH: amd64
      - GOOS: linux
        GOARCH: arm64
  tags:
    - ${GOARCH}
  script:
    - |
      /kaniko/executor \
        --context &quot;${CI_PROJECT_DIR}&quot; \
        --dockerfile &quot;${CI_PROJECT_DIR}/Dockerfile&quot; \
        --destination &quot;${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}-${GOARCH}&quot; \
        --build-arg TARGETOS=${GOOS} \
        --build-arg TARGETARCH=${GOARCH}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 GitLab Runner 上使用 Kaniko 构建镜像时，首先需要指定 Docker Credentials，以便 Kaniko 有权限能够推送镜像到镜像仓库中。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--context&lt;/code&gt;：指定构建上下文，通常是项目的根目录；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--dockerfile&lt;/code&gt;：指定 Dockerfile 的路径；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--destination&lt;/code&gt;：指定构建完成后推送到的镜像仓库地址。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--build-arg&lt;/code&gt;：指定 Dockerfile 中的构建参数 &lt;code&gt;ARG&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;构建结束后，Kaniko 会将构建好的镜像（&lt;code&gt;&amp;lt;image&amp;gt;:&amp;lt;sha256&amp;gt;-amd64&lt;/code&gt; 与 &lt;code&gt;&amp;lt;image&amp;gt;:&amp;lt;sha256&amp;gt;-arm64&lt;/code&gt;）推送到指定的镜像仓库中。&lt;/p&gt;
&lt;h3&gt;使用 Manifest Tool 构建多架构镜像&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;manifest:
  stage: manifest
  image:
    name: mplatform/manifest-tool:alpine-v2.2.0
    entrypoint: [&quot;&quot;]
  tags:
    - amd64
  script:
    - |
      manifest-tool \
        --username=${CI_REGISTRY_USER} --password=${CI_REGISTRY_PASSWORD} \
        push from-args --platforms linux/amd64,linux/arm64 \
        --template ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}-ARCH \
        --target ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时，&lt;code&gt;&amp;lt;image&amp;gt;:&amp;lt;sha256&amp;gt;-amd64&lt;/code&gt; 与 &lt;code&gt;&amp;lt;image&amp;gt;:&amp;lt;sha256&amp;gt;-arm64&lt;/code&gt; 两个支持 &lt;code&gt;amd64&lt;/code&gt; 与 &lt;code&gt;arm64&lt;/code&gt; 架构的镜像已经被推送到镜像仓库中，接下来需要利用工具来构建多架构镜像清单。&lt;a href=&quot;https://github.com/estesp/manifest-tool&quot;&gt;estesp/manifest-tool&lt;/a&gt; 是一个用来浏览和推送多架构镜像清单的工具，支持 &lt;a href=&quot;https://specs.opencontainers.org/image-spec/manifest/&quot;&gt;Open Container Initiative (OCI) v1&lt;/a&gt; 与 &lt;a href=&quot;https://distribution.github.io/distribution/spec/manifest-v2-2/&quot;&gt;Docker Image Manifest V2 Schema 2&lt;/a&gt; 的镜像格式。&lt;/p&gt;
&lt;p&gt;可以通过 manifest-tool 来基于先前构建好的不同架构的镜像来建立并推送多架构镜像清单。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;from-args&lt;/code&gt; 参数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--platforms&lt;/code&gt;：待构建的镜像清单的架构列表，格式为 &lt;code&gt;OS/ARCH/VARIANT?&lt;/code&gt; 并以逗号分隔，其中 &lt;code&gt;VARIANT&lt;/code&gt; 为可选项，如 &lt;code&gt;linux/amd64,linux/arm64&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--template&lt;/code&gt;：待构建的镜像模板，其中 &lt;code&gt;OS&lt;/code&gt;、&lt;code&gt;ARCH&lt;/code&gt; 与 &lt;code&gt;VARIANT&lt;/code&gt; 会被替换为 &lt;code&gt;--platforms&lt;/code&gt; 中指定的架构，如 &lt;code&gt;image:latest-ARCH&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--target&lt;/code&gt;：最终推送的多架构镜像清单的名称，如 &lt;code&gt;image:latest&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Manifest Tool 会根据 &lt;code&gt;&amp;lt;image&amp;gt;:&amp;lt;sha256&amp;gt;-amd64&lt;/code&gt; 与 &lt;code&gt;&amp;lt;image&amp;gt;:&amp;lt;sha256&amp;gt;-arm64&lt;/code&gt; 构建多架构镜像清单 &lt;code&gt;&amp;lt;image&amp;gt;:&amp;lt;sha256&amp;gt;&lt;/code&gt;，并将其推送到指定的镜像仓库中。&lt;/p&gt;
&lt;h2&gt;使用缓存加速构建&lt;/h2&gt;
&lt;p&gt;至此，我们已经可以在 GitLab Runner 上使用 Kaniko 构建多架构镜像了，拉取 &lt;code&gt;&amp;lt;image&amp;gt;:&amp;lt;sha256&amp;gt;&lt;/code&gt; 时会根据客户端的系统架构自动选择合适的镜像进行拉取。
但是如果直接使用前述的配置构建镜像的话，会发现每次构建都会重新下载依赖并重新编译代码，这会导致构建速度的显著降低，因此自然而然地就可以想到需要利用缓存来加速构建过程。
&lt;a href=&quot;https://github.com/GoogleContainerTools/kaniko/issues/969&quot;&gt;GoogleContainerTools/kaniko#969&lt;/a&gt; 中对如何在 Kaniko 中使用缓存加速构建有着详细的讨论，本文采用以下两种方式来加速构建过程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用 Kaniko 本身的缓存机制&lt;/li&gt;
&lt;li&gt;使用 GitLab Runner 的缓存机制结合 Go 的模块缓存&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;使用 Kaniko 缓存&lt;/h3&gt;
&lt;p&gt;Kaniko 本身提供了多种缓存方式来加速构建过程，即通过配置 &lt;code&gt;--cache-run-layers&lt;/code&gt; 与 &lt;code&gt;--cache-copy-layers&lt;/code&gt; 来分别缓存使用 &lt;code&gt;RUN&lt;/code&gt; 与 &lt;code&gt;COPY&lt;/code&gt; 的镜像层。在执行命令前，Kaniko 会先计算镜像层的摘要值来确定缓存是否存在，如果存在则会拉取并复用缓存而不会执行该镜像层的指令。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  script:
    - |
      /kaniko/executor \
+       --cache=true \
+       --cache-run-layers \
+       --cache-copy-layers \
+       --cache-repo=&quot;${CI_REGISTRY_IMAGE}/cache&quot; \
+       --cache-ttl=24h \
        --context &quot;${CI_PROJECT_DIR}&quot; \
        --dockerfile &quot;${CI_PROJECT_DIR}/Dockerfile&quot; \
        --destination &quot;${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}-${GOARCH}&quot; \
        --build-arg TARGETOS=${GOOS} \
        --build-arg TARGETARCH=${GOARCH}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--cache&lt;/code&gt;：启用缓存功能；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--cache-run-layers&lt;/code&gt;：缓存使用 &lt;code&gt;RUN&lt;/code&gt; 指令创建的镜像层；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--cache-copy-layers&lt;/code&gt;：缓存使用 &lt;code&gt;COPY&lt;/code&gt; 指令创建的镜像层；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--cache-repo&lt;/code&gt;：指定缓存存储的镜像仓库地址；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--cache-ttl&lt;/code&gt;：指定缓存的 TTL。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;使用 Go 缓存&lt;/h3&gt;
&lt;p&gt;除了使用 Kaniko 本身的缓存机制以外，我们还可以结合 GitLab Runner 的缓存机制结合 Go 的模块缓存来加速。&lt;/p&gt;
&lt;p&gt;Go 本身使用以下两种缓存：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GOCACHE&lt;/code&gt;：Go 的构建缓存目录；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GOMODCACHE&lt;/code&gt;：Go 的模块缓存目录，详见 &lt;a href=&quot;https://go.dev/ref/mod#module-cache&quot;&gt;Module Cache&lt;/a&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
如果使用的是 Docker Buildx 等使用 Daemon 的构建工具，可以选择在 Dockerfile 中使用 &lt;code&gt;RUN --mount=type=cache&lt;/code&gt; 来缓存 Go 模块。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;由于我们需要构建多架构的镜像，虽然 Go 的模块缓存是可以在不同架构间共享的，但是最好规避不同架构下的 Go 的构建缓存。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.go-cache:
  cache:
    - key:
        files:
          - go.mod
          - go.sum
      fallback_keys:
        - go-mod-cache
      paths:
        - .cache/go-mod/
      policy: pull-push
    - key: go-build-${GOARCH:-amd64}-${CI_COMMIT_REF_SLUG}
      fallback_keys:
        - go-build-${GOARCH:-amd64}
      paths:
        - .cache/go-build-${GOARCH:-amd64}/
      policy: pull-push
  before_script:
    - mkdir -p .cache/go-mod .cache/go-build-${GOARCH:-amd64}
    - export GOMODCACHE=&quot;$(pwd)/.cache/go-mod&quot;
    - export GOCACHE=&quot;$(pwd)/.cache/go-build-${GOARCH:-amd64}&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此处定义了一个 &lt;code&gt;.go-cache&lt;/code&gt; 的模板，包含了 Go 模块缓存与构建缓存的配置。在具体的 Job 中使用时，只需要继承 &lt;code&gt;.go-cache&lt;/code&gt; 模板即可：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;build:
  extends: .go-cache
  ...
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;随着多架构处理器的普及，构建支持多种架构的容器镜像已经成为现代 DevOps 流程中的重要环节。本文详细介绍了如何在 GitLab Runner 环境下使用 Kaniko 在非特权容器中实现多架构镜像的构建，并介绍了相应的流水线的构建配置。此外，本文通过结合 Kaniko 的缓存机制和 GitLab Runner 缓存功能与 Go 缓存机制的继承，有效地提升了镜像构建的效率。&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.docker.com/blog/faster-multi-platform-builds-dockerfile-cross-compilation-guide/&quot;&gt;Faster Multi-Platform Builds: Dockerfile Cross-Compilation Guide | Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arborxr.com/blog/developers-journal-building-unprivileged-multi-arch-images-with-kaniko-and-gitlab-ci&quot;&gt;Developer’s Journal: Building Unprivileged Multi-Arch Images with Kaniko and Gitlab CI - ArborXR&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.gitlab.com/ci/docker/using_buildkit/#build-multi-platform-images&quot;&gt;Build Docker images with BuildKit | GitLab Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;[^dockerfile#automatic-platform-args]: &lt;a href=&quot;https://docs.docker.com/reference/dockerfile/#automatic-platform-args-in-the-global-scope&quot;&gt;Dockerfile reference | Docker Docs&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;附录&lt;/h2&gt;
&lt;p&gt;附录中提供了一些示例代码片段。&lt;/p&gt;
&lt;h3&gt;使用 Kaniko&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;stages:
  - build
  - manifest

build:
  stage: build
  image:
    name: gcr.io/kaniko-project/executor:v1.24.0-debug
    entrypoint: [&quot;&quot;]
  variables:
    TARGETARCH: ${GOARCH}
    TARGETOS: ${GOOS}
  before_script:
    - mkdir -p /kaniko/.docker
    - echo &quot;{\&quot;auths\&quot;:{\&quot;$CI_REGISTRY\&quot;:{\&quot;username\&quot;:\&quot;$CI_REGISTRY_USER\&quot;,\&quot;password\&quot;:\&quot;$CI_REGISTRY_PASSWORD\&quot;}}}&quot; &amp;gt; /kaniko/.docker/config.json
  parallel:
    matrix:
      - GOOS: linux
        GOARCH: amd64
      - GOOS: linux
        GOARCH: arm64
  tags:
    - ${GOARCH}
  script:
    - |
      /kaniko/executor \
        --context &quot;${CI_PROJECT_DIR}&quot; \
        --dockerfile &quot;${CI_PROJECT_DIR}/Dockerfile&quot; \
        --destination &quot;${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}-${GOARCH}&quot; \
        --build-arg TARGETOS=${GOOS} \
        --build-arg TARGETARCH=${GOARCH} \
        --cache=true \
        --cache-run-layers \
        --cache-copy-layers \
        --cache-repo=&quot;${CI_REGISTRY_IMAGE}/cache&quot; \
        --cache-ttl=24h

manifest:
  stage: manifest
  image:
    name: mplatform/manifest-tool:alpine-v2.2.0
    entrypoint: [&quot;&quot;]
  tags:
    - amd64
  script:
    - |
      manifest-tool \
        --username=${CI_REGISTRY_USER} --password=${CI_REGISTRY_PASSWORD} \
        push from-args --platforms linux/amd64,linux/arm64 \
        --template ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}-ARCH \
        --target ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHORT_SHA}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;使用 Rootless BuildKit&lt;/h3&gt;
&lt;p&gt;可参考 GitLab 的相关文档 &lt;a href=&quot;https://docs.gitlab.com/ci/docker/using_buildkit/&quot;&gt;Build Docker images with BuildKit&lt;/a&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
需要设置 &lt;code&gt;user.max_user_namespaces&lt;/code&gt; 以允许在容器中使用 User Namespace。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;stages:
  - build
  - manifest

build:
  image: moby/buildkit:rootless
  stage: build
  variables:
    BUILDKITD_FLAGS: --oci-worker-no-process-sandbox
    CACHE_IMAGE: $CI_REGISTRY_IMAGE:cache
    TARGETARCH: ${GOARCH}
    TARGETOS: ${GOOS}
  before_script:
    - mkdir -p ~/.docker
    - echo &quot;{\&quot;auths\&quot;:{\&quot;$CI_REGISTRY\&quot;:{\&quot;username\&quot;:\&quot;$CI_REGISTRY_USER\&quot;,\&quot;password\&quot;:\&quot;$CI_REGISTRY_PASSWORD\&quot;}}}&quot; &amp;gt; ~/.docker/config.json
  parallel:
    matrix:
      - GOOS: linux
        GOARCH: amd64
      - GOOS: linux
        GOARCH: arm64
  tags:
    - ${GOARCH}
  script:
    - |
      buildctl-daemonless.sh build \
        --frontend dockerfile.v0 \
        --local context=. \
        --local dockerfile=. \
        --export-cache type=registry,ref=$CACHE_IMAGE \
        --import-cache type=registry,ref=$CACHE_IMAGE \
        --output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA-$GOARCH,push=true

manifest:
  stage: manifest
  image:
    name: mplatform/manifest-tool:alpine-v2.2.0
    entrypoint: [&quot;&quot;]
  script:
    - |
      manifest-tool \
        --username=${CI_REGISTRY_USER} --password=${CI_REGISTRY_PASSWORD} \
        push from-args --platforms linux/amd64,linux/arm64 \
        --template $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA-$GOARCH \
        --target $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;使用 Buildah&lt;/h3&gt;
&lt;p&gt;可参考 GitLab 的相关文档 &lt;a href=&quot;https://docs.gitlab.com/ci/docker/buildah_rootless_tutorial/&quot;&gt;Tutorial: Use Buildah in a rootless container with GitLab Runner Operator on OpenShift&lt;/a&gt;。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!WARNING]
需要设置 &lt;code&gt;user.max_user_namespaces&lt;/code&gt; 以允许在容器中使用 User Namespace，详见 &lt;a href=&quot;#troubleshooting&quot;&gt;Troubleshooting&lt;/a&gt; 部分。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;stages:
  - build
  - manifest

build:
  image: quay.io/buildah/stable
  stage: build
  variables:
    STORAGE_DRIVER: vfs
    BUILDAH_FORMAT: docker
    TARGETARCH: ${GOARCH}
    TARGETOS: ${GOOS}
  before_script:
    - echo &quot;$CI_REGISTRY_PASSWORD&quot; | buildah login -u &quot;$CI_REGISTRY_USER&quot; --password-stdin $CI_REGISTRY
  parallel:
    matrix:
      - GOOS: linux
        GOARCH: amd64
      - GOOS: linux
        GOARCH: arm64
  tags:
    - ${GOARCH}
  script:
    - buildah build --build-arg TARGETOS=$GOOS --build-arg TARGETARCH=$GOARCH -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA-$GOARCH .
    - buildah push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA-$GOARCH

manifest:
  stage: manifest
  image: quay.io/buildah/stable
  tags:
    - amd64
  script:
    - buildah manifest create $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
    - buildah manifest add --os=linux --arch=amd64 $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA-amd64
    - buildah manifest add --os=linux --arch=arm64 --variant v8 $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA-arm64
    - buildah manifest push --all $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Troubleshooting&lt;/h3&gt;
&lt;p&gt;在使用 BuildKit 或 Buildah 等工具时，可能会遇到 &lt;code&gt;user.max_user_namespaces&lt;/code&gt; 的限制问题。&lt;/p&gt;
&lt;p&gt;在 Kubernetes 集群中使用 GitLab Runner 时，可参考 BuildKit 所提供的使用特权容器的 DaemonSet 来配置节点的 &lt;code&gt;user.max_user_namespaces&lt;/code&gt; 参数，以允许在容器中使用 User Namespace。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: apps/v1
kind: DaemonSet
metadata:
  labels:
    app: sysctl-userns
  name: sysctl-userns
spec:
  selector:
    matchLabels:
      app: sysctl-userns
  template:
    metadata:
      labels:
        app: sysctl-userns
    spec:
      containers:
        - name: sysctl-userns
          image: busybox
          command: [&quot;sh&quot;, &quot;-euxc&quot;, &quot;sysctl -w user.max_user_namespaces=63359 &amp;amp;&amp;amp; sleep infinity&quot;]
          securityContext:
            privileged: true
&lt;/code&gt;&lt;/pre&gt;
</content:encoded><category>DevOps</category></item><item><title>在 LLaMA Factory 中使用自定义的 Training Callback 进行实验跟踪</title><link>https://rudeigerc.dev/posts/customized-training-callbacks-with-llama-factory/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/customized-training-callbacks-with-llama-factory/</guid><description>详细介绍如何在 LLaMA Factory 中基于 Transformers Trainer 实现自定义的 Training Callback，记录超参数、训练指标与GPU 使用情况，为 LLM 微调提供灵活的实验跟踪集成。</description><pubDate>Fri, 01 Aug 2025 13:24:46 GMT</pubDate><content:encoded>&lt;p&gt;随着 LLM 的快速发展，对模型进行微调已成为将通用模型适配到特定任务的关键技术。在微调过程中，实验跟踪和监控至关重要，它不仅帮助研究者追踪训练进度、分析模型性能，还能为后续的模型优化和参数调整提供重要依据。&lt;/p&gt;
&lt;p&gt;本文将详细介绍如何利用 Transformers 库的 Training Callback 机制，在流行的微调框架 LLaMA Factory 中实现自定义的实验跟踪功能，使用户能够根据自身需求灵活地记录训练指标、系统资源使用情况和模型配置信息，为 LLM 微调提供完整的实验跟踪集成。&lt;/p&gt;
&lt;h2&gt;背景&lt;/h2&gt;
&lt;h3&gt;Transformers&lt;/h3&gt;
&lt;p&gt;Transformers 是 Hugging Face 的 Python 开源库，其支持多种深度学习框架，并提供了统一的 API 设计来进行模型训练与推理。
Transformers 基本上成为了 LLM 领域的事实标准库，几乎所有的 LLM 的微调框架都是基于 Transformers 实现的，包括本文使用的 LLaMA Factory 与 Hugging Face 自身的 &lt;a href=&quot;https://github.com/huggingface/trl&quot;&gt;TRL&lt;/a&gt; 等。&lt;/p&gt;
&lt;h4&gt;Training Callback&lt;/h4&gt;
&lt;p&gt;Callback 是 Transformers 中用于在 Trainer 的训练过程中执行特定操作的机制。它们允许用户在训练的不同阶段插入自定义逻辑，例如记录指标或保存模型状态等，也可以根据自身需求来控制训练流程（如 &lt;a href=&quot;https://huggingface.co/docs/transformers/main_classes/callback#transformers.EarlyStoppingCallback&quot;&gt;Early stopping&lt;/a&gt;）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;trainer = Trainer(...)
trainer.add_callback(MyCallback)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Transformers 中的 &lt;code&gt;src/transformers/trainer_callback.py&lt;/code&gt; 与 &lt;code&gt;src/transformers/integrations/integration_utils.py&lt;/code&gt; 都提供了许多可以供参考的 Trainer Callback 的示例，用户只需要继承 &lt;code&gt;TrainerCallback&lt;/code&gt; 类并实现相关的方法并将其添加到 Trainer 中的 Callback 即可，Trainer 会根据加入 Callback 的顺序在特定步骤时依次调用。&lt;/p&gt;
&lt;h4&gt;Trainer&lt;/h4&gt;
&lt;p&gt;本节将简要介绍 Transformers 中的 Trainer 的工作流程，下文的伪代码简要描述了部分关键的训练流程。
Transformers 的 Trainer 的训练流程集成了前文所提及的 Training Callback 机制，由配置的处理以及相关组件的初始化开始，随后进入主要的训练流程（&lt;code&gt;on_train_begin&lt;/code&gt;）。
在训练过程中会循环迭代每个 epoch（&lt;code&gt;on_epoch_begin&lt;/code&gt;），并计算出需要的更新步数（基于 dataloader 的数量或是指定的 &lt;code&gt;max_steps&lt;/code&gt;，以及梯度累积步数 &lt;code&gt;gradient_accumulation_steps&lt;/code&gt;）并进入更新步数的循环。
在每个更新步数中（&lt;code&gt;on_step_begin&lt;/code&gt;），Trainer 会执行前向与反向传播计算损失，并进行梯度裁剪（如果需要的话），然后在实际参数更新的时候触发与 Optimizer 相关的回调（&lt;code&gt;on_pre_optimizer_step&lt;/code&gt; 和 &lt;code&gt;on_optimizer_step&lt;/code&gt;）。
根据梯度累积的设置，Trainer 会在每个梯度累积步数结束时（&lt;code&gt;on_step_end&lt;/code&gt;）或是每个更新步数结束时（&lt;code&gt;on_substep_end&lt;/code&gt;），根据回调设置的控制标志进行日志记录、保存模型或评估等操作。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def _inner_training_loop(...):
    setup_and_initialize()

    # Running training loop
    control = on_train_begin(...) # [!code highlight]

    for epoch in range(epochs_trained, num_train_epochs):
        control = on_epoch_begin(...) # [!code highlight]

        for update_step in range(total_updates):
            for i, inputs in enumerate(batch_samples):
                # For gradient accumulation boundaries
                if step % gradient_accumulation_steps == 0:
                    control = on_step_begin(...) # [!code highlight]

                # Forward and backward pass
                loss = training_step(model, inputs, num_items_in_batch)

                # Sync gradients
                if do_sync_step:
                    # Gradient clipping
                    if max_grad_norm &amp;gt; 0:
                        gradient_clipping(...)

                    # Pre-optimizer step hook
                    control = on_pre_optimizer_step(...) # [!code highlight]

                    # Optimizer step
                    optimizer.step()

                    # Post-optimizer step hook
                    control = on_optimizer_step(...) # [!code highlight]

                    # update learning rate
                    lr_scheduler.step()

                    # Post-step hook
                    control = on_step_end(...) # [!code highlight]

                    _maybe_log_save_evaluate(...)
                else:
                    # For gradient accumulation
                    control = on_substep_end(...) # [!code highlight]

                if control.should_training_stop or control.should_epoch_stop:
                    break

            # Break out of the nested loop
            if control.should_training_stop or control.should_epoch_stop:
                break

        if step &amp;lt; 0:
            control.should_training_stop = True

        control = on_epoch_end(...) # [!code highlight]

        _maybe_log_save_evaluate(...)

        if control.should_training_stop:
            break

    control = on_train_end(...) # [!code highlight]

    # Training completed

    return TrainOutput(global_step, train_loss, metrics)


def _maybe_log_save_evaluate(...):
    if control.should_log:
        # log()
        control = on_log(...) # [!code highlight]

    if control.should_evaluate:
        # evaluate()
        control = on_evaluate(...) # [!code highlight]

    if control.should_save:
        _save_checkpoint(...)

        control = on_save(...) # [!code highlight]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;LLaMA Factory&lt;/h3&gt;
&lt;p&gt;LLaMA Factory 是一个基于 Transformers 的微调框架，旨在简化 LLM 的训练和微调过程。它提供了易用的 UI 界面和灵活的 Python API，使得用户可以快速上手并进行自定义的训练任务，很好地平衡了专业使用者和非专业使用者的需求。&lt;/p&gt;
&lt;p&gt;以在 LLaMA Factory 中运行 SFT 为例，具体的工作流如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;读取 Tokenizer、数据集与模型&lt;/li&gt;
&lt;li&gt;Data Collation，SFT 中的 &lt;code&gt;SFTDataCollatorWith4DAttentionMask&lt;/code&gt; 继承了 Transformers 的 &lt;code&gt;DataCollatorForSeq2Seq&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;初始化 Trainer，SFT 中的 &lt;code&gt;CustomSeq2SeqTrainer&lt;/code&gt; 继承了 Transformers 的 &lt;code&gt;Seq2SeqTrainer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;训练
&lt;ul&gt;
&lt;li&gt;如果 &lt;code&gt;do_train&lt;/code&gt; 为 True，进行训练（&lt;code&gt;trainer.train&lt;/code&gt;）并保存模型&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;do_eval&lt;/code&gt; 为 True，基于评测数据集进行评估（&lt;code&gt;trainer.evaluate&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;do_predict&lt;/code&gt; 为 True，则进行预测（&lt;code&gt;trainer.predict&lt;/code&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;生成 Model Card 并推送模型&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;基于前文所提及的 Transformers 的 Training Callback，我们可以通过实现自定义的 Training Callback 来介入 Trainer 训练的流程。&lt;/p&gt;
&lt;h2&gt;实现自定义的 Training Callback&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class CustomTrackerCallback(TrainerCallback):
    def __init__(self):
        super().__init__()
        self.tracker = CustomTracker()

    @override
    def on_train_begin(self, args, state, control, model=None, **kwargs):
        pass

    @override
    def on_train_end(self, args, state, control, **kwargs):
        pass

    @override
    def on_log(self, args, state, control, logs=None, model=None, **kwargs):
        pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为了实现自定义的实验跟踪，我们需要关注在以下几个方法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;on_train_begin()&lt;/code&gt;：初始化实验记录，并记录相关的超参数。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;on_train_end()&lt;/code&gt;：结束当前的实验记录，并上传实验小结。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;on_log()&lt;/code&gt;：记录训练过程中的指标以及系统指标。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;需要注意的是，由于 LLaMA Factory 的 SFT 的 Workflow 在训练结束之后会根据 &lt;code&gt;do_eval&lt;/code&gt; 与 &lt;code&gt;do_predict&lt;/code&gt; 参数决定是否进行评估和预测，并将其结果通过 &lt;code&gt;log_metrics&lt;/code&gt; 记录，即会再次调用 &lt;code&gt;on_log()&lt;/code&gt; Callback。因此根据实验跟踪的实现，需要针对其进行额外的处理（如可能不能在 &lt;code&gt;on_train_end()&lt;/code&gt; 的时候结束实验）。&lt;/p&gt;
&lt;h3&gt;记录超参数&lt;/h3&gt;
&lt;p&gt;在 &lt;code&gt;on_train_begin()&lt;/code&gt; 方法中，我们可以初始化 Tracker 的实例（&lt;code&gt;args.run_name&lt;/code&gt;）并通过 &lt;code&gt;args&lt;/code&gt; 与 &lt;code&gt;model.config&lt;/code&gt; 和 &lt;code&gt;model.peft_config&lt;/code&gt; 等参数获取并记录相关的超参数与模型设置。&lt;/p&gt;
&lt;h3&gt;记录训练指标&lt;/h3&gt;
&lt;p&gt;Transformers 在训练过程中使用 &lt;code&gt;_maybe_log_save_evaluate()&lt;/code&gt; 函数来根据传入的参数判断是否要记录训练指标，或是保存模型和进行评估。&lt;/p&gt;
&lt;p&gt;默认情况下，在 &lt;code&gt;on_log()&lt;/code&gt; 回调函数中的 &lt;code&gt;logs&lt;/code&gt; 参数会包含以下几个指标：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;loss&lt;/code&gt;：损失 &lt;code&gt;round(self._nested_gather(tr_loss).mean().item() / (self.state.global_step - self._globalstep_last_logged), 4)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;grad_norm&lt;/code&gt;：梯度范数 &lt;code&gt;self.accelerator.clip_grad_norm_(...)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;learning_rate&lt;/code&gt;：学习率 &lt;code&gt;self.lr_scheduler.get_last_lr()[0]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;epoch&lt;/code&gt;：当前 Epoch &lt;code&gt;self.state.epoch&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;step&lt;/code&gt;：当前步数 &lt;code&gt;self.state.global_step&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;num_input_tokens_seen&lt;/code&gt;（if &lt;code&gt;args.include_num_input_tokens_seen&lt;/code&gt;）: 当前输入的 Token 数量 &lt;code&gt;self.state.num_input_tokens_seen&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此，我们可以根据 &lt;code&gt;state&lt;/code&gt; 或是 &lt;code&gt;logs&lt;/code&gt; 中的指标来记录相关的训练指标。&lt;/p&gt;
&lt;h3&gt;记录 GPU 指标&lt;/h3&gt;
&lt;p&gt;基于 &lt;code&gt;pynvml&lt;/code&gt;，我们可以便利地获取 GPU 的相关指标。如果使用的是非 NVIDIA 的 GPU 的话，还需要根据具体的型号来实现不同的指标获取方式。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;memory_allocated&lt;/code&gt;: 当前分配的 GPU 内存&lt;/li&gt;
&lt;li&gt;&lt;code&gt;memory_reserved&lt;/code&gt;: 当前保留的 GPU 内存&lt;/li&gt;
&lt;li&gt;&lt;code&gt;memory_active&lt;/code&gt;: 当前活跃的 GPU 内存&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;device_idx = torch.cuda.current_device()

total_memory = torch.cuda.get_device_properties(device_idx).total_memory
memory_allocated = torch.cuda.memory_allocated(device_idx)
memory_reserved = torch.cuda.memory_reserved(device_idx)
memory_active = torch.cuda.memory_stats(device_idx).get(
    &quot;active_bytes.all.peak&quot;, 0
)

gpu_memory_logs = {
    f&quot;gpu.{device_idx}.memory.allocated&quot;: memory_allocated / total_memory * 100,
    f&quot;gpu.{device_idx}.memory.reserved&quot;: memory_reserved / total_memory * 100,
    f&quot;gpu.{device_idx}.memory.active&quot;: memory_active / total_memory * 100,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然，我们也可以选择另外启动一个轻量级的进程去记录相关的系统指标而不需要耦合在 Callback 的逻辑中（如 &lt;code&gt;wandb&lt;/code&gt; 与 &lt;code&gt;mlflow&lt;/code&gt;）。&lt;/p&gt;
&lt;h2&gt;在 LLaMA Factory 中使用自定义的 Training Callback&lt;/h2&gt;
&lt;p&gt;目前 LLaMa Factory 的微调入口都是直接调用了 &lt;code&gt;llamafactory.train.tuner.run_exp&lt;/code&gt; 函数，但是实际上 &lt;code&gt;run_exp(args: Optional[dict[str, Any]] = None, callbacks: Optional[list[&quot;TrainerCallback&quot;]] = None) -&amp;gt; None&lt;/code&gt; 函数是可以接受 &lt;code&gt;callbacks&lt;/code&gt; 参数的，因此用户可以通过传入自定义的 Callback 来实现实验跟踪。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from llamafactory.train.tuner import run_exp

from tracker.integrations.transformers import (
    CustomTrackerCallback,
)


def main():
    run_exp(callbacks=[CustomTrackerCallback()])


if __name__ == &quot;__main__&quot;:
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用户可以简单地初始化 &lt;code&gt;CustomTrackerCallback&lt;/code&gt; 的实例并将其传递给 &lt;code&gt;run_exp&lt;/code&gt; 函数。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python -m tracker.cli &amp;lt;args&amp;gt;

## distributed training
torchrun -m tracker.cli &amp;lt;args&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;随后就可以使用 &lt;code&gt;python -m tracker.cli&lt;/code&gt; 或 &lt;code&gt;torchrun -m tracker.cli&lt;/code&gt; 替换掉原先的 &lt;code&gt;llamafactory-cli train&lt;/code&gt; 命令来运行微调任务。&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;在本文中，我们详细介绍了如何在 LLaMA Factory 中使用自定义的 Training Callback 进行实验跟踪。
基于实现自定义的 Callback，并通过实现训练过程中的相关回调函数，我们可以灵活地记录训练过程中相关指标，包括超参数、训练指标和系统资源使用情况等。
此外，我们还介绍了如何在 LLaMA Factory 中集成前述的自定义 Trainer Callback，并通过简单的 CLI 命令来运行微调任务。&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://huggingface.co/docs/transformers/main/en/main_classes/callback&quot;&gt;Callbacks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;附录：在其它微调相关框架中进行自定义的实验跟踪&lt;/h2&gt;
&lt;p&gt;在附录中，本文会简要介绍一些在其它微调相关框架中进行自定义的实验跟踪的方式。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;transformers&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;与本文所介绍的 LLaMA Factory 相同，基于 &lt;code&gt;transformers&lt;/code&gt; 的微调框架都可以通过实现自定义的 Training Callback 的方式来进行实验跟踪。&lt;/p&gt;
&lt;p&gt;可参考 &lt;a href=&quot;https://huggingface.co/docs/transformers/main/en/main_classes/callback&quot;&gt;Callbacks&lt;/a&gt; 文档获取更多示例。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;accelerate&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Accelerate 是 Hugging Face 提供的一个用于简化 PyTorch 分布式训练的库。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class GeneralTracker:
    &quot;&quot;&quot;
    A base Tracker class to be used for all logging integration implementations.

    Each function should take in `**kwargs` that will automatically be passed in from a base dictionary provided to
    [`Accelerator`].

    Should implement `name`, `requires_logging_directory`, and `tracker` properties such that:

    `name` (`str`): String representation of the tracker class name, such as &quot;TensorBoard&quot; `requires_logging_directory`
    (`bool`): Whether the logger requires a directory to store their logs. `tracker` (`object`): Should return internal
    tracking mechanism used by a tracker class (such as the `run` for wandb)

    Implementations can also include a `main_process_only` (`bool`) attribute to toggle if relevent logging, init, and
    other functions should occur on the main process or across all processes (by default will use `True`)
    &quot;&quot;&quot;

    main_process_only = True

    def __init__(self, _blank=False):
        if not _blank:
            err = &quot;&quot;
            if not hasattr(self, &quot;name&quot;):
                err += &quot;`name`&quot;
            if not hasattr(self, &quot;requires_logging_directory&quot;):
                if len(err) &amp;gt; 0:
                    err += &quot;, &quot;
                err += &quot;`requires_logging_directory`&quot;

            # as tracker is a @property that relies on post-init
            if &quot;tracker&quot; not in dir(self):
                if len(err) &amp;gt; 0:
                    err += &quot;, &quot;
                err += &quot;`tracker`&quot;
            if len(err) &amp;gt; 0:
                raise NotImplementedError(
                    f&quot;The implementation for this tracker class is missing the following &quot;
                    f&quot;required attributes. Please define them in the class definition: &quot;
                    f&quot;{err}&quot;
                )

    def start(self):
        &quot;&quot;&quot;
        Lazy initialization of the tracker inside Accelerator to avoid initializing PartialState before
        InitProcessGroupKwargs.
        &quot;&quot;&quot;
        pass

    def store_init_configuration(self, values: dict):
        &quot;&quot;&quot;
        Logs `values` as hyperparameters for the run. Implementations should use the experiment configuration
        functionality of a tracking API.

        Args:
            values (Dictionary `str` to `bool`, `str`, `float` or `int`):
                Values to be stored as initial hyperparameters as key-value pairs. The values need to have type `bool`,
                `str`, `float`, `int`, or `None`.
        &quot;&quot;&quot;
        pass

    def log(self, values: dict, step: Optional[int], **kwargs):
        &quot;&quot;&quot;
        Logs `values` to the current run. Base `log` implementations of a tracking API should go in here, along with
        special behavior for the `step parameter.

        Args:
            values (Dictionary `str` to `str`, `float`, or `int`):
                Values to be logged as key-value pairs. The values need to have type `str`, `float`, or `int`.
            step (`int`, *optional*):
                The run step. If included, the log will be affiliated with this step.
        &quot;&quot;&quot;
        pass

    def finish(self):
        &quot;&quot;&quot;
        Should run any finalizing functions within the tracking API. If the API should not have one, just don&apos;t
        overwrite that method.
        &quot;&quot;&quot;
        pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Accelerate 提供了一个通用的 Tracker 类 &lt;code&gt;GeneralTracker&lt;/code&gt;，用户可以通过继承该类来实现自定义的 Tracker。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from accelerate import Accelerator
from tracker.integrations.accelerate import CustomTracker

tracker = CustomTracker(run_name=&quot;test_run&quot;)
accelerator = Accelerator(log_with=tracker)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可参考 &lt;a href=&quot;https://huggingface.co/docs/accelerate/usage_guides/tracking&quot;&gt;Experiment trackers&lt;/a&gt; 文档获取更多示例。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;trl&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/huggingface/trl&quot;&gt;TRL&lt;/a&gt; 是 Hugging Face 开源一个用于进行包括 SFT、DPO、GRPO 等在内后训练的基于 Transformers 的 LLM 微调框架。&lt;/p&gt;
&lt;p&gt;在 TRL 中，基于 &lt;code&gt;transformers&lt;/code&gt; 的 Trainer（如 &lt;a href=&quot;https://huggingface.co/docs/trl/sft_trainer&quot;&gt;SFT&lt;/a&gt;、&lt;a href=&quot;https://huggingface.co/docs/trl/dpo_trainer&quot;&gt;DPO&lt;/a&gt; 与 &lt;a href=&quot;https://huggingface.co/docs/trl/grpo_trainer&quot;&gt;GRPO&lt;/a&gt; 等）都可以使用实现自定义的 Training Callback 的方式来进行实验跟踪，而针对 Stable Diffusion 的 Trainer（如 &lt;a href=&quot;https://huggingface.co/docs/trl/ddpo_trainer&quot;&gt;DDPO&lt;/a&gt; 与 &lt;a href=&quot;https://huggingface.co/docs/trl/alignprop_trainer&quot;&gt;AlignProp&lt;/a&gt;），可以使用 &lt;code&gt;accelerate&lt;/code&gt; 的自定义 Tracker 的方式来记录实验。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from tracker.integrations.transformers import (
    CustomTrackerCallback,
)
from trl import SFTTrainer

trainer = SFTTrainer(
    ...
    callbacks=[CustomTrackerCallback()],
)

trainer.train()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可参考 Transformers 的 &lt;a href=&quot;https://huggingface.co/docs/transformers/main/en/main_classes/callback&quot;&gt;Callbacks&lt;/a&gt; 或 Accelerate 的 &lt;a href=&quot;https://huggingface.co/docs/accelerate/usage_guides/tracking&quot;&gt;Experiment trackers&lt;/a&gt; 文档获取更多示例。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;deepspeed&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;DeepSpeed 是微软开发的一个开源深度学习优化库，专门用于加速大规模分布式深度学习训练。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Monitor(ABC):

    @abstractmethod
    def __init__(self, monitor_config):
        self.monitor_config = monitor_config

    @abstractmethod
    def write_events(self, event_list):
        pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不过目前 DeepSpeed 并没有提供 Out-of-tree 的方式来实现自定义的 Monitor。&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;torchtune&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;torchtune&lt;/code&gt; 是 PyTorch 社区所开源的 LLM 微调框架，旨在简化和优化 LLM 的训练和微调过程，并作为 PyTorch 原生的生态系统的一部分，可以与 PyTorch 生态系统的其它组件进行集成。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MetricLoggerInterface(Protocol):
    &quot;&quot;&quot;Abstract metric logger.&quot;&quot;&quot;

    def log(
        self,
        name: str,
        data: Scalar,
        step: int,
    ) -&amp;gt; None:
        &quot;&quot;&quot;Log scalar data.

        Args:
            name (str): tag name used to group scalars
            data (Scalar): scalar data to log
            step (int): step value to record
        &quot;&quot;&quot;
        pass

    def log_config(self, config: DictConfig) -&amp;gt; None:
        &quot;&quot;&quot;Logs the config as file

        Args:
            config (DictConfig): config to log
        &quot;&quot;&quot;
        pass

    def log_dict(self, payload: Mapping[str, Scalar], step: int) -&amp;gt; None:
        &quot;&quot;&quot;Log multiple scalar values.

        Args:
            payload (Mapping[str, Scalar]): dictionary of tag name and scalar value
            step (int): step value to record
        &quot;&quot;&quot;
        pass

    def close(self) -&amp;gt; None:
        &quot;&quot;&quot;
        Close log resource, flushing if necessary.
        Logs should not be written after `close` is called.
        &quot;&quot;&quot;
        pass
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;class CustomTrackerLogger(MetricLoggerInterface):
    pass
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;tune run lora_finetune_single_device \
    --config llama3/8B_lora_single_device \
    &amp;lt;args&amp;gt; \
    metric_logger._component_=custom_module.CustomTrackerLogger \
    &amp;lt;custom-tracker-args&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用户在创建微调任务的时候可以通过 &lt;code&gt;metric_logger._component_&lt;/code&gt; 参数来指定自定义的 MetricLogger，同时还支持使用 &lt;code&gt;metric_logger.&amp;lt;custom-args&amp;gt;&lt;/code&gt; 来传递相关的参数。&lt;/p&gt;
&lt;p&gt;可参考 &lt;a href=&quot;https://docs.pytorch.org/torchtune/stable/deep_dives/wandb_logging.html&quot;&gt;Logging to Weights &amp;amp; Biases&lt;/a&gt; 文档获取更多示例。&lt;/p&gt;
</content:encoded><category>LLM</category></item><item><title>年终总结 | 2024 年终总结：「变」</title><link>https://rudeigerc.dev/posts/year-in-review-2024/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/year-in-review-2024/</guid><description>2024 年终总结：「变」。</description><pubDate>Tue, 31 Dec 2024 05:11:52 GMT</pubDate><content:encoded>&lt;p&gt;2024 年对我来说是个过于难以定义的一年。今年年中的时候我终于下定决定从前司离职，虽然前司的工作环境和与同事之间的人际关系都相当不错，不过技术方面还是和个人的职业发展规划有一些分歧。&lt;/p&gt;
&lt;p&gt;之后的事情虽然有一些朋友已经知道了，不过我还是在此处简单提及一下，我于今年十月份回去台湾服兵役，预计在明年的二月会结训。虽然是一段漫长而又煎熬的旅程，不过这段时间并不都是毫无意义的（希望），比如我竟然还能在里面遇到比我小三届的高中学弟，以及有机会和许多台湾的同行有一些交流，都是非常难得的体验。&lt;/p&gt;
&lt;p&gt;今年对我来说也算是充满变化的一年吧，所以选择了「变」作为我今年的代表字。&lt;/p&gt;
&lt;h2&gt;ML Platform&lt;/h2&gt;
&lt;p&gt;我在前司主要担任的是机器学习平台开发工程师的工作，在这期间我调研了相当多国内外的产品，包括 Google 的 Vertex AI、AWS 的 SageMaker、阿里云的 PAI 等。&lt;/p&gt;
&lt;p&gt;我认为搭建机器学习平台的一个很有意思的地方在于，开发者不仅需要了解基础设施相关的技术，而且也应该要从用户（机器学习工程师、算法工程师、数据科学家等）的视角去了解他们需要平台所提供的功能。这也是当初涉足机器学习领域较少的我选择这个岗位的原因，我希望能够藉由搭建机器学习平台的机会去了解工业界的工程师都是如何使用计算资源的。&lt;/p&gt;
&lt;p&gt;但是在实际工作的过程中，我持续在思考一个问题，&lt;em&gt;我们需要开发的到底是一个什么样的产品？&lt;/em&gt; 机器学习平台在现在早已不是一个不成熟的概念，包括数据的管理、训练任务的管理、模型的版本控制以及工作流等，无论是开源的还是闭源的企业级机器学习平台都提供了丰富的解决方案。作为面向主要用户是企业内部人员的工程师，我们必须认识到并不是直接将外部的概念挪用进内部的产品就万事大吉，而更应该思考的是如何将用户既有的使用习惯迁移到新的产品形态上。此处可能就会涉及到一些 Trade Off，如果过于迁就用户整个平台的产品形态可能会过于分散，最后难以收束管理；而过于坚持己见希望推动一些实践方式的话又会对用户带来额外的迁移成本，对用户来说带去的收益也有限。&lt;/p&gt;
&lt;p&gt;当然这样的问题应该是不会有什么万能的解答的，持续这样的思考也是作为工程师的「醍醐味」吧。&lt;/p&gt;
&lt;h2&gt;Kubernetes&lt;/h2&gt;
&lt;p&gt;我从研究生的时候就一直关注基于 Kubernetes 的调度与资源管理的相关解决方案。Kueue 和 KWOK 是我近两年都一直在关注的 Kubernetes 社区的项目。Kueue 在项目建立之初我就开始关注了，当时刚好在调研有关多租户资源管理的方案，Kueue 的租户以及工作负载抽象设计相当吸引我，不会和 Volcano 一样一定要和调度的过程耦合在一起，如果希望的话也可以和 scheduler-plugins 搭配在一起使用，我认为是一种自由度相当高的解决方案。&lt;/p&gt;
&lt;p&gt;KWOK 是我在调研基于 Kubernetes 的E2E 测试的时候发现的工具，当然它在集成测试与性能测试的时候也都是相当好的工具，搭配 KinD 使用的话甚至都能在本地以很少的资源搭建 Kubernetes 集群运行前述的测试而不会被实际的计算资源限制。&lt;/p&gt;
&lt;p&gt;今年我还在线下参加了两次活动，一次是在上海举办的 KCD，一次是在香港举办的 KubeCon China，两次都听到的相当不错的分享，也和一些同行前辈们有了深入的交流，在香港甚至还见到了参加 LFX Mentorship 的时候认识的从未见过的多年网友，我认为这就是线下参加技术展会的最大乐趣了。当然必须要强调的是，作为从学生时代开始每年都在上海参加 KubeCon 的人而言，香港的 KubeCon 的午餐真的好吃非常多（而且还能看着非常漂亮的海景）。&lt;/p&gt;
&lt;h2&gt;LLM&lt;/h2&gt;
&lt;p&gt;2024 年 LLM 是一个绝对绕不过去的话题，无论是学术界还是工业界，LLM 相关的研究与应用都如雨后春笋一般让人目不暇接。今年也幸运地在工作中接触了一些 LLM 相关的内容，算是没有被这股浪潮抛弃在后。2023 年的时候我阅读了 vLLM PagedAttention 的论文，当时我受到了相当大的震撼，从来没有想过能够把内存的管理应用在推理过程的显存管理上，那时候我就觉得一定会有很多推理相关的工作会将操作系统或是其它领域的思想应用在推理过程中。而今年事实上也确实是推理相关的工作百花齐放的一年，各大操作系统会议的论文中有相当大的一部分都是与 LLM Inference 相关的，虽然我本身并不是专门研究 LLM 或是相关的 MLSys 的，不过有空的时候还是会读一下论文了解一下相关的进展与优化。&lt;/p&gt;
&lt;h2&gt;读书&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;书，只要买下来就好了。无论读与不读，只要把自己认为不错的书放在身边，人生就会因此而更加充实。
——京极夏彦&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;失去自由的这段漫长且痛苦的时间的少数优点是，我又重拾了读书的兴趣。得益于空闲的时间较多，而且手头上也没有电脑，主力的手机也是备用机的 iPhone XS（电池健康度 76% 战损版），我休假的时候基本就是在家里看书。台北有两家书店可以方便地购买到日文书籍：紀伊國屋与ジュンク堂書店。如果店里没有现货的话还会提供订货的服务，基本上一周之后就会空运到货了，周期和我的休假时间也很一致。&lt;/p&gt;
&lt;p&gt;可能是由于缺乏自由的缘故，我越来越喜欢看作家的随笔，主要是几位原先我就很喜欢的作家的作品，辻村深月、伊坂幸太郎与森博嗣等。可以从他们的书中感受到与现实生活的联系，也可以了解作家本身的想法，以及对他们与自己作品的看法。&lt;/p&gt;
&lt;p&gt;今年我个人最推荐的作品是森博嗣的《喜嶋先生の静かな世界》（喜岛老师的幽静世界），是我今年读到的最触动我的作品，某种意义上来说也是部很适合研究生与软件工程师阅读的作品，读到最后会让人反思自己失败的科研生活（笑）。&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;距离上一次写年终总结已经过了三年，其实实际上在这之间每年接近年末的时候我都会开始提笔，只不过最后都没办法完整地成文然后就作罢了。今年终于也利用离职到兵役之间的空闲时间把博客重构（重写）了一遍，尝试了一些以前一直想尝试却没有机会的技术栈，虽然还有少部分功能没有迁移过来，不过应该还是勉强能看一看的。&lt;/p&gt;
&lt;p&gt;其实在本文中还有不少东西想写的，不过纯用手机的话有些内容不大好去搜索，此外有些部分我也只能仰赖记忆去撰写，如有错漏的地方还望谅解。（后续有机会的话会把一些引用的条目补上去。）&lt;/p&gt;
&lt;p&gt;（欢迎各位 HR 与猎头在明年二月份之后联系我（Kubernetes 与云原生相关的职位），谢谢。）&lt;/p&gt;
&lt;p&gt;那么最后，感谢能看到此处的各位，祝愿各位 2025 年身体康健，诸事顺利。&lt;/p&gt;
&lt;p&gt;——写于台湾桃园&lt;/p&gt;
</content:encoded><category>年终总结</category></item><item><title>将博客从 Hugo 迁移到 Astro</title><link>https://rudeigerc.dev/posts/migrating-from-hugo-to-astro/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/migrating-from-hugo-to-astro/</guid><description>本文介绍了将博客从 Hugo 迁移到 Astro 的过程中的一些想法与选型，包括如何基于 Astro 对 Markdown 的渲染进行扩展，以及如何使用 Giscus 与 Pagefind 等工具为站点提供额外的功能集成等。</description><pubDate>Tue, 01 Oct 2024 14:25:19 GMT</pubDate><content:encoded>&lt;h2&gt;引言&lt;/h2&gt;
&lt;p&gt;我于 2019 年开始使用 &lt;a href=&quot;https://gohugo.io/&quot;&gt;Hugo&lt;/a&gt; 来构建我的博客，在那个时间点的话静态博客的方案选项还挺多的，除了 Hugo 之外还有 &lt;a href=&quot;https://jekyllrb.com/&quot;&gt;Jekyll&lt;/a&gt;、&lt;a href=&quot;https://hexo.io/&quot;&gt;Hexo&lt;/a&gt; 与 &lt;a href=&quot;https://www.gatsbyjs.com/&quot;&gt;Gatsby&lt;/a&gt; 等，印象中早期 GitHub Pages 针对 Jekyll 提供了第一方的集成支持，因此那时候 Jekyll 是许多人搭建静态博客的首选。Hugo 是个使用 Go 语言的静态网站生成器，当时应该是刚好开始接触 &lt;a href=&quot;https://kubernetes.io/&quot;&gt;Kubernetes&lt;/a&gt; 和 &lt;a href=&quot;https://go.dev/&quot;&gt;Go&lt;/a&gt;，而 Kubernetes 本身官方文档采用的就是 Hugo 的 &lt;a href=&quot;https://www.docsy.dev/&quot;&gt;Docsy&lt;/a&gt; 主题，可能也算是爱屋及乌吧，所以就选择了 Hugo。&lt;/p&gt;
&lt;p&gt;起初我是采用了 &lt;a href=&quot;https://github.com/vividvilla/ezhil&quot;&gt;vividvilla/ezhil&lt;/a&gt; 作为博客的主题，我很喜欢里面的一些设计风格，不会过于花里胡哨。但是过了一段时间之后就发现有些方面又不大符合我的需求，包括一些 Markdown 解析上的扩展，单纯 Fork 出去的话也不太好和上游对齐，最后在 2020 年的时候还是决定自己编写 Hugo 的主题 &lt;a href=&quot;https://github.com/rudeigerc/yggdrasill&quot;&gt;rudeigerc/yggdrasill&lt;/a&gt;。仓库中有提到，编写主题的时候走马观花地借鉴了不少人的设计，同时满足了一些基本的定制化的需求后，形成了一个比较稳定的版本，就一直沿用了四年到了现在。&lt;/p&gt;
&lt;p&gt;Hugo 编写主题的时候主要采用的是 Go Template 的方式，说实在的并不是特别好用，尤其是在 HTML 里使用的时候（以及在 &lt;a href=&quot;https://helm.sh/&quot;&gt;Helm&lt;/a&gt; 里已经被深深折磨之后），先前尝试了几个 ESlint 和 Prettier 的插件都没办法很好地去格式化代码，代码补全也有点问题，变量嵌套太深的时候并不是特别好处理，到最后代码的可维护性并不是特别好。&lt;/p&gt;
&lt;p&gt;此时，我在偶然之中了解到了 &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt;，我认为 Astro 的吸引我的部分主要是其高度的可扩展性，可以根据自己的需求进行相当细粒度的定制，不会受限于框架本身的约束，包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;基于 &lt;a href=&quot;https://docs.astro.build/en/reference/integrations-reference/&quot;&gt;Astro Integration API&lt;/a&gt; 的 Astro 扩展集成;&lt;/li&gt;
&lt;li&gt;基于 &lt;a href=&quot;https://github.com/remarkjs/remark&quot;&gt;Remark&lt;/a&gt; 与 &lt;a href=&quot;https://github.com/rehypejs/rehype&quot;&gt;Rehype&lt;/a&gt; 插件的 Markdown 渲染扩展；&lt;/li&gt;
&lt;li&gt;基于 &lt;a href=&quot;https://shiki.matsu.io/guide/transformers&quot;&gt;Shiki Transformers&lt;/a&gt; 的语法高亮扩展。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在接下来的部分当中，我想简单介绍一下这次将博客从 Hugo 迁移到 Astro 的过程中的一些想法与选型。&lt;/p&gt;
&lt;h2&gt;将文章从 Hugo 迁移到 Astro&lt;/h2&gt;
&lt;p&gt;Astro 提出了 &lt;a href=&quot;https://docs.astro.build/en/guides/content-collections/&quot;&gt;Collection&lt;/a&gt; 的概念对内容进行管理，并使用 &lt;a href=&quot;https://github.com/colinhacks/zod&quot;&gt;colinhacks/zod&lt;/a&gt; 进行基于 TypeScript 的 Frontmatter 的类型校验。&lt;/p&gt;
&lt;p&gt;如果 Astro 项目基于的是自带的 Blog 模板的话，除了需要将 Hugo 中用于标识日期的元数据 &lt;code&gt;date&lt;/code&gt; 替换为 &lt;code&gt;pubDate&lt;/code&gt; 以外（当然直接修改 Astro 中 &lt;code&gt;content&lt;/code&gt; 的元数据也是可行的），大部分的元数据与 Hugo 是通用的，因此文章内容上基本没什么迁移成本。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const blog = defineCollection({
  type: &quot;content&quot;,
  schema: z.object({
    title: z.string(),
    description: z.string().default(&quot;&quot;),
    pubDate: z.coerce.date(),
    updatedDate: z.coerce.date().optional(),
    heroImage: z.string().optional(),
    draft: z.boolean().optional().default(false),
    categories: z.array(z.string()).optional(),
    tags: z.array(z.string()).optional(),
    series: z.array(z.string()).optional(),
  }),
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是我在本项目中实际定义博客的 Schema，Astro 会根据在此处定义的类型相关的约束自动对 &lt;code&gt;content&lt;/code&gt; 中对应 &lt;code&gt;collection&lt;/code&gt;（即 &lt;code&gt;blog&lt;/code&gt;）中文件的 Frontmatter 进行校验。&lt;/p&gt;
&lt;h2&gt;使用 Biome 作为 Linter 与 Formatter&lt;/h2&gt;
&lt;p&gt;一般而言，一个前端项目需要配置包括 &lt;code&gt;eslint&lt;/code&gt;（Linting）、&lt;code&gt;stylelint&lt;/code&gt;（Linting）与 &lt;code&gt;prettier&lt;/code&gt;（Formatting）在内的多种 Linter 与 Formatter。&lt;a href=&quot;https://biomejs.dev/&quot;&gt;Biome&lt;/a&gt; 是一个适用于 JavaScript、JSON 与 CSS 等 Web 开发相关编程语言的工具，包括 Linter 与 Formatter，与前述工具的配置规则有着相当高的兼容性，因此可以以较低成本从这些工具迁移到 Biome 上。&lt;/p&gt;
&lt;p&gt;比较可惜的是目前 Biome 对 Astro 的支持目前还不是特别完善[^biome-language-support]，针对 Astro 文件仅有 Frontmatter 部分的支持，从实用性上来说就略显鸡肋。Astro 自身以及 Astro 的一些相关网站 &lt;a href=&quot;https://github.com/withastro/astro.build&quot;&gt;astro.build&lt;/a&gt; 与 &lt;a href=&quot;https://github.com/withastro/astro.new&quot;&gt;withastro/astro.new&lt;/a&gt; 先前也引入了 Biome 作为其 Linter 与 Formatter，但是之后又将 Prettier 重新引入作为 Astro 文件的默认 Formatter，我现在所采用的配置主要就来自于 &lt;a href=&quot;https://github.com/withastro/astro.build&quot;&gt;withastro/astro.build&lt;/a&gt; 仓库中的相关配置。&lt;/p&gt;
&lt;p&gt;Biome 会从 &lt;code&gt;biome.json&lt;/code&gt; 文件中读取配置，此处将 &lt;code&gt;formatter&lt;/code&gt; 配置为对 &lt;code&gt;*.astro&lt;/code&gt; 不启用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;$schema&quot;: &quot;./node_modules/@biomejs/biome/configuration_schema.json&quot;,
  ...
  &quot;formatter&quot;: {
    &quot;enabled&quot;: true,
    &quot;ignore&quot;: [&quot;*.astro&quot;, &quot;pnpm-lock.yaml&quot;]
  },
  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同时根据 Astro 官方的配置指引[^astro-prettier]配置 Prettier：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/** @type {import(&quot;prettier&quot;).Config} */
export default {
  plugins: [&quot;prettier-plugin-astro&quot;],
  overrides: [
    {
      files: &quot;*.astro&quot;,
      options: {
        parser: &quot;astro&quot;,
      },
    },
  ],
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果使用的是 VSCode 的话，需要注意为不同的文件类型启用相对应的插件。&lt;/p&gt;
&lt;h2&gt;Tailwind CSS + Shadcn UI&lt;/h2&gt;
&lt;p&gt;我本身前端相关的经验主要都是基于 React 或是 Vue 配合现成的组件库（如 &lt;a href=&quot;https://mui.com/material-ui/&quot;&gt;Material UI&lt;/a&gt; 与 &lt;a href=&quot;https://ant.design/&quot;&gt;Ant Design&lt;/a&gt;）配合一些 CSS-in-JS 的框架对样式进行自定义，此次刚好有机会能够使用 Tailwind CSS 来体验一下使用原子化 CSS[^atmoic-css]编写样式的感觉，Astro 同时也以 &lt;a href=&quot;https://docs.astro.build/en/reference/integrations-reference/&quot;&gt;Integration&lt;/a&gt; 的形式提供了对 Tailwind CSS 的支持。&lt;/p&gt;
&lt;p&gt;在引入了 Tailwind CSS 的基础上，我采用 &lt;a href=&quot;https://ui.shadcn.com/&quot;&gt;shadcn/ui&lt;/a&gt; 作为基底的组件库，不得不说使用 shadcn 的体验相当理想，尤其是对我这种轻前端的人来说，不用过多关注设计的细节就可以实现相当不错的效果，在自定义样式的时候也很方便，只需直接引入原子化 CSS 对应的类即可。此外，&lt;a href=&quot;https://github.com/shadcn-ui/taxonomy&quot;&gt;shadcn-ui/taxonomy&lt;/a&gt; 也提供了一些页面的设计可作为参考。&lt;/p&gt;
&lt;p&gt;有关如何在 Astro 项目中引入 Tailwind CSS 与 &lt;code&gt;shadcn&lt;/code&gt;，可以参考 &lt;code&gt;shadcn/ui&lt;/code&gt; 的相关文档[^shadcn-astro]。&lt;/p&gt;
&lt;h2&gt;基于 Giscus 的评论系统&lt;/h2&gt;
&lt;p&gt;在博客搭建的初期我采用的是 &lt;a href=&quot;https://disqus.com/&quot;&gt;Disqus&lt;/a&gt; 作为评论系统，这也是 Hugo 提供第一方支持的评论系统，但是评论数据托管在没什么关联性的第三方网站上总归还是感觉有点奇怪，而且还有隐私与数据安全性的问题，我印象中当时 Disqus 也因此引发过相当大的争论，因此之后我就在寻找评论系统的替代品，最终选择了 Giscus。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://giscus.app/&quot;&gt;Giscus&lt;/a&gt; 是一个基于 &lt;a href=&quot;https://docs.github.com/en/discussions&quot;&gt;GitHub Discussions&lt;/a&gt; 的评论系统，只需要进行简单的 JavaScript 配置就可以将其引入到博客当中。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script src=&quot;https://giscus.app/client.js&quot;
        data-repo=&quot;[ENTER REPO HERE]&quot;
        data-repo-id=&quot;[ENTER REPO ID HERE]&quot;
        data-category=&quot;[ENTER CATEGORY NAME HERE]&quot;
        data-category-id=&quot;[ENTER CATEGORY ID HERE]&quot;
        data-mapping=&quot;pathname&quot;
        data-strict=&quot;0&quot;
        data-reactions-enabled=&quot;1&quot;
        data-emit-metadata=&quot;0&quot;
        data-input-position=&quot;bottom&quot;
        data-theme=&quot;preferred_color_scheme&quot;
        data-lang=&quot;en&quot;
        crossorigin=&quot;anonymous&quot;
        async&amp;gt;
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;基于 Pagefind 的搜索集成&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://pagefind.app/&quot;&gt;Pagefind&lt;/a&gt; 是一个使用 Rust 编写的静态搜索库，具有高效的搜索性能，可以很方便地与静态网站框架集成，仅需要在构建后的静态文件的基础上构建索引即可。&lt;/p&gt;
&lt;p&gt;如果要在 Astro 中引入 Pagefind 的支持，Astro 默认会将构建后的文件输出到 &lt;code&gt;dist&lt;/code&gt; 目录，因此只需要在 &lt;code&gt;pagefind.yaml&lt;/code&gt; 文件中指定 &lt;code&gt;site&lt;/code&gt; 为 &lt;code&gt;dist&lt;/code&gt; 即可：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;site: dist
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;同时在 &lt;code&gt;package.json&lt;/code&gt; 中添加 Post Hook 以在构建完成后运行 Pagefind：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;scripts&quot;: {
  &quot;preinstall&quot;: &quot;npx only-allow pnpm&quot;,
  &quot;build&quot;: &quot;astro check &amp;amp;&amp;amp; astro build&quot;,
  &quot;postbuild&quot;: &quot;pnpx pagefind&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;基于 Remark 与 Rehype 的 Markdown 扩展&lt;/h2&gt;
&lt;p&gt;Astro 基于 Remark 提供了 Markdown 渲染的支持，其默认启用了一些 Remark 插件，如 &lt;code&gt;remark-gfm&lt;/code&gt; 与 &lt;code&gt;remark-smartypants&lt;/code&gt;，同时也支持用户自定义的 Remark 插件与 Rehype 插件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart LR
  syntaxTree((&quot;Syntax Tree&quot;))
  input((&quot;Input&quot;))
  output((&quot;Output&quot;))

  input --&amp;gt; Parser
  Parser --&amp;gt;|Parse| syntaxTree
  syntaxTree --&amp;gt; Compiler
  Compiler --&amp;gt;|Stringify| output

  subgraph Run
    direction TB
    syntaxTree &amp;lt;--&amp;gt;|Run| Transformers
  end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/unifiedjs/unified&quot;&gt;&lt;code&gt;unified&lt;/code&gt;&lt;/a&gt; 是基于抽象语法树的文本内容统一接口，前文提到的 Remark 与 Rehype 插件都是其生态系统的一部份。使用 unified 的处理过程主要包含三个步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Parser（parse）：将输入的文本转换成抽象语法树；&lt;/li&gt;
&lt;li&gt;Transformer（run）：对抽象语法树进行处理；&lt;/li&gt;
&lt;li&gt;Compiler（stringify）：将抽象语法树重新编译成文本。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;flowchart LR
  subgraph &quot;Input&quot;
    Markdown
  end

  subgraph &quot;Unified&quot;
    subgraph &quot;Remark&quot;
      direction LR
      remark-parse --&amp;gt;|mdast| remark-plugins
      remark-plugins --&amp;gt;|mdast| remark-rehype
    end

    subgraph &quot;Rehype&quot;
      direction LR
      rehype-plugins --&amp;gt;|hast| rehype-stringify
    end

    remark-rehype --&amp;gt;|hast| rehype-plugins
  end

  subgraph &quot;Output&quot;
    HTML
  end

  Markdown --&amp;gt;|string| remark-parse
  rehype-stringify --&amp;gt;|string| HTML
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上图展示的是一个具体的例子，其简要描述了 Astro 使用 Remark 与 Rehype 插件渲染 Markdown 文件的部分过程（此处省略了一些 Astro 默认启用的插件以及一些 Astro 内部的自定义插件），具体包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;通过 &lt;code&gt;remark-parse&lt;/code&gt; 将输入的 Markdown 文本转换成 Markdown 抽象语法树 &lt;code&gt;mdast&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;运行配置中的自定义 &lt;code&gt;remark&lt;/code&gt; 插件；&lt;/li&gt;
&lt;li&gt;通过 &lt;code&gt;remark-rehype&lt;/code&gt; 插件将 &lt;code&gt;mdast&lt;/code&gt; 转换成超文本抽象语法树 &lt;code&gt;hast&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;运行配置中的自定义 &lt;code&gt;rehype&lt;/code&gt; 插件；&lt;/li&gt;
&lt;li&gt;通过 &lt;code&gt;rehype-stringify&lt;/code&gt; 将 &lt;code&gt;hast&lt;/code&gt; 重新编译成文本输出。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;此外，Astro 默认使用的语法高亮工具 &lt;a href=&quot;https://shiki.style/&quot;&gt;Shiki&lt;/a&gt; 也提供了 &lt;a href=&quot;https://shiki.style/guide/transformers&quot;&gt;Transformers&lt;/a&gt; 的方式基于 &lt;code&gt;hast&lt;/code&gt; 对代码高亮的支持提供了多个扩展点。&lt;/p&gt;
&lt;p&gt;通过前述的几种方式（&lt;code&gt;remark&lt;/code&gt; 插件、&lt;code&gt;rehype&lt;/code&gt; 插件与 Shiki Transformers）都可以很方便地基于抽象语法树对 Markdown 渲染过程中的特定步骤进行自定义扩展。&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;本文介绍了一些我在将博客从 Hugo 迁移到 Astro 的时候的一些想法与选型。我认为能够从头开始搭建博客是一件相当有意思的事情，虽然我本身并不是专职的前端工程师，但也能靠着现有成熟框架的封装来自行搭建一个算是相当符合自身审美以及需求的内容展示平台。对于对前端并不是特别熟悉的工程师来说，在如今有了 &lt;a href=&quot;https://www.cursor.com/&quot;&gt;Cursur&lt;/a&gt; 以及 &lt;a href=&quot;https://v0.dev/&quot;&gt;v0&lt;/a&gt; 等工具辅助之后，想必像这样自行 DIY 的成本与难度会更低，如果对此感兴趣的话也可以尝试一下。&lt;/p&gt;
&lt;p&gt;后续如果有机会的话，希望基于前文所提到的部分选型来进行详细的介绍，包括一些具体的实现细节与配置方法，以及其它的实现方案与对比。&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.astro.build/en/guides/migrate-to-astro/from-hugo/&quot;&gt;Migrating from Hugo | Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://diygod.cc/unified-markdown&quot;&gt;如何优雅编译一个 Markdown 文档 - DIYgod&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;[^biome-language-support]: &lt;a href=&quot;https://biomejs.dev/internals/language-support/#html-super-languages-support&quot;&gt;Language support | Biome&lt;/a&gt;
[^shadcn-astro]: &lt;a href=&quot;https://ui.shadcn.com/docs/installation/astro&quot;&gt;Astro - shadcn/ui&lt;/a&gt;
[^atmoic-css]: &lt;a href=&quot;https://css-tricks.com/lets-define-exactly-atomic-css/&quot;&gt;Let’s Define Exactly What Atomic CSS Is | CSS-Tricks&lt;/a&gt;
[^astro-prettier]: &lt;a href=&quot;https://docs.astro.build/en/editor-setup/#prettier&quot;&gt;Editor Setup | Docs&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Astro</category></item><item><title>在 Astro 中集成 Mermaid</title><link>https://rudeigerc.dev/posts/astro-mermaid/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/astro-mermaid/</guid><description>本文介绍了如何基于 rehype-mermaid 插件为 Astro 提供 Mermaid 集成。</description><pubDate>Mon, 30 Sep 2024 11:46:19 GMT</pubDate><content:encoded>&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;Mermaid 是一个基于 JavaScript 的图表绘制工具，其可以通过解析类 Markdown 的语法来实现图表的创建和动态修改[^mermaid]。&lt;/p&gt;
&lt;p&gt;Astro 本身并没有提供 Mermaid 相关的第一方集成（&lt;a href=&quot;https://github.com/withastro/astro/issues/4433&quot;&gt;withastro/astro#4433&lt;/a&gt;），因此为了在 Astro 中支持相关的功能，首先需要简单了解一下 Astro 将 Markdown 解析并渲染成 HTML 文本的过程。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart LR
  subgraph &quot;Input&quot;
    Markdown
  end

  subgraph &quot;Unified&quot;
    direction TB
    subgraph &quot;Remark&quot;
      Parser --&amp;gt;|mdast| remark-rehype
    end

    subgraph &quot;Rehype&quot;
      direction LR
      rehype-shiki --&amp;gt;|hast| custom-rehype-plugins
      custom-rehype-plugins --&amp;gt;|hast| Compiler
    end
  end

  subgraph &quot;Output&quot;
    HTML
  end

  Markdown --&amp;gt;|string| Remark
  Remark --&amp;gt;|hast| Rehype
  Rehype --&amp;gt;|string| HTML
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上图展示了 Astro 使用 Remark 与 Rehype 插件渲染 Markdown 文件的部分过程（本文不会涵盖具体的 &lt;a href=&quot;https://github.com/unifiedjs/unified&quot;&gt;&lt;code&gt;unified&lt;/code&gt;&lt;/a&gt; 相关的抽象语法树解析过程，如有兴趣可以参考相关的文章[^unified-markdown]），具体包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;通过 Remark 将输入的 Markdown 文本转换成 Markdown 抽象语法树 &lt;code&gt;mdast&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;通过 &lt;code&gt;remark-rehype&lt;/code&gt; 插件将 &lt;code&gt;mdast&lt;/code&gt; 转换成超文本抽象语法树 &lt;code&gt;hast&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;若 &lt;code&gt;syntaxHighlight&lt;/code&gt; 为 &lt;code&gt;true&lt;/code&gt;，通过 Astro 自定义 &lt;code&gt;rehype&lt;/code&gt; 插件 &lt;code&gt;rehype-shiki&lt;/code&gt; 以 &lt;a href=&quot;https://shiki.matsu.io/guide/transformers&quot;&gt;Shiki Transformer&lt;/a&gt; 的形式支持 Shiki 相关的语法高亮；&lt;/li&gt;
&lt;li&gt;运行其它配置中的自定义 &lt;code&gt;rehype&lt;/code&gt; 插件；&lt;/li&gt;
&lt;li&gt;通过编译器（&lt;code&gt;rehype-stringify&lt;/code&gt;）将 &lt;code&gt;hast&lt;/code&gt; 重新编译成文本并作为输出导出。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// Replace &quot;shiki&quot; class naming with &quot;astro-code&quot;
node.properties.class = classValue.replace(/shiki/g, &apos;astro-code&apos;);

// Add data-language attribute
node.properties.dataLanguage = lang;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在默认情况下，Astro 的 &lt;code&gt;rehype-shiki&lt;/code&gt; 会将 Mermaid 代码块直接转换为 &lt;code&gt;pre.astro-code&lt;/code&gt; 元素作为普通的代码片段渲染。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;pre class=&quot;astro-code github-dark&quot; style=&quot;background-color:#24292e;color:#e1e4e8; overflow-x: auto;&quot; tabindex=&quot;0&quot; data-language=&quot;mermaid&quot;&amp;gt;
  &amp;lt;code&amp;gt;
  &amp;lt;/code&amp;gt;
&amp;lt;/pre&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此，一个比较自然的想法就是引入一个 &lt;code&gt;rehype&lt;/code&gt; 插件来提供对 Mermaid 相关功能解析的支持。&lt;/p&gt;
&lt;h2&gt;使用 &lt;code&gt;rehype-mermaid&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/remcohaszing/rehype-mermaid&quot;&gt;&lt;code&gt;remcohaszing/rehype-mermaid&lt;/code&gt;&lt;/a&gt; 是一个支持渲染 Mermaid 图像的 &lt;code&gt;rehype&lt;/code&gt; 插件，其能够将 &lt;code&gt;&amp;lt;pre class=&quot;mermaid&quot;&amp;gt;&lt;/code&gt; 与 &lt;code&gt;&amp;lt;code class=&quot;language-mermaid&quot;&amp;gt;&lt;/code&gt; 元素替换为渲染后的图像。包含 Mermaid 代码块的 Markdown 文件在经过 &lt;code&gt;remark-rehype&lt;/code&gt; 插件处理后即会生成符合前述格式的 HTML 代码。&lt;/p&gt;
&lt;h3&gt;安装 &lt;code&gt;rehype-mermaid&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;安装 &lt;code&gt;rehype-mermaid&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpm add rehype-mermaid
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然而，安装 &lt;code&gt;rehype-mermaid&lt;/code&gt; 后在 &lt;code&gt;markdown.rehypePlugins&lt;/code&gt; 中直接配置是没办法让其起作用的。如同前文中所提及的，用户的自定义 &lt;code&gt;rehype&lt;/code&gt; 插件是在 Astro 第一方的 Shiki 插件作用之后的，此时代码高亮相关的片段已经被处理过转换为 &lt;code&gt;pre.astro-code&lt;/code&gt; 元素，&lt;code&gt;rehype-mermaid&lt;/code&gt; 插件无法正常识别并渲染。&lt;/p&gt;
&lt;h3&gt;更新 Astro 配置&lt;/h3&gt;
&lt;p&gt;为了在支持 Shiki 代码高亮的同时启用 Mermaid 支持，需要对 Astro 配置进行更新，如下所示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { defineConfig } from &quot;astro/config&quot;;
import { rehypeShiki } from &quot;@astrojs/markdown-remark&quot;;
import rehypeMermaid from &quot;rehype-mermaid&quot;;

export default defineConfig({
  markdown: {
    rehypePlugins: [
      rehypeMermaid,
      rehypeShiki,
    ],
    syntaxHighlight: false,
  },
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只需要在 Astro 的配置中关闭默认的语法高亮支持，在启用 &lt;code&gt;rehypeMermaid&lt;/code&gt; 之后重新引入 &lt;code&gt;rehypeShiki&lt;/code&gt; 即可。需要注意的是如果采用这种方式的话，原先所有 &lt;code&gt;shiki&lt;/code&gt; 相关的配置都应该作为 &lt;code&gt;rehypeShiki&lt;/code&gt; 的参数一起传入，如 &lt;code&gt;[rehypeShiki, { theme: &quot;github-dark&quot; }]&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;此外，由于 &lt;code&gt;rehype-mermaid&lt;/code&gt; 依赖 &lt;code&gt;playwright&lt;/code&gt;，如果本地或者 CI 环境没有相关的浏览器二进制文件的话可能会产生如下错误：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;browserType.launch: Executable doesn&apos;t exist at /Users/rudeigerc/Library/Caches/ms-playwright/chromium-1134/chrome-mac/Chromium.app/Contents/MacOS/Chromium
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时通过 &lt;code&gt;playwright-core&lt;/code&gt; 人工安装相对应的浏览器即可：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pnpx playwright-core install --with-deps chromium
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/withastro/astro/issues/4433&quot;&gt;withastro/astro#4433&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/remcohaszing/rehype-mermaid&quot;&gt;remcohaszing/rehype-mermaid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.mono0x.net/2024/02/11/astro-mermaid/&quot;&gt;Astro のブログに Mermaid を導入した | monolithic kernel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://t28.dev/blog/render-mermaid-on-astro-site&quot;&gt;Astro 製のサイトで Markdown 内に書いた Mermaid のダイアグラムを描画する | t28.dev&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;[^mermaid]: &lt;a href=&quot;https://mermaid.js.org/&quot;&gt;Mermaid | Diagramming and charting tool&lt;/a&gt;
[^unified-markdown]: &lt;a href=&quot;https://diygod.cc/unified-markdown&quot;&gt;如何优雅编译一个 Markdown 文档 - DIYgod&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Astro</category></item><item><title>排查创建 Kubernetes 自定义资源时缺失字段的问题</title><link>https://rudeigerc.dev/posts/kubernetes-crd-missing-fields/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/kubernetes-crd-missing-fields/</guid><description>本文介绍了排查在创建 Kubernetes 自定义资源时缺失字段的问题的症状、原因以及解决方案。</description><pubDate>Mon, 16 Sep 2024 13:16:44 GMT</pubDate><content:encoded>&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;Volcano&lt;a href=&quot;%5BVolcano%5D(https://volcano.sh/)&quot;&gt;^volcano&lt;/a&gt; 是在 Kubernetes 上运行高性能工作负载的容器批量计算引擎，其定义了 &lt;code&gt;Job&lt;/code&gt; CRD 用于描述 Volcano 中作业的执行。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: batch.volcano.sh/v1alpha1
kind: Job
metadata:
  name: test-job
spec:
  minAvailable: 3
  schedulerName: volcano
  priorityClassName: high-priority
  queue: default
  tasks:
    - replicas: 6
      name: &quot;default-nginx&quot;
      template:
        metadata:
          name: web
        spec:
          containers:
            - image: nginx
              imagePullPolicy: IfNotPresent
              name: nginx
              resources:
                requests:
                  cpu: &quot;1&quot;
          restartPolicy: OnFailure
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Source: &lt;a href=&quot;https://volcano.sh/en/docs/vcjob/#example&quot;&gt;VolcanoJob | Volcano&lt;/a&gt;)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: batch.volcano.sh/v1alpha1
kind: Job
metadata:
  name: test-job
spec:
  minAvailable: 3
  schedulerName: volcano
  priorityClassName: high-priority
  queue: default
+ extraJobField: extraJobField
  tasks:
    - replicas: 6
      name: &quot;default-nginx&quot;
      template:
        metadata:
          name: web
        spec:
          containers:
            - image: nginx
              imagePullPolicy: IfNotPresent
              name: nginx
              resources:
                requests:
                  cpu: &quot;1&quot;
          restartPolicy: OnFailure
+         extraTaskField: extraTaskField
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为了满足内部业务的需要，我们对 Volcano 的 Job CRD 进行了特定的修改，&lt;strong&gt;先后在两次更新中&lt;/strong&gt;于 &lt;code&gt;.spec.extraJobField&lt;/code&gt; 与 &lt;code&gt;.spec.tasks[0].extraTaskField&lt;/code&gt; 添加了额外的字段。&lt;/p&gt;
&lt;h2&gt;症状&lt;/h2&gt;
&lt;p&gt;在通过 &lt;code&gt;client-go&lt;/code&gt; 创建 Volcano Job 时，发现创建出的 Job 虽然有 &lt;code&gt;.spec.extraJobField&lt;/code&gt; 字段，但是缺失了 &lt;code&gt;.spec.tasks[0].extraTaskField&lt;/code&gt; 字段。&lt;/p&gt;
&lt;h2&gt;原因&lt;/h2&gt;
&lt;p&gt;首先，我们检查了集群内部署的 CRD 的版本，发现是已经添加过 &lt;code&gt;.spec.tasks[0].extraTaskField&lt;/code&gt; 字段的最新版本。此外，为了排除是否是 &lt;code&gt;controller-runtime&lt;/code&gt; 或是 &lt;code&gt;client-go&lt;/code&gt; 本身相关的问题，我们尝试使用 &lt;code&gt;kubectl&lt;/code&gt; 的方式基于 YAML 文件创建 Volcano Job，但是没有触发前述问题，因此我们初步判断问题可能出现在请求经过 API Server 的准入阶段的时候。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flowchart LR
  object((Object)) --&amp;gt; apiServer[API Server]
  subgraph apiServer[API Server]
    direction LR
    Authentication --&amp;gt; Authotization
    Authotization --&amp;gt; mutating
    subgraph admissionControl[Admission Control]
      mutating[Mutating Webhooks] --&amp;gt; validating[Validating Webhooks]
    end
  end
  validating --&amp;gt; etcd[(etcd)]

  click Authentication &quot;https://kubernetes.io/docs/reference/access-authn-authz/authentication/&quot;
  click Authotization &quot;https://kubernetes.io/docs/reference/access-authn-authz/authorization/&quot;
  click admissionControl &quot;https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上图描述了请求 Kubernetes API 的时候会经历的几个步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;身份验证（Authentication）[^authentication]&lt;/li&gt;
&lt;li&gt;授权（Authorization）[^authorization]&lt;/li&gt;
&lt;li&gt;准入控制（Admission Control）[^admission-controllers]
&lt;ol&gt;
&lt;li&gt;验证（Validating）&lt;/li&gt;
&lt;li&gt;变更（Mutating）&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Volcano 针对其定义的 CRD（Job、PodGroup 与 Queue）以及 Pod 都有定义 Validating 和 Mutating Webhook。在 Mutating Webhook 的过程中，该 Webhook 在一定条件下会对请求的资源对象进行修改。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/volcano-sh/volcano/blob/release-1.10/pkg/webhooks/admission/jobs/mutate/mutate_job.go#L83-L112&quot;&gt;&lt;code&gt;pkg/webhooks/admission/jobs/mutate/mutate_job.go#L83-L112&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Jobs mutate jobs.
func Jobs(ar admissionv1.AdmissionReview) *admissionv1.AdmissionResponse {
	...
	job, err := schema.DecodeJob(ar.Request.Object, ar.Request.Resource)
	if err != nil {
		return util.ToAdmissionResponse(err)
	}

	var patchBytes []byte
	switch ar.Request.Operation {
	case admissionv1.Create:
		patchBytes, _ = createPatch(job)
	default:
		err = fmt.Errorf(&quot;expect operation to be &apos;CREATE&apos; &quot;)
		return util.ToAdmissionResponse(err)
	}
	...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过断点与日志等调试方法，我们发现在调用 &lt;code&gt;schema.DecodeJob&lt;/code&gt; 的时候，解码出来的 &lt;code&gt;job&lt;/code&gt; 对象就已经缺失了 &lt;code&gt;.spec.tasks[0].extraTaskField&lt;/code&gt; 字段，由此可以推断此处 Task 相关的 CRD 的定义并不是最新的版本。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/volcano-sh/volcano/blob/release-1.10/pkg/webhooks/admission/jobs/mutate/mutate_job.go#L114-L142&quot;&gt;&lt;code&gt;pkg/webhooks/admission/jobs/mutate/mutate_job.go#L114-L142&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func createPatch(job *v1alpha1.Job) ([]byte, error) {
	...
	pathSpec := mutateSpec(job.Spec.Tasks, &quot;/spec/tasks&quot;, job)
	if pathSpec != nil {
		patch = append(patch, *pathSpec)
	}
	...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只有当调用 &lt;code&gt;mutateSpec&lt;/code&gt; 函数后返回的 &lt;code&gt;pathSpec&lt;/code&gt; 不为 &lt;code&gt;nil&lt;/code&gt; 的时候才会将其添加到 &lt;code&gt;patch&lt;/code&gt; 中。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/volcano-sh/volcano/blob/release-1.10/pkg/webhooks/admission/jobs/mutate/mutate_job.go#L185-L223&quot;&gt;&lt;code&gt;pkg/webhooks/admission/jobs/mutate/mutate_job.go#L185-L223&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func mutateSpec(tasks []v1alpha1.TaskSpec, basePath string, job *v1alpha1.Job) *patchOperation {
	// TODO: Enable this configuration when dependOn supports coexistence with the gang plugin
	// if _, ok := job.Spec.Plugins[mpi.MpiPluginName]; ok {
	// 	mpi.AddDependsOn(job)
	// }
	patched := false
	for index := range tasks {
		// add default task name
		taskName := tasks[index].Name
		if len(taskName) == 0 {
			patched = true
			tasks[index].Name = v1alpha1.DefaultTaskSpec + strconv.Itoa(index)
		}

		if tasks[index].Template.Spec.HostNetwork &amp;amp;&amp;amp; tasks[index].Template.Spec.DNSPolicy == &quot;&quot; {
			patched = true
			tasks[index].Template.Spec.DNSPolicy = v1.DNSClusterFirstWithHostNet
		}

		if tasks[index].MinAvailable == nil {
			patched = true
			minAvailable := tasks[index].Replicas
			tasks[index].MinAvailable = &amp;amp;minAvailable
		}

		if tasks[index].MaxRetry == 0 {
			patched = true
			tasks[index].MaxRetry = defaultMaxRetry
		}
	}
	if !patched {
		return nil
	}
	return &amp;amp;patchOperation{
		Op:    &quot;replace&quot;,
		Path:  basePath,
		Value: tasks,
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 &lt;code&gt;mutateSpec&lt;/code&gt; 函数中，对于每一个 Task 都会进行一系列的检查，如果有字段缺失会对其进行默认值的填充，具体而言：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;tasks[].Name&lt;/code&gt; 为空时，填充 &lt;code&gt;v1alpha1.DefaultTaskSpec&lt;/code&gt;（即 &lt;code&gt;default&lt;/code&gt;） + &lt;code&gt;strconv.Itoa(index)&lt;/code&gt; 为默认值；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tasks[].Template.Spec.HostNetwork&lt;/code&gt; 不为 &lt;code&gt;nil&lt;/code&gt; 且 &lt;code&gt;tasks[].Template.Spec.DNSPolicy&lt;/code&gt; 为空时，填充 &lt;code&gt;v1.DNSClusterFirstWithHostNet&lt;/code&gt; 为 &lt;code&gt;DNSPolicy&lt;/code&gt; 的默认值；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tasks[].MinAvailable&lt;/code&gt; 为 &lt;code&gt;nil&lt;/code&gt; 时，填充 &lt;code&gt;tasks[].Replicas&lt;/code&gt; 为默认值；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tasks[].MaxRetry&lt;/code&gt; 为 &lt;code&gt;0&lt;/code&gt; 时，填充 &lt;code&gt;defaultMaxRetry&lt;/code&gt;（即 &lt;code&gt;3&lt;/code&gt;） 为默认值。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在以上任一分支触发后，即会返回 patch 操作的结果，此处是通过 &lt;code&gt;replace&lt;/code&gt; 操作对 &lt;code&gt;.spec.tasks&lt;/code&gt; 字段整体进行替换的。&lt;/p&gt;
&lt;h3&gt;小结&lt;/h3&gt;
&lt;p&gt;综上，实际上产生问题的原因主要是由于在更新 CRD 之后没有重新构建并部署 Webhook，导致 Mutating Webhook 在解码 Task 的时候缺失了字段，被解码的对象&lt;strong&gt;仅在&lt;/strong&gt;填充默认值的时候才会被使用并加入到 Patch 中。&lt;/p&gt;
&lt;p&gt;后续在验证过程中，我们发现问题并不能被稳定复现，其主要原因在于使用 &lt;code&gt;kubectl&lt;/code&gt; 创建 Volcano Job 时所创建的 YAML 文件的 Sepcification 是直接通过 &lt;code&gt;kubectl get vcjob -oyaml&lt;/code&gt; 得到的，因此所有的默认值已经被填充了，在经过 Mutating Webhook 的时候相关的字段不会被覆盖。&lt;/p&gt;
&lt;p&gt;此外，由于集群中的 CRD 已经被更新了，即 &lt;code&gt;.spec.extraJobField&lt;/code&gt; 与 &lt;code&gt;.spec.tasks[0].extraTaskField&lt;/code&gt; 两个字段都存在，所以在 API Server 验证的时候皆不会产生问题。&lt;/p&gt;
&lt;h2&gt;解决方案&lt;/h2&gt;
&lt;p&gt;基于修改过的 CRD API 定义重新构建镜像并部署 Webhook 服务即可。&lt;/p&gt;
&lt;p&gt;[^authentication]: &lt;a href=&quot;https://kubernetes.io/docs/reference/access-authn-authz/authentication/&quot;&gt;Authenticating | Kubernetes&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^authorization]: &lt;a href=&quot;https://kubernetes.io/docs/reference/access-authn-authz/authorization/&quot;&gt;Authenticating | Kubernetes&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^admission-controllers]: &lt;a href=&quot;https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/&quot;&gt;Admission Controllers | Kubernetes&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Troubleshooting</category></item><item><title>排查在 Tekton 中使用来自自签名私有镜像仓库的镜像时的证书问题</title><link>https://rudeigerc.dev/posts/tekton-translating-taskspec-to-pod-unknown-authority/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/tekton-translating-taskspec-to-pod-unknown-authority/</guid><description>本文介绍了排查在 Tekton 中使用来自自签名私有镜像仓库的镜像时的证书问题的症状、原因以及解决方案。</description><pubDate>Mon, 09 Sep 2024 08:56:35 GMT</pubDate><content:encoded>&lt;h2&gt;背景&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Tekton Pipelines&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tasks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://hub.tekton.dev/tekton/task/git-clone&quot;&gt;&lt;code&gt;git-clone&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://hub.tekton.dev/tekton/task/kaniko&quot;&gt;&lt;code&gt;kaniko&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;症状&lt;/h2&gt;
&lt;p&gt;在 Tekton Pipelines 中运行 &lt;code&gt;kaniko&lt;/code&gt; Task 时，出现以下错误：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;failed to create task run pod &quot;task-run&quot;: translating TaskSpec to Pod: error getting image manifest: Get https://registry.private/v2/: x509: certificate signed by unknown authority. Maybe missing or invalid Task default/task
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;原因&lt;/h2&gt;
&lt;p&gt;看到 x509 相关的报错的时候，首先会联想到是证书相关的问题，因为公司内部的私有镜像仓库使用的是自签名证书，可以初步判断是在 Tekton reconcile 过程中的某个步骤没有指定 CA 证书，从而导致了前述错误。&lt;/p&gt;
&lt;p&gt;由于先前在使用其它 Task 的时候没有触发过相关问题，仅在 &lt;code&gt;kaniko&lt;/code&gt; 中会出现，所以起初以为是 &lt;code&gt;kaniko&lt;/code&gt; 的 Task 本身的问题，尝试了一些修改 &lt;code&gt;kaniko&lt;/code&gt; 运行参数的方法都没办法解决。
后来通过在 &lt;a href=&quot;https://github.com/tektoncd/pipeline&quot;&gt;tektoncd/pipeline&lt;/a&gt; 中搜索相关的 Issue 发现，在 &lt;a href=&quot;https://github.com/tektoncd/pipeline/issues/3105&quot;&gt;tektoncd/pipeline#3105&lt;/a&gt; 中，Tekton 的维护者 &lt;a href=&quot;https://github.com/vdemeester&quot;&gt;@vdemeester&lt;/a&gt; 在评论中提到：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It is correct &lt;em&gt;in a way&lt;/em&gt;. This error happens &lt;strong&gt;before&lt;/strong&gt; pulling the image. Tekton is doing some entrypoint magic (cc @bobcatfish @imjasonh for their talk link 😁) : in case no &lt;code&gt;command&lt;/code&gt; is specified, the controller will try to fetch the image configuration to get the command (&lt;code&gt;entrypoint&lt;/code&gt; is the &quot;docker&quot; sense)… And this is where it fails, because the tekton controller might not have the certificates available.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;前述错误的根本原因在于，Tekton 会对 entrypoint 进行一些 hacking，因此当 Task 中未指定 &lt;code&gt;command&lt;/code&gt; 的时候，Controller 会尝试访问镜像元数据获取镜像配置中设置的 &lt;code&gt;entrypoint&lt;/code&gt;，当没有对应的 CA 证书的时候就会报错。&lt;/p&gt;
&lt;h3&gt;Walkthrough&lt;/h3&gt;
&lt;p&gt;我们可以通过嵌套的 error traceback 大致上判断错误发生的位置，大概的调用链如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;pkg/reconciler/taskrun/taskrun.go#L233&lt;/code&gt; taskrun.Reconciler.ReconcileKind：Tekton &lt;code&gt;TaskRun&lt;/code&gt; 的主要 Reconcile 逻辑&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pkg/reconciler/taskrun/taskrun.go#L221&lt;/code&gt; taskrun.Reconciler.ReconcileKind&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pkg/reconciler/taskrun/taskrun.go#L660&lt;/code&gt; taskrun.Reconciler.reconcile&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pkg/reconciler/taskrun/taskrun.go#L656&lt;/code&gt; taskrun.Reconciler.reconcile&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pkg/reconciler/taskrun/taskrun.go#L905&lt;/code&gt; taskrun.Reconciler.createPod：根据 &lt;code&gt;Task&lt;/code&gt; 的配置构建 Pod&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pkg/reconciler/taskrun/taskrun.go#L899&lt;/code&gt; taskrun.Reconciler.createPod&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pkg/pod/pod.go#L265&lt;/code&gt; pod.Build：根据 &lt;code&gt;TaskRun&lt;/code&gt; 与 &lt;code&gt;TaskSpec&lt;/code&gt; 构建 Pod&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pkg/pod/entrypoint_lookup.go#L75&lt;/code&gt; pod.resolveEntrypoints：针对没有指定 &lt;code&gt;command&lt;/code&gt; 的所有 &lt;code&gt;step&lt;/code&gt;，获取对应镜像的 &lt;code&gt;entrypoint&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pkg/pod/entrypoint_lookup_impl.go#L82&lt;/code&gt; pod.entrypointCache.get&lt;/li&gt;
&lt;li&gt;&lt;code&gt;google/go-containerregistry/pkg/v1/remote/descriptor.go#L73&lt;/code&gt; remote.Get：基于镜像 Reference 返回对应的 Descriptor&lt;/li&gt;
&lt;li&gt;&lt;code&gt;google/go-containerregistry/pkg/v1/remote/puller.go#L94&lt;/code&gt; remote.puller.Get&lt;/li&gt;
&lt;li&gt;&lt;code&gt;google/go-containerregistry/pkg/v1/remote/fetcher.go#L103&lt;/code&gt; remote.fetcher.get&lt;/li&gt;
&lt;li&gt;&lt;code&gt;google/go-containerregistry/pkg/v1/remote/fetcher.go#L129&lt;/code&gt; remote.fetcher.fetchManifest：通过镜像 Reference 向镜像存储发送请求获取镜像相关的元数据&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此，实际上是在最后一步通过 &lt;code&gt;f.client.Do(req.withContext(ctx))&lt;/code&gt; 发送 HTTP 请求的时候触发了 x509 相关的错误。&lt;/p&gt;
&lt;h3&gt;小结&lt;/h3&gt;
&lt;p&gt;通过查询相关的 Issue，以及分析错误的调用链，我们可以判断前述问题主要是由于 Tekton 在基于 &lt;code&gt;Task&lt;/code&gt; 构建对应的 Pod 的时候，针对没有指定 &lt;code&gt;command&lt;/code&gt; 的 &lt;code&gt;step&lt;/code&gt; 的时候会去获取相应的镜像的 &lt;code&gt;entrypoint&lt;/code&gt; 填充，由此导致了在发送请求的时候 x509 证书验证错误。&lt;/p&gt;
&lt;h2&gt;解决方案&lt;/h2&gt;
&lt;p&gt;在 &lt;a href=&quot;https://github.com/tektoncd/pipeline/pull/2787&quot;&gt;tektoncd/pipeline#2787&lt;/a&gt; 之后，可以在 &lt;code&gt;config-registry-cert&lt;/code&gt; ConfigMap 中添加自签名的 CA 证书。&lt;/p&gt;
&lt;p&gt;通过 &lt;code&gt;openssl&lt;/code&gt; 获取特定域名的 CA 证书：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo | openssl s_client -connect example.com:443 2&amp;gt;/dev/null | openssl x509
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将证书内容添加到 &lt;code&gt;config-registry-cert&lt;/code&gt; ConfigMap 中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kubectl edit cm -n tekton-pipelines config-registry-cert
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: v1
kind: ConfigMap
metadata:
  name: config-registry-cert
  namespace: tekton-pipelines
  labels:
    app.kubernetes.io/instance: default
    app.kubernetes.io/part-of: tekton-pipelines
data:
  cert: |
    -----BEGIN CERTIFICATE-----
    ...
    -----END CERTIFICATE-----
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Reference: &lt;a href=&quot;https://github.com/tektoncd/pipeline/blob/main/config/config-registry-cert.yaml&quot;&gt;pipeline/config/config-registry-cert.yaml&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;随后重启 Tekton Pipeline 对应的 Deployment 即可。&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tekton.dev/vault/pipelines-main/additional-configs/#configuring-self-signed-cert-for-private-registry&quot;&gt;Additional Configuration Options | Tekton&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;相关 Issue：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/tektoncd/pipeline/issues/1171&quot;&gt;tektoncd/pipeline#1171 - x509 certificate signed by unknown authority&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/tektoncd/pipeline/issues/3105&quot;&gt;tektoncd/pipeline#3105 - translating TaskSpec to Pod: error getting image manifest&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;相关仓库：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/golang/go&quot;&gt;golang/go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/tektoncd/pipeline&quot;&gt;tektoncd/pipeline&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/google/go-containerregistry&quot;&gt;google/go-containerregistry&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Troubleshooting</category></item><item><title>Kubernetes Community Days Shanghai 2024 Recap</title><link>https://rudeigerc.dev/posts/kcd-shanghai-2024/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/kcd-shanghai-2024/</guid><description>2024 年 4 月 20 日，Kubernetes Community Days Shanghai 2024 在上海举行。本文将会对一些我现场观看的议题进行简单的介绍与梳理。</description><pubDate>Sat, 27 Apr 2024 16:05:28 GMT</pubDate><content:encoded>&lt;p&gt;2024 年 4 月 20 日，Kubernetes Community Days Shanghai 2024 在上海举行。由于今年的 KubeCon China 会在 7 月份于香港举行，因此这次的活动某种意义上算是今年 KubeCon China 无法在上海举办的替代。在下文中，我将会对一些我现场观看的议题进行简单的介绍与梳理，主要包括 Kubernetes 调度与资源管理以及 LLM 相关的议题。&lt;/p&gt;
&lt;h2&gt;Keynotes&lt;/h2&gt;
&lt;h3&gt;Godel Scheduler：字节开源内部超大规模在离线统一调度器 - 任玉泉，字节跳动&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/godel.png&quot; alt=&quot;godel&quot; /&gt;&lt;/p&gt;
&lt;p&gt;(Source: https://www.cncf.io/blog/2024/04/02/godel-scheduler-open-sourced-a-unified-scheduler-for-online-and-offline-workloads/)&lt;/p&gt;
&lt;p&gt;演讲者为来自字节跳动的任玉泉，他主要介绍了字节跳动内部基于 Kubernetes 的大规模在离线统一调度系统 &lt;a href=&quot;https://github.com/kubewharf/godel-scheduler&quot;&gt;Gödel&lt;/a&gt;[^godel]。和先前维护 Kubernetes 与 YARN 两套在离线调度系统的方案相比，Gödel 基于统一的调度策略实现了在线与离线工作负载的统一调度，以期减少维护的成本并提高集群资源利用率。&lt;/p&gt;
&lt;p&gt;Gödel 主要由以下三个组件组成：Dispatcher、Scheduler 与 Binder，其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dispatacher 作为调度生命周期的入口接收创建请求，基于一定的策略维护了优先级队列，并将请求分发给不同的调度器。&lt;/li&gt;
&lt;li&gt;Scheduler 负责接收来自 Dispatcher 的请求，并做出调度决策。Gödel 的调度器使用了类似 Google 的 Omega[^omega] 的共享状态机制，由多个调度器共享相同的调度视野，以获得相比于单体调度器更好的调度性能。&lt;/li&gt;
&lt;li&gt;Binder 使用乐观并发控制机制，将 Pod 绑定到调度器指定的节点上。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;实际上我在演讲之前就已经通过他们团队先前在 SoCC 2023 Industry Track 发表的论文对 Gödel 有了一定的了解，当时我印象中这应该是 SoCC 第一次有 Industry Track 所以特别关注过。文章本身我认为是个不错的工程实践，我印象相当深的一点是，在实验评估中他们指出在 10000 个节点 1 秒钟提交 2800 个 Pod 的情况下，使用 Volcano 的 Kubernetes 集群的调度性能是相当灾难性的，仅为 12 Pods/s，与之相比 YARN 是 1000 Pods/s，Gödel 是 1950 Pods/s。&lt;/p&gt;
&lt;p&gt;不过当初看到这篇文章的时候一个很自然的疑问就出现在了我的脑中，具体可以参见下文 &lt;a href=&quot;#%E5%9F%BA%E4%BA%8E-nri-%E5%AE%9E%E7%8E%B0%E7%B2%BE%E7%BB%86%E5%8C%96%E4%B8%94%E5%8F%AF%E6%8F%92%E6%8B%94%E7%9A%84%E5%AE%B9%E5%99%A8%E8%B5%84%E6%BA%90%E7%AE%A1%E7%90%86---%E6%9B%B9%E8%B4%BA%E5%AD%97%E8%8A%82%E8%B7%B3%E5%8A%A8--%E4%BB%BB%E5%BC%BAintel&quot;&gt;Katalyst 相关的总结&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;相关仓库：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kubewharf/godel-scheduler&quot;&gt;kubewharf/godel-scheduler&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;相关文章：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cncf.io/blog/2024/04/02/godel-scheduler-open-sourced-a-unified-scheduler-for-online-and-offline-workloads/&quot;&gt;Gödel Scheduler open-sourced: a unified scheduler for online and offline workloads | CNCF&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;相关论文：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Wu Xiang, Yakun Li, Yuquan Ren, Fan Jiang, Chaohui Xin, Varun Gupta, Chao Xiang, Xinyi Song, Meng Liu, Bing Li, Kaiyang Shao, Chen Xu, Wei Shao, Yuqi Fu, Wilson Wang, Cong Xu, Wei Xu, Caixue Lin, Rui Shi, and Yuming Liang. 2023. Gödel: Unified Large-Scale Resource Management and Scheduling at ByteDance. In Proceedings of the 2023 ACM Symposium on Cloud Computing (SoCC &apos;23). Association for Computing Machinery, New York, NY, USA, 308–323. https://doi.org/10.1145/3620678.3624663&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;云原生基础设施 &amp;amp; 操作系统&lt;/h2&gt;
&lt;h3&gt;基于 NRI 实现精细化且可插拔的容器资源管理 - 曹贺，字节跳动 &amp;amp; 任强，Intel&lt;/h3&gt;
&lt;p&gt;两位演讲者分别是来自字节跳动的曹贺与来自 Intel 的任强，他们分别介绍了字节跳动开源的 Katalyst 以及其插件化的资源管理机制、CRI 扩展框架 NRI 以及 Katalyst 基于 NRI 的优化。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/containerd/nri&quot;&gt;NRI&lt;/a&gt; 是 &lt;a href=&quot;https://github.com/containerd/containerd&quot;&gt;Containerd&lt;/a&gt; 的子项目，一个 CRI 运行时插件扩展管理的通用框架，其允许用户自定义资源管理的插件，使得用户能够在容器的生命周期中对容器的配置进行一定程度的修改。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/kubewharf/katalyst-core&quot;&gt;Katalyst&lt;/a&gt; 是字节跳动所开源的细粒度资源管理与调度系统，有关 Katalyst 的介绍可参考我先前参加 KubeCon China 2023 时写的 &lt;a href=&quot;https://rudeigerc.dev/posts/kubecon-china-2023/&quot;&gt;Recap&lt;/a&gt;，演讲者同样是字节跳动的曹贺。在原先的设计中，Katalyst 在 Kubelet 中实现了 QoS Resource Manger（QRM）对 Kubelet 中自带的 CPU Manager 与 Memory Manager 进行了替换，以实现更细粒度的动态资源管理，并通过与 Topology Manager 集成实现了 NUMA 亲和的资源管理。然而，这种对 Kubelet 进行扩展的方式具有一定的侵入性，只能在使用 Kubewharf 发行版的 Kubernetes 集群中使用。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./images/nri.png&quot; alt=&quot;nri&quot; /&gt;&lt;/p&gt;
&lt;p&gt;因此，Katalyst 基于 NRI 机制将 QRM 从 Kubelet 中解耦，提出了 Out-of-Band Resource Manager（ORM）（&lt;a href=&quot;https://github.com/kubewharf/katalyst-core/issues/430&quot;&gt;kubewharf/katalyst-core#430&lt;/a&gt;）。Katalyst 实现了 Katalyst Agent 作为 NRI Plugin 在节点上作为独立的进程运行，在其中实现了 Out-of-Band Resource Manager，复用了先前 QRM 框架中的部分插件，并通过 NRI 与容器运行时通信，实现动态的容器资源管理，与先前的实现机制相比具有更好的可扩展性与可插拔性。&lt;/p&gt;
&lt;p&gt;值得一提的是，提问环节的时候，有位观众问了一个我想问的问题：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Katalyst 与 Gödel 的关系是什么？如何在使用 Katalyst 的同时指定 Gödel 为调度器？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;实际上我在看到 Gödel 的论文的时候就有这个疑问，虽然两者的出发点明显不同，Gödel 主要还是想整合 Kubernetes 和 YARN 的资源所以在上层抽象出了统一的调度结构，而 Katalyst 关注的是混部场景下的细粒度节点资源管理，但是无法回避的是两者的使用场景是有一定的重叠的，本质上都是针对复杂异构工作负载场景下大规模 Kubernetes 集群的资源管理与调度问题的框架。&lt;/p&gt;
&lt;p&gt;在这个问题的回答中，演讲者提到了两者的关系是互补的，可以根据实际场景选择使用。听到这个回答我就明白了，这两者应该就是字节内部不同的团队的解决方案吧，当时就和一起听这个 Talk 的朋友不约而同地笑了出来。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;相关仓库：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kubewharf/katalyst-core&quot;&gt;kubewharf/katalyst-core&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/containerd/nri&quot;&gt;containerd/nri&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/containers/nri-plugins&quot;&gt;containers/nri-plugins&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;相关 KubeCon 演讲：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://kccnceu2024.sched.com/event/1YeMY&quot;&gt;Beyond Default: Harnessing CPU Affinity for Enhanced Performance Across Your Workload Portfolio - Antti Kervinen, Intel &amp;amp; Dixita Narang, Google LLC&lt;/a&gt;, KubeCon EU 2024&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kccncna2023.sched.com/event/1R2nL&quot;&gt;Advancing Memory Management in Kubernetes: Next Steps with Memory QoS - Dixita Narang, Google &amp;amp; Antti Kervinen, Intel&lt;/a&gt;, KubeCon NA 2023&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kccncosschn2023.sched.com/event/1PTH5&quot;&gt;使用可插拔和可定制的智能运行时提升工作负载的QoS | Enhance Workload QoS with Pluggable and Customizable Smarter Runtimes - Rougang Han, Alibaba &amp;amp; Kang Zhang, Intel&lt;/a&gt;, KubeCon China 2023&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kccncna2022.sched.com/event/182JT&quot;&gt;NRI: Extending Containerd And CRI-O With Common Plugins - Krisztian Litkey, Intel &amp;amp; Mike Brown, IBM&lt;/a&gt;, KubeCon NA 2022&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kccnceu2021.sched.com/event/iE1Y&quot;&gt;Maximizing Workload&apos;s Performance With Smarter Runtimes - Krisztian Litkey &amp;amp; Alexander Kanevskiy, Intel&lt;/a&gt;, KubeCon EU 2021&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;AI &amp;amp; Cloud&lt;/h2&gt;
&lt;p&gt;虽然这个分论坛的标题是 AI &amp;amp; Cloud，不过基本上可以说是 LLM 的 Session，大部分的演讲全部都是围绕 LLM 展开的，甚至有相当学院派的学术分享。&lt;/p&gt;
&lt;h3&gt;释放 Stable Diffusion 无限可能：基于 Kubernetes 的大规模部署最佳实践 - 于昺蛟，亚马逊云科技 &amp;amp; 郑予彬，亚马逊云科技&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/stable-diffusion-on-amazon-eks.png&quot; alt=&quot;stable-diffusion-on-amazon-eks&quot; /&gt;&lt;/p&gt;
&lt;p&gt;(Source: https://aws-samples.github.io/stable-diffusion-on-eks/en/implementation-guide/architecture/architecture/)&lt;/p&gt;
&lt;p&gt;演讲者为来自亚马逊云科技的于昺蛟与郑予彬。演讲整体主要是在介绍如何基于 Amazon EKS 的生态系统来部署 Stable Diffusion 的解决方案，虽然使用了相当多的 AWS 的服务，不过整体架构都能在开源社区中找到对应的方案，因此我认为还是相当有参考价值的。我个人比较关注的是以下几点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;利用 &lt;a href=&quot;https://github.com/kedacore/keda&quot;&gt;KEDA&lt;/a&gt; 基于队列长度自动扩缩容器副本&lt;/li&gt;
&lt;li&gt;利用 &lt;a href=&quot;https://github.com/kubernetes-sigs/karpenter&quot;&gt;Karpenter&lt;/a&gt; 自动扩缩实例&lt;/li&gt;
&lt;li&gt;基于 &lt;a href=&quot;https://github.com/bottlerocket-os/bottlerocket&quot;&gt;Bottlerocket&lt;/a&gt; 实现容器镜像缓存（&lt;a href=&quot;https://aws.amazon.com/jp/blogs/containers/reduce-container-startup-time-on-amazon-eks-with-bottlerocket-data-volume/&quot;&gt;Reduce container startup time on Amazon EKS with Bottlerocket data volume&lt;/a&gt;）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;KEDA 与 Karpenter 我先前都有一定的了解，KEDA 相比社区的 Horizontal Pod Autoscaler 具有更好的扩展能力，同时支持 Deployment 由零到一的扩缩容，而 Karpenter 也是相当成熟的节点自动扩缩容的项目，不过我个人比较少接触这种使用场景所以没有真正使用过。Bottlerocket 是我在此次演讲中第一次听说的项目，还有待更深入的了解，我了解过的一般针对镜像缓存与加速的解决方案似乎都是基于 &lt;a href=&quot;https://github.com/containerd/stargz-snapshotter&quot;&gt;containerd/stargz-snapshotter&lt;/a&gt; 或 &lt;a href=&quot;https://github.com/dragonflyoss/Dragonfly2&quot;&gt;dragonflyoss/Dragonfly2&lt;/a&gt; 的。&lt;/p&gt;
&lt;p&gt;由于在这场演讲的时候我到达的比较晚，只听到了中后半段，让我印象相当深刻的是演讲者的演讲风格，真的很像在讲单口相声，我还是第一次在技术分享上看到这样的风格的分享，挺有意思的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;相关仓库：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/aws-samples/stable-diffusion-on-eks&quot;&gt;aws-samples/stable-diffusion-on-eks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kedacore/keda&quot;&gt;kedacore/keda&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kubernetes-sigs/karpenter&quot;&gt;kubernetes-sigs/karpenter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/aws/karpenter-provider-aws&quot;&gt;aws/karpenter-provider-aws&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/bottlerocket-os/bottlerocket&quot;&gt;bottlerocket-os/bottlerocket&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;相关文章：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://aws.amazon.com/cn/blogs/china/stable-diffusion-image-generation-solution-based-on-amazon-eks/&quot;&gt;云端生成式 AI – 基于 Amazon EKS 的 Stable Diffusion 图像生成方案&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;用 Operator 将 LLM 和 Gateway 结合的更容易 - 张晋涛，Kong Inc.&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/ai-gateway.png&quot; alt=&quot;ai-gateway&quot; /&gt;&lt;/p&gt;
&lt;p&gt;(Source: https://docs.konghq.com/gateway/latest/get-started/ai-gateway/)&lt;/p&gt;
&lt;p&gt;演讲者为来自 Kong Inc. 的张晋涛。张晋涛首先介绍了调用 LLM 的 API 的时候存在的问题，包括安全性、成本、可靠性、可观测性与配额等多方面的问题，并以此引入了 AI Gateway 的概念。我认为 AI Gateway 本质上就是在正常的 Gateway 的基础上为其赋予了 LLM 相关的能力，这样就可以在利用传统 Gateway 本身优势的同时，也能够更好地支持 LLM 服务。此外，他还介绍了 Kong 所开源的 &lt;a href=&quot;https://github.com/Kong/gateway-operator&quot;&gt;Kong Gateway Operator&lt;/a&gt;，通过 CRD 以及 Kubernetes 的 Gateway API 以更加便利的形式管理 AI Gateway 的资源，更好地与 Kubernetes 的生态系统集成。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: gateway-operator.konghq.com/v1alpha1
kind: AIGateway
metadata:
  name: kong-aigateway
spec:
  gatewayClassName: kong-ai-gateways
  largeLanguageModels:
    cloudHosted:
      - identifier: marketing-team-classic-chatgpt
        model: gpt-3.5-turbo-instruct
        promptType: completions
        aiCloudProvider:
          name: openai
      - identifier: devteam-chatgpt
        model: gpt-4
        promptType: chat
        defaultPrompts:
          - role: system
            content: &quot;You are a helpful assistant who responds in the style of Sherlock Holmes.&quot;
        defaultPromptParams:
          # temperature: &quot;0.5&quot; # higher confidence predictions
          maxTokens: 50 # shorter responses
          # topP: &quot;0.9&quot; # higher diversity
        aiCloudProvider:
          name: openai
  cloudProviderCredentials:
    name: acme-ai-cloud-providers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Source: https://github.com/Kong/gateway-operator/blob/main/config/samples/aigateway.yaml)&lt;/p&gt;
&lt;p&gt;近期我在公司内部部署 LLM 服务的时候也有遇到相似的问题（当然纯粹的内部服务的话有一些前文提及的问题是可以不必考虑的），
虽然目前也有如 &lt;a href=&quot;https://github.com/lm-sys/FastChat&quot;&gt;lm-sys/FastChat&lt;/a&gt; 这样的项目在应用层实现了简易的 Gateway 的功能，但是在 Kubernetes 生产环境中确实还是需要更加完善的解决方案，包括身份验证与授权的集成、可观测性的集成等，我认为 Kong 所给出的基于 Gateway 的解决方案是个相当不错的思路。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;相关仓库：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Kong/kong&quot;&gt;Kong/kong&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Kong/gateway-operator&quot;&gt;Kong/gateway-operator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;AI 原生数据库与 RAG - 张颖峰，InfiniFlow&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;./images/infiniflow.png&quot; alt=&quot;infiniflow&quot; /&gt;&lt;/p&gt;
&lt;p&gt;演讲者为来自 InfiniFlow 的张颖峰，他首先介绍了基于向量数据库的 RAG 的工作流程，然后针对目前长上下文的 LLM 与 RAG 进行了对比，他认为：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;不论 LLM“大海捞针”能力有多强，RAG 永远不可少，但不是当下的 RAG。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;他指出目前 RAG 的主要挑战有以下三个：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;向量无法提供精确查询&lt;/li&gt;
&lt;li&gt;向量无法满足多样化查询需求&lt;/li&gt;
&lt;li&gt;Garbage In, Garbage Out&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此，他们提出了 InfiniFlow，即 Infinity 与 RAGFlow 的组合，前者是 AI 原生数据库，后者是基于视觉模型和多样化模板的&lt;a href=&quot;https://github.com/infiniflow/ragflow/blob/main/deepdoc/README.md&quot;&gt;文档理解&lt;/a&gt;的 RAG 引擎，通过两者的组合可以提供更高质量的 RAG 服务解决方案。&lt;/p&gt;
&lt;p&gt;实际上随着目前模型支持的上下文长度越来越大，RAG 的作用以及长上下文的 LLM 相关的论争也越来越激烈，有一部分观点认为 RAG 已死，在 LLM 本身就支持长上下文的情况下 RAG 只能提供相当有限的作用；另一部分观点认为长上下文的 LLM 仍然无法完全替代 RAG，本身还是具有一定的局限性，包括幻觉与可解释性等问题仍然无法解决。近期 NVIDIA 发表的一篇论文[^ruler]也表示实际上长上下文的模型的有效上下文范围很有可能没有模型本身声称的那么大，因此我认为这个问题还有待进一步的研究与讨论，不过我个人的观点也与演讲者相近，即使是在有长上下文模型的情况下，RAG 仍然是有其存在的价值的。&lt;/p&gt;
&lt;p&gt;有关 RAG 与长上下文 LLM 的相关信息可以参考以下 LangChain 的工程师的 Slides：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.google.com/presentation/d/1mJUiPBdtf58NfuSEQ7pVSEQ2Oqmek7F1i4gBwR6JDss/preview&quot;&gt;Unifying RAG and long context LLMs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;相关仓库：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/infiniflow/infinity&quot;&gt;infiniflow/infinity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/infiniflow/ragflow&quot;&gt;infiniflow/ragflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;这次是我第一次参加 Kubernetes Community Days 的活动，与 KubeCon 不同，KCD 的演讲之间是没有休息的间隔的，整体的流程相当紧凑，因此留给观众提问的时间也有限，不过整体上我认为是一次挺有收获的活动，无论是 Kubernetes 本身还是 LLM 相关的议题也都给我带来一些新的思考。当然必须提及的是，由于举办的地点在字节跳动位于漕河泾的办公楼，所以附近吃的还是不少的，这点还是&lt;a href=&quot;/posts/kubecon-china-2023/#summary&quot;&gt;比 KubeCon 要强不少&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/cncf/presentations&quot;&gt;[Slides]&lt;/a&gt;
&lt;a href=&quot;https://space.bilibili.com/1274679632/channel/collectiondetail?sid=2842495&quot;&gt;[Videos]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^godel]: Wu Xiang, Yakun Li, Yuquan Ren, Fan Jiang, Chaohui Xin, Varun Gupta, Chao Xiang, Xinyi Song, Meng Liu, Bing Li, Kaiyang Shao, Chen Xu, Wei Shao, Yuqi Fu, Wilson Wang, Cong Xu, Wei Xu, Caixue Lin, Rui Shi, and Yuming Liang. 2023. Gödel: Unified Large-Scale Resource Management and Scheduling at ByteDance. In Proceedings of the 2023 ACM Symposium on Cloud Computing (SoCC &apos;23). Association for Computing Machinery, New York, NY, USA, 308–323. https://doi.org/10.1145/3620678.3624663&lt;/p&gt;
&lt;p&gt;[^omega]: Malte Schwarzkopf, Andy Konwinski, Michael Abd-El-Malek, and John Wilkes. 2013. Omega: flexible, scalable schedulers for large compute clusters. In Proceedings of the 8th ACM European Conference on Computer Systems (EuroSys &apos;13). Association for Computing Machinery, New York, NY, USA, 351–364. https://doi.org/10.1145/2465351.2465386&lt;/p&gt;
&lt;p&gt;[^ruler]: Cheng-Ping Hsieh, Simeng Sun, Samuel Kriman, Shantanu Acharya, Dima Rekesh, Fei Jia, and Boris Ginsburg. 2024. RULER: What’s the Real Context Size of Your Long-Context Language Models?. http://arxiv.org/abs/2404.06654&lt;/p&gt;
</content:encoded><category>Event</category></item><item><title>在 Tekton 中使用 CEL 表达式过滤 GitLab Webhook 事件的文件变更</title><link>https://rudeigerc.dev/posts/tekton-gitlab-cel/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/tekton-gitlab-cel/</guid><description>本文介绍了如何在使用 GitLab 作为代码存储库的时候，在 Tekton Triggers 中基于 CEL Interceptor 利用 CEL 表达式实现对变更文件的过滤，并触发相对应的流水线。</description><pubDate>Thu, 15 Feb 2024 09:43:55 GMT</pubDate><content:encoded>&lt;h2&gt;TL;DR&lt;/h2&gt;
&lt;p&gt;在 Tekton Triggers 的 CEL Interceptor 中使用 CEL 表达式实现 Python 项目文件变更的过滤：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;body.commits.map(commit, commit.added + commit.modified + commit.removed).exists(files, true in files.map(file, matches(file, &quot;^(src|tests)|^pyproject.toml$&quot;)))
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;triggers:
  - name: gitlab-listener
    interceptors:
      - ref:
          name: gitlab
          kind: ClusterInterceptor
        params:
          - name: secretRef
            value:
              secretName: gitlab-secret
              secretKey: secretToken
          - name: eventTypes
            value:
              - Push Hook
      - ref:
          name: cel
          kind: ClusterInterceptor
        params:
          - name: filter
            value: body.commits.map(commit, commit.added + commit.modified + commit.removed).exists(files, true in files.map(file, matches(file, &quot;^(src|tests)|^pyproject.toml$&quot;)))
          - name: overlays
            value:
              - key: truncated_sha
                expression: body.pull_request.head.sha.truncate(7)
              - key: branch_name
                expression: body.ref.split(&quot;/&quot;)[2]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;在 CI/CD 平台中，通过过滤提交到 Git 的文件变更来触发不同的流水线是一种常见的需求，如在开发流程中过滤代码相关文件变更触发测试流水线，在发布流程中通过过滤 Tag Push 事件触发发布流水线等。&lt;/p&gt;
&lt;p&gt;如在 Github Actions 中，可编写如下配置使得在 &lt;code&gt;push&lt;/code&gt; 事件发生并且文件变更包含 JavaScript 文件时会触发工作流。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;on:
  push:
    paths:
      - &quot;**.js&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Tekton Triggers&lt;/h3&gt;
&lt;p&gt;在 Tekton 中，Tekton Triggers 中的 EventListener 与 Trigger 负责拦截各种事件，并根据自定义资源的定义触发相应的流水线。具体的流程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;代码存储库中发生事件；&lt;/li&gt;
&lt;li&gt;根据代码存储库的 Webhook 的配置，发送 POST 请求至指定的地址；&lt;/li&gt;
&lt;li&gt;Tekton Triggers 的 EventListener Service 接收 Webhook 事件；&lt;/li&gt;
&lt;li&gt;EventListener 触发对应的 Trigger 并通过 Trigger 中定义的 Interceptor，Interceptor 的职能包括 Token 验证与事件过滤等；&lt;/li&gt;
&lt;li&gt;Trigger 通过 TriggerTemplate 与 TemplateBinding 创建对应的 TaskRun 或 PipelineRun。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;用户可以自行编排流水线来实现自身的使用需求，如在流水线结束后通过 IM 发送流水线运行的结果。&lt;/p&gt;
&lt;h3&gt;CEL&lt;/h3&gt;
&lt;p&gt;通用表达式语言（Common Express Language，CEL）[^cel]是 Google 开源的非图灵完备语言，用于实现表达式评估的常见语义。目前 CEL 被应用于 Kubernetes 的 API 验证规则与策略规则中[^kubernetes-cel][^kubernetes-crd-validation-using-cel]，以及 Google Cloud 的部分服务中，如 Secure Web Proxy[^secure-web-proxy-cel]。&lt;/p&gt;
&lt;p&gt;Tekton 提供了 CEL Interceptor 用于在 Trigger 中使用 CEL 表达式来过滤或修改事件的 Payload。&lt;/p&gt;
&lt;h3&gt;Interceptors&lt;/h3&gt;
&lt;p&gt;Tekton Triggers 的 Interceptor 是一种事件处理器，它会在 TriggerBinding 之前运行，便于用户对事件的 Payload 进行拦截、修改与验证等操作，也可将修改过的 Payload 的部份传递给 TriggerBinding，进而传递给具体运行流水线的 TaskRun 或 PipelineRun。&lt;/p&gt;
&lt;p&gt;如果用户使用的代码存储库是 GitHub 的话，Trigger 中的 GitHub Interceptor 提供了 &lt;code&gt;addChangedFiles&lt;/code&gt; 参数，可在 CEL Interceptor 中使用 &lt;code&gt;extensions.changed_files&lt;/code&gt; 来获取变更的文件&lt;a href=&quot;%5BTekton%5D(https://tekton.dev/docs/triggers/interceptors/#adding-changed-files)&quot;&gt;^tekton-interceptor-adding-changed-files&lt;/a&gt;，并结合 CEL 表达式进行条件判断决定是否触发对应的 Pipeline。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;triggers:
  - name: github-listener
    interceptors:
      - ref:
          name: &quot;github&quot;
          kind: ClusterInterceptor
          apiVersion: triggers.tekton.dev
        params:
          - name: &quot;secretRef&quot;
            value:
              secretName: github-secret
              secretKey: secretToken
          - name: &quot;eventTypes&quot;
            value: [&quot;pull_request&quot;, &quot;push&quot;]
          - name: &quot;addChangedFiles&quot;
            value:
              enabled: true
      - ref:
          name: cel
        params:
          - name: filter
            # execute only when a file within the controllers directory has changed
            value: extensions.changed_files.matches(&apos;controllers/&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然而，上述的参数在 GitLab 与 BitBucket Interceptor 中并不支持，两者都仅支持简易的 Token 验证（&lt;code&gt;secretRef&lt;/code&gt;）以及事件类型的过滤（&lt;code&gt;eventTypes&lt;/code&gt;），因此如果要在使用 GitLab 或 BitBucket 的情况下实现文件变更的过滤的话，除了对 Tekton Triggers 有关 Interceptor 的部份&lt;a href=&quot;https://github.com/tektoncd/triggers/blob/main/pkg/interceptors/bitbucket/bitbucket.go&quot;&gt;^tekton-triggers-interceptors-gitlab&lt;/a&gt;进行修改以外，只能尝试通过 CEL 表达式来实现相应的效果。&lt;/p&gt;
&lt;h2&gt;过滤 GitLab Webhook 事件的文件变更&lt;/h2&gt;
&lt;p&gt;GitLab Webhook 的文档[^gitlab-webhook-push-events]给出了 Push Event 被触发的时候的 Payload 的例子：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;object_kind&quot;: &quot;push&quot;,
  &quot;event_name&quot;: &quot;push&quot;,
  &quot;before&quot;: &quot;95790bf891e76fee5e1747ab589903a6a1f80f22&quot;,
  &quot;after&quot;: &quot;da1560886d4f094c3e6c9ef40349f7d38b5d27d7&quot;,
  &quot;ref&quot;: &quot;refs/heads/master&quot;,
  &quot;ref_protected&quot;: true,
  &quot;checkout_sha&quot;: &quot;da1560886d4f094c3e6c9ef40349f7d38b5d27d7&quot;,
  &quot;user_id&quot;: 4,
  &quot;user_name&quot;: &quot;John Smith&quot;,
  &quot;user_username&quot;: &quot;jsmith&quot;,
  &quot;user_email&quot;: &quot;john@example.com&quot;,
  &quot;user_avatar&quot;: &quot;https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80&quot;,
  &quot;project_id&quot;: 15,
  &quot;project&quot;: {
    &quot;id&quot;: 15,
    &quot;name&quot;: &quot;Diaspora&quot;,
    &quot;description&quot;: &quot;&quot;,
    &quot;web_url&quot;: &quot;http://example.com/mike/diaspora&quot;,
    &quot;avatar_url&quot;: null,
    &quot;git_ssh_url&quot;: &quot;git@example.com:mike/diaspora.git&quot;,
    &quot;git_http_url&quot;: &quot;http://example.com/mike/diaspora.git&quot;,
    &quot;namespace&quot;: &quot;Mike&quot;,
    &quot;visibility_level&quot;: 0,
    &quot;path_with_namespace&quot;: &quot;mike/diaspora&quot;,
    &quot;default_branch&quot;: &quot;master&quot;,
    &quot;homepage&quot;: &quot;http://example.com/mike/diaspora&quot;,
    &quot;url&quot;: &quot;git@example.com:mike/diaspora.git&quot;,
    &quot;ssh_url&quot;: &quot;git@example.com:mike/diaspora.git&quot;,
    &quot;http_url&quot;: &quot;http://example.com/mike/diaspora.git&quot;
  },
  &quot;repository&quot;: {
    &quot;name&quot;: &quot;Diaspora&quot;,
    &quot;url&quot;: &quot;git@example.com:mike/diaspora.git&quot;,
    &quot;description&quot;: &quot;&quot;,
    &quot;homepage&quot;: &quot;http://example.com/mike/diaspora&quot;,
    &quot;git_http_url&quot;: &quot;http://example.com/mike/diaspora.git&quot;,
    &quot;git_ssh_url&quot;: &quot;git@example.com:mike/diaspora.git&quot;,
    &quot;visibility_level&quot;: 0
  },
  &quot;commits&quot;: [
    {
      &quot;id&quot;: &quot;b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327&quot;,
      &quot;message&quot;: &quot;Update Catalan translation to e38cb41.\n\nSee https://gitlab.com/gitlab-org/gitlab for more information&quot;,
      &quot;title&quot;: &quot;Update Catalan translation to e38cb41.&quot;,
      &quot;timestamp&quot;: &quot;2011-12-12T14:27:31+02:00&quot;,
      &quot;url&quot;: &quot;http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327&quot;,
      &quot;author&quot;: {
        &quot;name&quot;: &quot;Jordi Mallach&quot;,
        &quot;email&quot;: &quot;jordi@softcatala.org&quot;
      },
      &quot;added&quot;: [&quot;CHANGELOG&quot;],
      &quot;modified&quot;: [&quot;app/controller/application.rb&quot;],
      &quot;removed&quot;: []
    },
    {
      &quot;id&quot;: &quot;da1560886d4f094c3e6c9ef40349f7d38b5d27d7&quot;,
      &quot;message&quot;: &quot;fixed readme&quot;,
      &quot;title&quot;: &quot;fixed readme&quot;,
      &quot;timestamp&quot;: &quot;2012-01-03T23:36:29+02:00&quot;,
      &quot;url&quot;: &quot;http://example.com/mike/diaspora/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7&quot;,
      &quot;author&quot;: {
        &quot;name&quot;: &quot;GitLab dev user&quot;,
        &quot;email&quot;: &quot;gitlabdev@dv6700.(none)&quot;
      },
      &quot;added&quot;: [&quot;CHANGELOG&quot;],
      &quot;modified&quot;: [&quot;app/controller/application.rb&quot;],
      &quot;removed&quot;: []
    }
  ],
  &quot;total_commits_count&quot;: 4
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以发现主要需要关注的是 &lt;code&gt;commits&lt;/code&gt; 数组中的 &lt;code&gt;added&lt;/code&gt;、&lt;code&gt;modified&lt;/code&gt; 与 &lt;code&gt;removed&lt;/code&gt; 的部份，只需要将每个 commit 中的三者结合起来并过滤掉重复的部分，即可得到一次 Push Event 中整体的文件变更的列表。&lt;/p&gt;
&lt;p&gt;如果使用如 JavaScript 等对函数式支持比较好的语言的话，可以通过 &lt;code&gt;flatMap()&lt;/code&gt; 与 &lt;code&gt;filter()&lt;/code&gt; 的组合来获取所有的变更文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;body.commits
  .flatMap((commit) =&amp;gt; commit.added.concat(commit.modified, commit.removed))
  .filter((item, index, array) =&amp;gt; array.indexOf(item) === index);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是由于 CEL 表达式不支持类似 &lt;code&gt;flatten()&lt;/code&gt; 或是 &lt;code&gt;reduce()&lt;/code&gt; 之类的降维操作，需要采用一点迂回的方法才能实现相同的效果：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;body.commits.map(commit, commit.added + commit.modified + commit.removed).exists(files, true in files.map(file, matches(file, &quot;^(app|tests)|^Gemfile$&quot;)))
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;将 &lt;code&gt;commits&lt;/code&gt; 数组中的 &lt;code&gt;added&lt;/code&gt;、&lt;code&gt;modified&lt;/code&gt; 与 &lt;code&gt;removed&lt;/code&gt; 合并为二维数组（&lt;code&gt;e.map(x, t)&lt;/code&gt;）。结果为 &lt;code&gt;[[&apos;CHANGELOG&apos;, &apos;app/controller/application.rb&apos;], [&apos;CHANGELOG&apos;, &apos;app/controller/application.rb&apos;]]&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;判断是否存在某个 commit 中的变更文件满足模式 &lt;code&gt;true in files.map(file, matches(file, &quot;^(app|tests)|^Gemfile$&quot;))&lt;/code&gt;（&lt;code&gt;e.exists(x, p)&lt;/code&gt;）。结果为 &lt;code&gt;true in [true, true] = true&lt;/code&gt;。
&lt;ol&gt;
&lt;li&gt;判断是否存在某个文件满足正则表达式 &lt;code&gt;^(app|tests)|^Gemfile$&lt;/code&gt;（&lt;code&gt;matches(x, p)&lt;/code&gt;）。结果为 &lt;code&gt;[false, true]&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;matches&lt;/code&gt; 支持在匹配的模式中使用正则表达式，只需要对这部份进行修改就可以满足不同使用场景。在前述的例子中，该表达式会过滤以 &lt;code&gt;app&lt;/code&gt; 与 &lt;code&gt;tests&lt;/code&gt; 开头的文件以及 &lt;code&gt;Gemfile&lt;/code&gt; 文件的变更。&lt;/p&gt;
&lt;h3&gt;验证 CEL 表达式&lt;/h3&gt;
&lt;p&gt;Tekton Triggers 提供了一个 &lt;code&gt;cel-eval&lt;/code&gt; 工具，可以用来验证 CEL 表达式的行为是否符合期望。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;go install github.com/tektoncd/triggers/cmd/cel-eval@latest
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;由于 Tekton Triggers 在 &lt;code&gt;go.mod&lt;/code&gt; 中使用 &lt;code&gt;replace&lt;/code&gt; 替换了部分 Kubernetes 相关的包，因此在 &lt;code&gt;go install&lt;/code&gt; 的时候会失败。目前只能直接 clone 仓库并通过 &lt;code&gt;make bin/cel-eval&lt;/code&gt; 来编译。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;git clone github.com/tektoncd/triggers --depth 1
cd triggers
make bin/cel-eval
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们基于前文所提到的 GitLab 的文档中所给出的 Push Event 的 Payload 作为例子来验证 CEL 表达式。&lt;/p&gt;
&lt;p&gt;将前述的 CEL 表达式保存到 &lt;code&gt;expression.txt&lt;/code&gt; 文件中，并将前述的事件的 Payload 写入 &lt;code&gt;http.txt&lt;/code&gt; 中（替换省略号的部分）。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：应根据实际的 Payload 的长度修改 &lt;code&gt;Content-Length&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;POST /foo HTTP/1.1
Content-Length: 2589
Content-Type: application/json
X-Header: tacocat

...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将 &lt;code&gt;expression.txt&lt;/code&gt; 作为表达式文件，&lt;code&gt;http.txt&lt;/code&gt; 作为 HTTP 请求文件作为参数输入 &lt;code&gt;cel-eval&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cel-eval -e expression.txt -r http.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果 CEL 表达式的结果符合预期的话，&lt;code&gt;cel-eval&lt;/code&gt; 会返回 &lt;code&gt;true&lt;/code&gt;，否则返回 &lt;code&gt;false&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据 GitLab Webhook Push 事件的 Payload 的例子，该事件中的变更文件为 &lt;code&gt;[&apos;CHANGELOG&apos;, &apos;app/controller/application.rb&apos;]&lt;/code&gt;，由于 &lt;code&gt;app/controller/application.rb&lt;/code&gt; 以 &lt;code&gt;app&lt;/code&gt; 开头满足正则表达式，所以该 CEL 表达式的结果为 &lt;code&gt;true&lt;/code&gt;。可以在自行修改 &lt;code&gt;expression.txt&lt;/code&gt; 与 &lt;code&gt;http.txt&lt;/code&gt; 的内容以验证不同的 CEL 表达式以及不同的 Payload。&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;本文介绍了如何在使用 GitLab 作为代码存储库的时候，在 Tekton Triggers 中基于 CEL Interceptor 利用 CEL 表达式实现对变更文件的过滤，并触发相对应的流水线。&lt;/p&gt;
&lt;p&gt;当然，如果能够在和 GitHub Interceptor 一样在 Tekton Triggers 的代码中直接在 GitLab Interceptor 中集成 &lt;code&gt;addChangedFiles&lt;/code&gt; 参数的话会更为理想，这样就可以通过在 CEL Interceptor 中使用 &lt;code&gt;extensions.changed_files.matches(&apos;^(app|tests)|^Gemfile$&apos;)&lt;/code&gt; 来实现和本文介绍的内容相同的效果。&lt;/p&gt;
&lt;p&gt;[^kubernetes-cel]: &lt;a href=&quot;https://kubernetes.io/zh-cn/docs/reference/using-api/cel/&quot;&gt;Kubernetes 中的通用表达式语言 | Kubernetes&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^kubernetes-crd-validation-using-cel]: &lt;a href=&quot;https://opensource.googleblog.com/2023/11/kubernetes-crd-validation-using-cel.html&quot;&gt;Kubernetes CRD Validation Using CEL | Google Open Source Blog&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^secure-web-proxy-cel]: &lt;a href=&quot;https://cloud.google.com/secure-web-proxy/docs/cel-matcher-language-reference&quot;&gt;CEL matcher language reference | Secure Web Proxy | Google Cloud&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^cel]: &lt;a href=&quot;https://github.com/google/cel-spec&quot;&gt;google/cel-spec: Common Expression Language&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;[^gitlab-webhook-push-events]: &lt;a href=&quot;https://docs.gitlab.com/ee/user/project/integrations/webhook_events.html#push-events&quot;&gt;Webhook events | GitLab&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>Tekton</category><category>GitOps</category></item><item><title>使用 FastChat 快速部署 LLM 服务</title><link>https://rudeigerc.dev/posts/llm-inference-with-fastchat/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/llm-inference-with-fastchat/</guid><description>FastChat 是以 UC Berkeley 主导的 Large Model Systems Organization 所开源的面向 LLM 的训练、推理与评估的开放平台，通过该平台可以快速部署多模型的 LLM 服务，并且通过 Web UI 以及兼容 OpenAI API 的 RESTful API 访问。本文介绍了如何快速使用 FastChat 在服务器上部署 LLM 服务。</description><pubDate>Sun, 24 Dec 2023 12:33:49 GMT</pubDate><content:encoded>&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/lm-sys/FastChat&quot;&gt;FastChat&lt;/a&gt; 是以 UC Berkeley 主导的 &lt;a href=&quot;https://lmsys.org/&quot;&gt;Large Model Systems Organization&lt;/a&gt; 所开源的面向 LLM 的训练、推理与评估的开放平台，通过该平台可以快速部署多模型的 LLM 服务，并且通过 Web UI 以及兼容 OpenAI API 的 RESTful API 访问。&lt;/p&gt;
&lt;h2&gt;准备工作&lt;/h2&gt;
&lt;h3&gt;使用虚拟环境&lt;/h3&gt;
&lt;p&gt;为了避免潜在的依赖冲突，在安装 Python 相关的依赖的时候推荐使用单独的&lt;a href=&quot;https://docs.python.org/3/library/venv.html&quot;&gt;虚拟环境&lt;/a&gt;进行管理。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# initiate virtual environment
python -m venv .venv
# activate virtual environemnt
source .venv/bin/activate
# upgrade `pip`
pip install -U pip
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;安装 FastChat&lt;/h3&gt;
&lt;p&gt;通过 &lt;code&gt;pip&lt;/code&gt; 安装 FastChat：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install &quot;fschat[model_worker,webui]&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;在特定情况下，在安装过程中有可能会出现找不到 &lt;code&gt;accelerator&lt;/code&gt; 模块的报错，可通过 &lt;code&gt;pip install accelerator&lt;/code&gt; 手动安装。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;安装 vLLM&lt;/h3&gt;
&lt;p&gt;为了得到更好的推理性能，我们在这边使用 &lt;a href=&quot;https://github.com/vllm-project/vllm&quot;&gt;vLLM&lt;/a&gt; 作为后端来加速推理。&lt;/p&gt;
&lt;p&gt;通过 &lt;code&gt;pip&lt;/code&gt; 安装 vLLM：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install vllm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;vLLM 通过 PagedAttention[^pagedattention] 以及基于 Ray 的张量并行来实现了高效的显存管理与并行计算，笔者将在另一篇文章当中详细介绍 vLLM 与 PagedAttention 的设计与实现，此处不再赘述。&lt;/p&gt;
&lt;h3&gt;（可选）获取 LLM 模型&lt;/h3&gt;
&lt;p&gt;由于在测试过程中我们使用的是内网环境，因此没办法在启动 FastChat 时自动下载模型，需要手动从 HuggingFace Hub 下载模型便于离线部署。&lt;/p&gt;
&lt;p&gt;Hugging Face 提供了多种方式来下载模型，我们在这边选择使用 &lt;code&gt;huggingface-cli&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install -U &quot;huggingface_hub[cli]&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;huggingface-cli download --resume-download --local-dir-use-symlinks False --token &quot;hg_TOKEN&quot; meta-llama/Llama-2-7b-chat-hf --local-dir Llama-2-7b-chat-hf
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;使用 FastChat 部署 LLM&lt;/h2&gt;
&lt;p&gt;为了能够通过 Web 和 SDK 访问 LLM 服务，我们需要在服务器上部署以下 FastChat 的组件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Controller&lt;/li&gt;
&lt;li&gt;Worker&lt;/li&gt;
&lt;li&gt;Gradio Web Server&lt;/li&gt;
&lt;li&gt;OpenAI API Server&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;images/fastchat.png&quot; alt=&quot;fastchat&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;启动 Controller&lt;/h3&gt;
&lt;p&gt;启动 FastChat Controller：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python3 -m fastchat.serve.controller
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;默认端口为 &lt;code&gt;21001&lt;/code&gt;，可通过 &lt;code&gt;--port&lt;/code&gt; 指定。&lt;/p&gt;
&lt;h3&gt;启动 vLLM Worker&lt;/h3&gt;
&lt;p&gt;基于 vLLM Worker 和 LLM 启动推理服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python3 -m fastchat.serve.vllm_worker meta-llama/Llama-2-7b-chat-hf --num-gpus 2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;默认端口为 &lt;code&gt;21002&lt;/code&gt;，可通过 &lt;code&gt;--port&lt;/code&gt; 指定。FastChat 的 Worker 会向 Controller 注册自身，并通过心跳机制保持连接。&lt;/p&gt;
&lt;p&gt;本文使用的是 &lt;code&gt;meta-llama/Llama-2-7b-chat-hf&lt;/code&gt; 模型，理论上在 FP16 精度下需要约 15G 的显存，因此我们在这边通过指定 &lt;code&gt;--num-gpus&lt;/code&gt; 参数选择使用两块 RTX 3080（显存为 10G）。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;本文使用 vLLM 作为推理后端，如果希望使用默认的推理后端的话可使用 &lt;code&gt;fastchat.serve.model_worker&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;s&gt;由于 FastChat 的 vLLM Worker 在实现的时候使用了 &lt;a href=&quot;https://peps.python.org/pep-0584/&quot;&gt;PEP 584&lt;/a&gt; 中提出的针对字典的 Union Operator，该提案在 Python 3.9 中被实现，因此在 Python 3.8 的环境下运行会报错。&lt;a href=&quot;https://github.com/lm-sys/FastChat/pull/2824&quot;&gt;lm-sys/FastChat#2824&lt;/a&gt;&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&lt;s&gt;可执行 &lt;code&gt;sed -i &apos;s/yield (json.dumps(ret | {&quot;finish_reason&quot;: &quot;none&quot;}) + &quot;\\0&quot;).encode()/yield (json.dumps({**ret | **{&quot;finish_reason&quot;: &quot;none&quot;}}) + &quot;\\0&quot;).encode()/g&apos; .venv/venv/lib/python3.8/site-packages/fastchat/serve/vllm_worker.py&lt;/code&gt; 作为临时的解决方案。&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2023-12-24 Update:&lt;/strong&gt; 该 Pull Request 已被合并，应可在下一次 Release 被修复。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;启动 Gradio Web Server&lt;/h3&gt;
&lt;p&gt;FastChat 基于 Gradio 提供了可视化交互聊天界面。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python3 -m fastchat.serve.gradio_web_server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;默认端口为 &lt;code&gt;7860&lt;/code&gt;，可通过 &lt;code&gt;--port&lt;/code&gt; 指定。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;如果在启动过程中报错的话，这有可能是由于 Gradio 的版本不兼容导致的，将 Gradio 降级至 3.48.0 即可解决。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;启动 OpenAI API Server&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;python3 -m fastchat.serve.openai_api_server
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;默认端口为 &lt;code&gt;8000&lt;/code&gt;，可通过 &lt;code&gt;--port&lt;/code&gt; 指定。&lt;/p&gt;
&lt;h2&gt;使用 OpenAI SDK&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;本文使用的 OpenAI SDK 的版本为 v1，与 v0 版本的接口存在一定的差异，详情请访问 OpenAI Python SDK 的文档。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;安装 OpenAI Python SDK：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install openai
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 FastChat OpenAI API Server 的端点初始化 OpenAI 客户端：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from openai import OpenAI

client = OpenAI(base_url=&quot;http://localhost:8000/v1&quot;, api_key=&quot;na&quot;)

model = &quot;meta-llama/Llama-2-7b-chat-hf&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在启动 OpenAI API Server 的时候可通过参数 &lt;code&gt;--api-keys&lt;/code&gt; 指定 API Key 的列表，在不指定的情况下在客户端填入任意值皆可。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：该方法将废弃。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;创建 Completion：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;prompt = &quot;Once upon a time&quot;

completion = client.completions.create(
    model=model,
    prompt=prompt,
    max_tokens=64
)
print(prompt + completion.choices[0].text)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Once upon a time, ......
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建 Chat Completion（Default）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;completion = client.chat.completions.create(
    model = model,
    messages=[
        {
            &quot;role&quot;: &quot;user&quot;,
            &quot;content&quot;: &quot;Say this is a test&quot;,
        }
    ],
)
print(completion.choices[0].message.content)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Hello, ......
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;创建 Chat Completion（Stream）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;completion = client.chat.completions.create(
    model = model,
    messages=[
        {
            &quot;role&quot;: &quot;user&quot;,
            &quot;content&quot;: &quot;Say this is a test&quot;,
        }
    ],
    stream=True
)

for chunk in completion:
    if chunk.choices[0].finish_reason == &quot;stop&quot;:
        break
    else:
        print(chunk.choices[0].delta.content, end=&quot;&quot;, flush=True)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Hello, ......
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以访问 &lt;a href=&quot;https://platform.openai.com/docs/api-reference&quot;&gt;OpenAI API 的文档&lt;/a&gt;获取更多的信息。&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;本文介绍了如何快速使用 FastChat 在服务器上部署 LLM 服务。笔者先前对 LLM 知之甚少，仅通过一些通俗的介绍有着初步的认识，因此能够在这么短的时间内就能基于已有的模型部署出一个可用的 LLM 服务还是相当惊喜的。后续笔者可能还会围绕 LLM 相关的应用与系统进行更深入的了解与探索。&lt;/p&gt;
&lt;h2&gt;附录&lt;/h2&gt;
&lt;h3&gt;环境&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Python v3.8.10&lt;/li&gt;
&lt;li&gt;FastChat v0.2.34&lt;/li&gt;
&lt;li&gt;vLLM v0.2.6&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;其它 LLM 推理框架与后端&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/ninehills/llm-inference-benchmark&quot;&gt;ninehills/llm-inference-benchmark&lt;/a&gt; 提供了多种 LLM 推理框架与后端的性能对比，此处仅列出笔者有了解过的部分系统。&lt;/p&gt;
&lt;p&gt;Frameworks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vllm-project/vllm&quot;&gt;vllm-project/vllm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/bentoml/OpenLLM&quot;&gt;bentoml/OpenLLM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/oobabooga/text-generation-webui&quot;&gt;oobabooga/text-generation-webui&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/huggingface/text-generation-inference&quot;&gt;huggingface/text-generation-inference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xorbitsai/inference&quot;&gt;xorbitsai/inference&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Backends:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/huggingface/transformers&quot;&gt;huggingface/transformers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/huggingface/candle&quot;&gt;huggingface/candle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/huggingface/text-generation-inference&quot;&gt;huggingface/text-generation-inference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vllm-project/vllm&quot;&gt;vllm-project/vllm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/NVIDIA/TensorRT-LLM&quot;&gt;NVIDIA/TensorRT-LLM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/microsoft/DeepSpeed-MII&quot;&gt;microsoft/DeepSpeed-MII&lt;/a&gt; / &lt;a href=&quot;https://github.com/microsoft/DeepSpeed/tree/master/blogs/deepspeed-fastgen&quot;&gt;DeepSpeed-FastGen&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;[^pagedattention]: Efficient Memory Management for Large Language Model Serving with PagedAttention. SOSP &apos;23. https://arxiv.org/abs/2309.06180&lt;/p&gt;
</content:encoded><category>LLM</category></item><item><title>排查通过 Ingress 暴露的 Jupyter Lab 的伪终端间歇性输出字符问题</title><link>https://rudeigerc.dev/posts/output-corruption-jupyter-server-terminal-ingress/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/output-corruption-jupyter-server-terminal-ingress/</guid><description>本文介绍了排查通过 Ingress 暴露的 Jupyter Lab 的伪终端间歇性输出字符问题的症状、原因以及解决方案。</description><pubDate>Mon, 27 Nov 2023 13:50:10 GMT</pubDate><content:encoded>&lt;h2&gt;背景&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Python 3.8.10&lt;/li&gt;
&lt;li&gt;Vim 8.1&lt;/li&gt;
&lt;li&gt;jupyterlab 3.6.5&lt;/li&gt;
&lt;li&gt;jupyter-server 2.6.0&lt;/li&gt;
&lt;li&gt;jupyter-server-terminals 0.4.4&lt;/li&gt;
&lt;li&gt;terminado 0.17.1&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在公司的内部环境中，我们将 Jupyter Lab 作为一个服务部署在 Kubernetes 集群中作为用户的开发环境，并通过 Ingress 将其暴露在内网上。用户可以通过浏览器访问 Jupyter Lab，同时也可以通过 Jupyter Lab 内置的伪终端与容器交互。Kubernetes 集群所使用的 Ingress Controller 是 &lt;a href=&quot;https://kubernetes.github.io/ingress-nginx/&quot;&gt;Ingress NGINX Controller&lt;/a&gt;。&lt;/p&gt;
&lt;h2&gt;症状&lt;/h2&gt;
&lt;p&gt;在 Jupyter Lab 的终端页面中，终端会间歇性地输出先前已经输出过的字符，有时候会包含一些乱码字符，形如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;2R0;276;0c10;rgb:0000/0000/000011;rgb:ffff/ffff/ffff12;2$y
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;原因&lt;/h2&gt;
&lt;p&gt;首先需要排查间接性输出先前的输出内容的原因。Jupyter Lab 是通过 Websocket 将伪终端的内容发送给 Jupyter Server 的，且输出字符的时间点与 Websocket 的链接断开并重新建立的时间点一致，因此推断出这个问题与 Websocket 的重连有关。&lt;/p&gt;
&lt;h3&gt;Nginx Ingress Controller&lt;/h3&gt;
&lt;p&gt;我们使用 Ingress NGINX Controller 作为 Kubernetes 集群的 Ingress Controller，其&lt;a href=&quot;https://kubernetes.github.io/ingress-nginx/user-guide/miscellaneous/#websockets&quot;&gt;文档&lt;/a&gt;中推荐在使用 Websocket 的时候将 &lt;code&gt;proxy-read-timeout&lt;/code&gt; 与 &lt;code&gt;proxy-send-timeout&lt;/code&gt; 由默认的 60 秒设置为至少 3600 秒（即一小时），以避免 Websocket 断开链接，但是并没有起作用。&lt;/p&gt;
&lt;p&gt;随后，我们发现这个现象与 NGINX 的 reload 机制有关。在用户创建或者删除开发环境的时候，服务端会随之动态地增加或删除对应的 Ingress 资源，这个动作会导致 NGINX reload，导致所有的 Websocket 链接都会被断开。这也解释了为什么这个问题是间歇性的，因为用户创建或者删除开发环境的频率不是恒定的，此外，我们确实也发现在深夜此现象并不会出现，进一步证实了这个推断。&lt;/p&gt;
&lt;h3&gt;Websocket Read Buffer&lt;/h3&gt;
&lt;p&gt;在前一部分中我们提到，Jupyter Lab 通过 Websocket 使得伪终端与服务端进行通信，而在 NGINX reload 过后，Websocket 的链接会在一定时间后断开并建立新的 Websocket 链接。&lt;/p&gt;
&lt;p&gt;Jupyter Lab 中与 Websocket 相关的库主要是以下两个：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jupyter-server/jupyter_server_terminals&quot;&gt;jupyter-server/jupyter_server_terminals&lt;/a&gt; - A Jupyter Server Extension Providing Support for Terminals&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jupyter/terminado&quot;&gt;jupyter/terminado&lt;/a&gt; - Terminals served by tornado websockets&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 terminado v0.12.2 中，开发者引入了一个新的特性，即在 Websocket 重新链接的时候，会从服务端的 Buffer 中读取先前的输出并返回给客户端，导致了间歇性输出字符这个问题的出现。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;terminado/websocket.py&lt;/code&gt; 中：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def open(self, url_component=None):
    &quot;&quot;&quot;Websocket connection opened.

    Call our terminal manager to get a terminal, and connect to it as a
    client.
    &quot;&quot;&quot;
    # Jupyter has a mixin to ping websockets and keep connections through
    # proxies alive. Call super() to allow that to set up:
    super(TermSocket, self).open(url_component)

    self._logger.info(&quot;TermSocket.open: %s&quot;, url_component)

    url_component = _cast_unicode(url_component)
    self.term_name = url_component or &apos;tty&apos;
    self.terminal = self.term_manager.get_terminal(url_component)
    self.terminal.clients.append(self)
    self.send_json_message([&quot;setup&quot;, {}])
    self._logger.info(&quot;TermSocket.open: Opened %s&quot;, self.term_name)
    # Now drain the preopen buffer, if reconnect.
    buffered = &quot;&quot;
    preopen_buffer = self.terminal.read_buffer.copy()
    while True:
        if not preopen_buffer:
            break
        s = preopen_buffer.popleft()
        buffered += s
    if buffered:
        self.on_pty_read(buffered)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;转义字符&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;2R0;276;0c10;rgb:0000/0000/000011;rgb:ffff/ffff/ffff12;2$y
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最终，我们需要知道这些控制字符具体代表的含义以及为何会出现在数据流中。我们发现这些字符是在执行 Vim 相关操作的时候才会产生的。&lt;/p&gt;
&lt;p&gt;通过 Websocket 传输的数据流可以发现，实际上这是五段 ANSI 转义序列（ANSI escape sequence）的组合。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ANSI 转义序列是一种带内信号（in-band signaling）的转义序列标准，用于控制视频文本终端上的光标位置、颜色和其他选项。在文本中嵌入确定的字节序列，大部分以 ESC 转义字符和 &quot;[&quot; 字符开始，终端会把这些字节序列解释为相应的指令，而不是普通的字符编码。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;STDOUT ...
STDIN \u001b[2;2R
STDIN \u001b[&amp;gt;0;276;0c
STDIN \u001b]10;rgb:0000/0000/0000\u001b\\
STDIN \u001b]11;rgb:ffff/ffff/ffff\u001b\\
STDOUT ...
STDIN \u001b[?12;2$y
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;\u001b&lt;/code&gt; 是 &lt;code&gt;ESC&lt;/code&gt; 的 Unicode 编码，&lt;code&gt;\u001b[&lt;/code&gt; 代表控制序列导入器（Control Sequence Introducer，&lt;strong&gt;CSI&lt;/strong&gt;），&lt;code&gt;\u001b]&lt;/code&gt; 代表操作系统命令（Operating System Command，&lt;strong&gt;OSC&lt;/strong&gt;），&lt;code&gt;\u001b\&lt;/code&gt; 代表字符串终止（String Terminator，&lt;strong&gt;ST&lt;/strong&gt;）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
```text
CSI P s n

Device Status Report (DSR)

P s = 5 → Status Report CSI 0 n (‘‘OK’’)
P s = 6 → Report Cursor Position (CPR) [row;column] as
CSI r ; c R
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;\u001b[2;2R&lt;/code&gt; 即 &lt;code&gt;CSI 2 ; 2 R&lt;/code&gt;，表示光标的位置是第 2 行第 2 列，该序列是状态控制报告（Device Status Report，DSR）。&lt;/p&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;CSI &amp;gt; P s c

Send Device Attributes (Secondary DA)

P s = 0 or omitted → request the terminal’s identification code. The response depends on the decTerminalID resource setting. It should apply only to VT220 and up, but xterm extends this to VT100.
→ CSI &amp;gt; P p ; P v ; P c c
where P p denotes the terminal type
→ 0 (‘‘VT100’’)
→ 1 (‘‘VT220’’)
and P v is the firmware version (for xterm, this is the XFree86 patch number, starting with 95). In a DEC terminal, P c indicates the ROM cartridge registration number and is always zero.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;\u001b[&amp;gt;0;276;0c&lt;/code&gt; 即 &lt;code&gt;CSI &amp;gt; 0 ; 276 ; 0 c&lt;/code&gt;，表示该终端类型是 &lt;code&gt;VT100&lt;/code&gt;，固件的版本是 &lt;code&gt;276&lt;/code&gt;，ROM 芯片的注册号是 &lt;code&gt;0&lt;/code&gt;，该序列是 DA2 报告。&lt;/p&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;OSC P s ; P t ST

P s = 1 0 → Change VT100 text foreground color to P t
P s = 1 1 → Change VT100 text background color to P t
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;\u001b]10;rgb:0000/0000/0000\u001b\\&lt;/code&gt; 即 &lt;code&gt;OSC 10 ; rgb : 0000 / 0000 / 0000 ST&lt;/code&gt;，表示将前景色设置为黑色。
&lt;code&gt;\u001b]11;rgb:ffff/ffff/ffff\u001b\\&lt;/code&gt; 即 &lt;code&gt;OSC 11 ; rgb : ffff / ffff / ffff ST&lt;/code&gt;，表示将背景色设置为白色。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CSI ? P m h

DEC Private Mode Set (DECSET)

P s = 1 2 → Start Blinking Cursor (att610)

CSI Ps $ p
          Request ANSI mode (DECRQM).  For VT300 and up, reply DECRPM is
            CSI Ps; Pm $ y
          where Ps is the mode number as in SM/RM, and Pm is the mode
          value:
            0 - not recognized
            1 - set
            **2 - reset**
            3 - permanently set
            4 - permanently reset

CSI ? Ps $ p
          Request DEC private mode (DECRQM).  For VT300 and up, reply
          DECRPM is
            CSI ? Ps; Pm $ y
          where Ps is the mode number as in DECSET/DECSET, Pm is the
          mode value as in the ANSI DECRQM.
          Two private modes are read-only (i.e., 1 3  and 1 4 ),
          provided only for reporting their values using this control
          sequence.  They correspond to the resources cursorBlink and
          cursorBlinkXOR.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;\u001b[?12;2$y&lt;/code&gt; 即 &lt;code&gt;CSI ? 12 ; 2 $ y&lt;/code&gt;，表示将光标闪烁模式开启。&lt;/p&gt;
&lt;p&gt;因此实际上这些字符都是在与伪终端中的 Vim 交互的时候产生的转义序列，而这些转义序列通过 Websocket 被错误地发送给了 Jupyter Server。&lt;/p&gt;
&lt;h3&gt;小结&lt;/h3&gt;
&lt;p&gt;综上，我们可以发现这个问题是由于三个现象的叠加导致的：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在新增或删除 Ingress 资源的时候，NGINX reload 会被触发，导致所有的 Websocket 链接都会被断开。&lt;/li&gt;
&lt;li&gt;在 jupyter/terminado 0.12.2 之后，服务端会读取重连之前的 Read Buffer 数据并返回给客户端。&lt;a href=&quot;https://github.com/jupyter/terminado/compare/0.12.1...v0.12.2&quot;&gt;[CHANGELOG]&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Vim 相关操作的转义序列被错误地发送给了 Jupyter Server。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;解决方案&lt;/h2&gt;
&lt;p&gt;针对前述第一个问题，我们可以通过在 Nginx 当中配置 &lt;code&gt;worker-shutdown-timeout&lt;/code&gt;（默认为 240 秒）来延长 NGINX worker gracefully shutdown 的时间，从而避免 Websocket 的链接被频繁断开。然而采用这种方式会导致原先应该被回收的 Worker 一直被保留，最终可能导致 NGINX OOM，因此是否需要通过该配置来解决这个问题还需要进一步的评估。&lt;/p&gt;
&lt;p&gt;针对前述第二与第三个问题，我们可以将 &lt;code&gt;terminado&lt;/code&gt; 降级至 0.12.1 版本，在该版本下 Jupyter Lab 的 Terminal 在重新链接 Websocket 的时候不会读取 Buffer，虽然并没有从根本上解决，但可以间接规避掉这个问题。&lt;/p&gt;
&lt;p&gt;然而，在 macOS 环境下实际上我并没有办法复现这个问题，即在 Jupyter Lab 的 Termninal 中使用 Vim 的时候，Websocket 不会将 Vim 转义序列作为输入发送给 Jupyter Server，也因此在重放的时候不会出现这个问题。此现象可能要在特定的环境下才会出现，而这点还需要进一步的排查。&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.xfree86.org/current/ctlseqs.html&quot;&gt;Xterm Control Sequences&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://invisible-island.net/xterm/ctlseqs/ctlseqs.html&quot;&gt;ctlseqs(ms)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;相关 Issue：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kubernetes/ingress-nginx/issues/6731&quot;&gt;kubernetes/ingress-nginx#6731 - Frequent backend reloads causing disruption&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/sagemathinc/cocalc/issues/1269&quot;&gt;sagemathinc/cocalc#1269 - terminal -- control code corruption&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/sagemathinc/cocalc/issues/3277&quot;&gt;sagemathinc/cocalc#3277 - xterm.js terminal -- control code corruption&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/coder/code-server/issues/3657&quot;&gt;coder/code-server#3657 - restored terminal from browser reload may append 2R0;276;0c some times&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xtermjs/xterm.js/issues/3307&quot;&gt;xtermjs/xterm.js#3307 - Control Sequences emitted when opening vim&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/xtermjs/xterm.js/issues/4127&quot;&gt;xtermjs/xterm.js#4127 - Destructive console output after exiting vim editor&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Troubleshooting</category></item><item><title>Structuring a Python Project with PDM</title><link>https://rudeigerc.dev/posts/structuring-a-python-project/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/structuring-a-python-project/</guid><description>This tutorial shows you how to structure a Python project with PDM, a modern Python package and dependency manager supporting the latest PEP standards.</description><pubDate>Sun, 12 Nov 2023 14:16:39 GMT</pubDate><content:encoded>&lt;p&gt;This tutorial shows you how to structure a Python project with &lt;a href=&quot;https://pdm-project.org/latest/&quot;&gt;PDM&lt;/a&gt;, a modern Python package and dependency manager supporting the latest PEP standards.&lt;/p&gt;
&lt;h2&gt;Background&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;PDM&lt;/strong&gt; is a modern Python package and dependency manager supporting the latest PEP standards, including &lt;a href=&quot;https://peps.python.org/pep-0621/&quot;&gt;PEP 621&lt;/a&gt; project metadata and a &lt;a href=&quot;https://peps.python.org/pep-0517/&quot;&gt;PEP 517&lt;/a&gt; build backend. PDM simplifies the process od depenedency management and lifecycle management of Python projects.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alternatives:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python-poetry/poetry&quot;&gt;python-poetry/poetry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/pypa/hatch&quot;&gt;pypa/hatch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mitsuhiko/rye&quot;&gt;mitsuhiko/rye&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Objectives&lt;/h2&gt;
&lt;p&gt;This tutorial covers the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;#initialize-a-new-project&quot;&gt;Initialize a new project&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#configure-development-environment&quot;&gt;Configure development environment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#manage-development-dependencies&quot;&gt;Manage development dependencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;#build-and-publish&quot;&gt;Build and Publish&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Python 3.7+&lt;/li&gt;
&lt;li&gt;PDM&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Install PDM&lt;/h3&gt;
&lt;h4&gt;Install PDM with &lt;code&gt;pipx&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;pipx&lt;/code&gt; is a tool to install and run applications in isolated virtual environments. It is remommended to install Python application in an isolated environemnt to acoid potential dependency conflicts.&lt;/p&gt;
&lt;p&gt;Install &lt;code&gt;pipx&lt;/code&gt; and ensure directories necessary for &lt;code&gt;pipx&lt;/code&gt; operation are in &lt;code&gt;PATH&lt;/code&gt; environment variable:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python -m pip install --user pipx
python -m pipx ensurepath
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install &lt;code&gt;pdm&lt;/code&gt; with &lt;code&gt;pipx&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pipx install pdm[all]
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Install PDM with &lt;code&gt;pip&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;You can still install &lt;code&gt;pdm&lt;/code&gt; with &lt;code&gt;pip&lt;/code&gt; normally:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install --user pdm
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;(Optional) Enable central installation caches of PDM&lt;/h3&gt;
&lt;p&gt;Similar to &lt;code&gt;pnpm&lt;/code&gt;, &lt;code&gt;pdm&lt;/code&gt; also supports &lt;a href=&quot;https://pdm-project.org/latest/usage/config/#central-installation-caches&quot;&gt;central installation caches&lt;/a&gt; to avoid waste of disc space.&lt;/p&gt;
&lt;p&gt;Enable the central installation caches of PDM:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pdm config install.cache on
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Initialize a new project&lt;/h2&gt;
&lt;p&gt;Create a new project with &lt;code&gt;pdm init&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir my-project &amp;amp;&amp;amp; cd my-project
pdm init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The outcome is similar to the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Creating a pyproject.toml for PDM...
Please enter the Python interpreter to use
0. /usr/local/bin/python (3.11)
1. /usr/local/bin/python3.12 (3.12)
2. /usr/local/bin/python3.11 (3.11)
3. /usr/local/bin/python3.10 (3.10)
Please select (0):
Would you like to create a virtualenv with /usr/local/bin/python? [y/n] (y):
Virtualenv is created successfully at /Users/rudeigerc/Developer/my-project/.venv
Is the project a library that is installable?
If yes, we will need to ask a few more questions to include the project name and build backend [y/n] (n): y
Project name (my-project):
Project version (0.1.0):
Project description ():
Which build backend to use?
0. pdm-backend
1. setuptools
2. flit-core
3. hatchling
Please select (0):
License(SPDX name) (MIT):
Author name (rudeigerc):
Author email (rudeigerc@gmail.com):
Python requires(&apos;*&apos; to allow any) (&amp;gt;=3.11):
Project is initialized successfully
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Make sure that you have selected the proper Python interpreter.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;Work with virtual environment&lt;/h3&gt;
&lt;p&gt;PDM will ask whether to use virtual environment when initializing a new project. It is recommended to use virtual environment to isolate the project from the global environment.&lt;/p&gt;
&lt;p&gt;Actiavte the virtual environment in the project:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;eval $(pdm venv activate)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Please refer to the &lt;a href=&quot;https://pdm-project.org/latest/usage/venv/&quot;&gt;documentation&lt;/a&gt; to learn more about how to manage virtual environments with PDM.&lt;/p&gt;
&lt;h2&gt;Configure the project&lt;/h2&gt;
&lt;h3&gt;(Optional) Configure package indexes&lt;/h3&gt;
&lt;p&gt;To overcome the network issues, you can configure package indexes to use mirrors of PyPI.&lt;/p&gt;
&lt;p&gt;Add &lt;code&gt;tuna&lt;/code&gt; and &lt;code&gt;sjtug&lt;/code&gt; as package indexes in &lt;code&gt;pyproject.toml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[[tool.pdm.source]]
url = &quot;https://pypi.tuna.tsinghua.edu.cn/simple&quot;
name = &quot;tuna&quot;

[[tool.pdm.source]]
url = &quot;https://mirror.sjtu.edu.cn/pypi/web/simple&quot;
name = &quot;sjtug&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can choose to ignore the stored package indexes and use the aforementioned package indexes only defined in &lt;code&gt;pyproject.toml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pdm config --local pypi.ignore_stored_index true
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Configure development environment&lt;/h3&gt;
&lt;h4&gt;Configure Visual Studio Code&lt;/h4&gt;
&lt;p&gt;Here are some recommended extensions for Visual Studio Code:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;recommendations&quot;: [
    &quot;charliermarsh.ruff&quot;,
    &quot;editorconfig.editorconfig&quot;,
    &quot;ms-python.mypy-type-checker&quot;,
    &quot;ms-python.python&quot;,
    &quot;ms-python.vscode-pylance&quot;,
    &quot;njpwerner.autodocstring&quot;,
    &quot;redhat.vscode-yaml&quot;,
    &quot;tamasfe.even-better-toml&quot;
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can define recommended extensions in workspace scope in &lt;code&gt;.vscode/extensions.json&lt;/code&gt;&lt;/p&gt;
&lt;h5&gt;Configure Python language support&lt;/h5&gt;
&lt;p&gt;Configure Python language support in &lt;code&gt;.vscode/settings.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;python.analysis.autoImportCompletions&quot;: true,
  &quot;python.analysis.extraPaths&quot;: [&quot;.venv/lib/python3.11/site-packages&quot;],
  &quot;python.analysis.fixAll&quot;: [&quot;source.unusedImports&quot;],
  &quot;python.languageServer&quot;: &quot;Pylance&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that &lt;code&gt;extraPaths&lt;/code&gt; should match the path based on the interpeter version of Python in your virtual environment.&lt;/p&gt;
&lt;h4&gt;(Optional) Configure &lt;code&gt;.editorconfig&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://editorconfig.org/&quot;&gt;EditorConfig&lt;/a&gt; is a tool that helps developers define and maintain consistent coding styles between different editors and IDEs.&lt;/p&gt;
&lt;p&gt;Here is an example of &lt;code&gt;.editorconfig&lt;/code&gt; for Python projects:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;root = true

[*]
charset = utf-8
trim_trailing_whitespace = true
end_of_line = lf
indent_style = space
insert_final_newline = true
indent_size = 2

[*.py]
indent_size = 4

[pyproject.toml]
indent_size = 4
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Manage development dependencies&lt;/h3&gt;
&lt;p&gt;In this section, the tutorial will cover the following topics:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Linting and formatting&lt;/li&gt;
&lt;li&gt;Testing&lt;/li&gt;
&lt;li&gt;Documentation&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Linting and formatting&lt;/h4&gt;
&lt;h5&gt;Configure &lt;code&gt;ruff&lt;/code&gt;&lt;/h5&gt;
&lt;p&gt;&lt;code&gt;ruff&lt;/code&gt; is an extremely fast Python linter and code formatter, written in Rust.&lt;/p&gt;
&lt;p&gt;Since v0.1.2, &lt;code&gt;ruff&lt;/code&gt; supports both &lt;em&gt;linting&lt;/em&gt; and &lt;em&gt;formatting&lt;/em&gt; with outstanding performance compared to existing tools. As a result, &lt;code&gt;ruff&lt;/code&gt; is recommended as the default linter and formatter for Python projects in this tutorial.&lt;/p&gt;
&lt;p&gt;Add &lt;code&gt;ruff&lt;/code&gt; as a development dependency in group &lt;code&gt;lint&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pdm add -dG lint ruff
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Configure &lt;code&gt;ruff&lt;/code&gt; in &lt;code&gt;pyproject.toml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[tool.ruff]
select = [
    &quot;B&quot;, # flake8-bugbear
    &quot;C4&quot;, # flake8-comprehensions
    &quot;E&quot;, # pycodestyle - Error
    &quot;F&quot;, # Pyflakes
    &quot;I&quot;, # isort
    &quot;W&quot;, # pycodestyle - Warning
    &quot;UP&quot;, # pyupgrade
]
ignore = [
    &quot;E501&quot;, # line-too-long
    &quot;W191&quot;, # tab-indentation
]
include = [&quot;**/*.py&quot;, &quot;**/*.pyi&quot;, &quot;**/pyproject.toml&quot;]

[tool.ruff.pydocstyle]
convention = &quot;google&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Configure &lt;code&gt;ruff&lt;/code&gt; in &lt;code&gt;.vscode/settings.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;[python]&quot;: {
    &quot;editor.codeActionsOnSave&quot;: {
      &quot;source.fixAll&quot;: true,
      &quot;source.organizeImports&quot;: true
    },
    &quot;editor.defaultFormatter&quot;: &quot;charliermarsh.ruff&quot;,
    &quot;editor.formatOnSave&quot;: true,
    &quot;editor.rulers&quot;: [88]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Please refer to the &lt;a href=&quot;https://docs.astral.sh/ruff&quot;&gt;documentation of &lt;code&gt;ruff&lt;/code&gt;&lt;/a&gt; for more details about configuration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alternatives:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Linter
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/PyCQA/flake8&quot;&gt;PyCQA/flake8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/pylint-dev/pylint&quot;&gt;pylint-dev/pylint&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Formatter
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/psf/black&quot;&gt;psf/black&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/google/pyink&quot;&gt;google/pyink&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/google/yapf&quot;&gt;google/yapf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/hhatto/autopep8&quot;&gt;hhatto/autopep8&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;Configure &lt;code&gt;mypy&lt;/code&gt;&lt;/h5&gt;
&lt;p&gt;&lt;code&gt;mypy&lt;/code&gt; is an optional static type checker for Python.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pdm add -dG lint mypy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Configure &lt;code&gt;mypy&lt;/code&gt; in &lt;code&gt;pyproject.toml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[tool.mypy]
strict = true
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;If you run mypy with the --strict flag, you will basically never get a type related error at runtime without a corresponding mypy error, unless you explicitly circumvent mypy somehow.&lt;/p&gt;
&lt;p&gt;However, this flag will probably be too aggressive if you are trying to add static types to a large, existing codebase. See &lt;a href=&quot;https://mypy.readthedocs.io/en/stable/existing_code.html#existing-code&quot;&gt;Using mypy with an existing codebase&lt;/a&gt; for suggestions on how to handle that case.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Configure &lt;code&gt;mypy&lt;/code&gt; in &lt;code&gt;.vscode/settings.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;mypy-type-checker.path&quot;: [&quot;.venv/bin/mypy&quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Please refer to the &lt;a href=&quot;https://mypy.readthedocs.io/en/stable/config_file.html&quot;&gt;documentation of &lt;code&gt;mypy&lt;/code&gt;&lt;/a&gt; for more details about configuration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alternatives:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/microsoft/pyright&quot;&gt;microsoft/pyright&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/google/pytype&quot;&gt;google/pytype&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/facebook/pyre-check&quot;&gt;facebook/pyre-check&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;Configure PDM scripts&lt;/h5&gt;
&lt;p&gt;PDM supports running scripts or commands with local packages loaded, similar to &lt;code&gt;npm&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[tool.pdm.scripts]
lint = &quot;ruff .&quot;
fmt = &quot;ruff format .&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Please refer to the &lt;a href=&quot;https://pdm-project.org/latest/usage/scripts/&quot;&gt;documentaion of PDM scripts&lt;/a&gt; for more details.&lt;/p&gt;
&lt;h5&gt;(Optional) Configure &lt;code&gt;pre-commit&lt;/code&gt;&lt;/h5&gt;
&lt;p&gt;&lt;a href=&quot;https://pre-commit.com/&quot;&gt;&lt;code&gt;pre-commit&lt;/code&gt;&lt;/a&gt; is a framework for managing and maintaining multi-language pre-commit hooks.&lt;/p&gt;
&lt;p&gt;Add &lt;code&gt;pre-commit&lt;/code&gt; as a development dependency in group &lt;code&gt;lint&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pdm add -dG lint pre-commit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add a pre-commit configuration file &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: check-yaml
        args:
          - --unsafe
      - id: end-of-file-fixer
      - id: trailing-whitespace
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.1.2
    hooks:
      - id: ruff
      - id: ruff-format
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.6.1
    hooks:
      - id: mypy
        language: system
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install the Git hook scripts:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pdm run pre-commit install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pre-commit&lt;/code&gt; will be triggered automatically before each commit.&lt;/p&gt;
&lt;p&gt;Besides, you can also modify the aforementioned &lt;code&gt;pdm.scripts&lt;/code&gt; section in &lt;code&gt;pyproject.toml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[tool.pdm.scripts]
- lint = &quot;ruff .&quot;
- fmt = &quot;ruff format .&quot;
+ lint = &quot;pre-commit run --all-files&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can run &lt;code&gt;pdm run lint&lt;/code&gt; to lint the project manually.&lt;/p&gt;
&lt;h4&gt;Testing&lt;/h4&gt;
&lt;h5&gt;Configure &lt;code&gt;pytest&lt;/code&gt;&lt;/h5&gt;
&lt;p&gt;Pytest is a testing framework for Python.&lt;/p&gt;
&lt;p&gt;Add &lt;code&gt;pytest&lt;/code&gt; and &lt;code&gt;pytest-cov&lt;/code&gt; as development dependencies in group &lt;code&gt;test&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pdm add -dG pytest pytest-cov
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Configure testing support in &lt;code&gt;.vscode/settings.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;python.testing.pytestArgs&quot;: [&quot;tests&quot;],
  &quot;python.testing.pytestEnabled&quot;: true,
  &quot;python.testing.pytestPath&quot;: &quot;.venv/bin/pytest&quot;,
  &quot;python.testing.unittestEnabled&quot;: false
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the configuration above, you can run tests with the built-in test runner of Visual Studio Code.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alternatives:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;unittest&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;(Optional) Documentation&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://www.mkdocs.org/&quot;&gt;MkDocs&lt;/a&gt; is a static site generator for project documentation, and &lt;a href=&quot;https://squidfunk.github.io/mkdocs-material/&quot;&gt;mkdocs-material&lt;/a&gt; is a powerful documentation framework on top of MkDocs.&lt;/p&gt;
&lt;p&gt;Add &lt;code&gt;mkdocs&lt;/code&gt; and &lt;code&gt;mkdocs-material&lt;/code&gt; as development dependencies in group &lt;code&gt;docs&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pdm add -dG docs mkdocs mkdocs-material
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Configure YAML schema validation in &lt;code&gt;.vscode/settings.json&lt;/code&gt; as mentioned in the &lt;a href=&quot;https://squidfunk.github.io/mkdocs-material/creating-your-site/#minimal-configuration&quot;&gt;documentation&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;yaml.schemas&quot;: {
    &quot;https://squidfunk.github.io/mkdocs-material/schema.json&quot;: &quot;mkdocs.yml&quot;
  },
  &quot;yaml.customTags&quot;: [
    &quot;!ENV scalar&quot;,
    &quot;!ENV sequence&quot;,
    &quot;tag:yaml.org,2002:python/name:material.extensions.emoji.to_svg&quot;,
    &quot;tag:yaml.org,2002:python/name:material.extensions.emoji.twemoji&quot;,
    &quot;tag:yaml.org,2002:python/name:pymdownx.superfences.fence_code_format&quot;
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Initialize a new documentation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pdm run mkdocs new .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will create the following structure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;├─ docs/
│  └─ index.md
└─ mkdocs.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Enable &lt;code&gt;mkdocs-material&lt;/code&gt; in &lt;code&gt;mkdocs.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;theme:
  name: material
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add a script to run &lt;code&gt;mkdocs&lt;/code&gt; in &lt;code&gt;pyproject.toml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[tool.pdm.scripts]
docs = &quot;mkdocs serve&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start the development server:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pdm run docs
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The detailed configuration of &lt;code&gt;mkdocs&lt;/code&gt; and &lt;code&gt;mkdocs-material&lt;/code&gt; is out of the scope of this tutorial. Please refer to the &lt;a href=&quot;https://squidfunk.github.io/mkdocs-material/getting-started/&quot;&gt;documentation&lt;/a&gt; for more details.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alternatives:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/sphinx-doc/sphinx&quot;&gt;sphinx-doc/sphinx&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/pradyunsg/furo&quot;&gt;pradyunsg/furo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/lepture/shibuya&quot;&gt;lepture/shibuya&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/facebook/docusaurus&quot;&gt;facebook/docusaurus&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/vuejs/vitepress&quot;&gt;vuejs/vitepress&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Build and Publish&lt;/h2&gt;
&lt;p&gt;After configuring the project and finishing coding and testing, you can build and publish the project to &lt;a href=&quot;https://pypi.org/&quot;&gt;PYPI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;PDM also provides a &lt;a href=&quot;https://peps.python.org/pep-0517/&quot;&gt;PEP 517&lt;/a&gt; build &lt;a href=&quot;https://backend.pdm-project.org/&quot;&gt;backend&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[build-system]
requires = [&quot;pdm-backend&quot;]
build-backend = &quot;pdm.backend&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Configure project metadata&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://www.python.org/dev/peps/pep-0621/&quot;&gt;PEP 621&lt;/a&gt; is a standard for Python project metadata.&lt;/p&gt;
&lt;p&gt;Define project metadata in &lt;code&gt;pyproject.toml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[project]
name = &quot;my-project&quot;
version = &quot;0.1.0&quot;
description = &quot;&quot;
authors = [{name = &quot;rudeigerc&quot;, email=&quot;rudeigerc@gmail.com&quot;}]
dependencies = []
requires-python = &quot;&amp;gt;=3.11&quot;
readme = &quot;README.md&quot;
license = {text = &quot;MIT&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Package version&lt;/h4&gt;
&lt;p&gt;PDM backend supports dynamic project version.&lt;/p&gt;
&lt;p&gt;Remove the &lt;code&gt;version&lt;/code&gt; field and import &lt;code&gt;version&lt;/code&gt; in the &lt;code&gt;dynamic&lt;/code&gt; field in &lt;code&gt;pyproject.toml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[project]
- version = &quot;0.1.0&quot;
+ dynamic = [&quot;version&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;From a given file path&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;[tool.pdm.version]
source = &quot;file&quot;
path = &quot;src/my-package/__init__.py&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The file &lt;code&gt;src/my-package/__init__.py&lt;/code&gt; should contain a &lt;code&gt;__version__&lt;/code&gt; variable:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;__version__ = &quot;0.1.0&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;From SCM tag&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;[tool.pdm.version]
source = &quot;scm&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tag current commit with version &lt;code&gt;v0.0.1&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git tag v0.0.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check the artifacts with &lt;code&gt;pdm build&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pdm build
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Configure PYPI&lt;/h3&gt;
&lt;p&gt;To upload your package to PYPI, you need to create an account on &lt;a href=&quot;https://pypi.org/&quot;&gt;PYPI&lt;/a&gt; and generate an API token in &lt;code&gt;Account settings &amp;gt; API Tokens &amp;gt; Add API Token&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With your username and the generated token, configure PYPI remote repository in &lt;code&gt;pyproject.toml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pdm config repository.pypi.url https://pypi.org/simple
pdm config repository.pypi.username &amp;lt;username&amp;gt;
pdm config repository.pypi.password &amp;lt;api-token&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check the configuration of repository &lt;code&gt;pypi&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pdm config
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The outcome is similar to the following:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Home configuration (config.toml):
repository.pypi.url = &quot;https://pypi.org/simple&quot;
repository.pypi.username = &amp;lt;username&amp;gt;
repository.pypi.password = &amp;lt;api-token&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Build and publish the project to PYPI:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pdm publish -r pypi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that for &lt;strong&gt;test&lt;/strong&gt; purpose, you should publish the project to &lt;a href=&quot;https://test.pypi.org/&quot;&gt;Test PYPI&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pdm config repository.testpypi.url https://test.pypi.org/simple
pdm config repository.testpypi.username &amp;lt;username&amp;gt;
pdm config repository.testpypi.password &amp;lt;api-token&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;pdm publish -r testpypi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Please refer the documenation of &lt;a href=&quot;https://backend.pdm-project.org/metadata/&quot;&gt;PDM Backend&lt;/a&gt; and &lt;a href=&quot;https://packaging.python.org/en/latest/tutorials/packaging-projects/&quot;&gt;Python Package User Guide&lt;/a&gt; for more details about publishing Python packages.&lt;/p&gt;
&lt;h2&gt;Clean up&lt;/h2&gt;
&lt;h3&gt;Delete the project&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;cd .. &amp;amp;&amp;amp; rm -rf my-project
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Appendix&lt;/h2&gt;
&lt;h3&gt;Libraries&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/astral-sh/ruff&quot;&gt;ruff&lt;/a&gt; - An extremely fast Python linter and code formatter, written in Rust.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/python/mypy&quot;&gt;mypy&lt;/a&gt; - Optional static typing for Python&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mkdocs/mkdocs&quot;&gt;mkdocs&lt;/a&gt; - Project documentation with Markdown.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/squidfunk/mkdocs-material&quot;&gt;mkdocs-material&lt;/a&gt; - Documentation that simply works&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/pytest-dev/pytest&quot;&gt;pytest&lt;/a&gt; - The pytest framework makes it easy to write small tests, yet scales to support complex functional testing&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/pytest-dev/pytest-cov&quot;&gt;pytest-cov&lt;/a&gt; - Coverage plugin for pytest.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/pre-commit/pre-commit&quot;&gt;pre-commit&lt;/a&gt; - A framework for managing and maintaining multi-language pre-commit hooks.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Template&lt;/h3&gt;
&lt;p&gt;Since v2.8, PDM supports initializing a project from a template.&lt;/p&gt;
&lt;p&gt;I have created a template &lt;a href=&quot;https://github.com/rudeigerc/pdm-template-rudeigerc&quot;&gt;rudeigerc/pdm-template-rudeigerc&lt;/a&gt; for my personal use mainly based on this tutorial.&lt;/p&gt;
&lt;p&gt;You can initialize a project from this template with the following command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pdm init https://github.com/rudeigerc/pdm-template-rudeigerc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here are some other recommended Python project templates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/microsoft/python-package-template&quot;&gt;microsoft/python-package-template&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Tutorial</category><category>Python</category></item><item><title>KubeCon + CloudNativeCon + Open Source Summit China 2023 Recap</title><link>https://rudeigerc.dev/posts/kubecon-china-2023/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/kubecon-china-2023/</guid><description>2023 年 9 月 26 日至 9 月 28 日，KubeCon + CloudNativeCon + Open Source Summit China 2023 时隔四年在上海线下召开。本文将会对一些我现场观看的议题进行简单的介绍与梳理。</description><pubDate>Wed, 04 Oct 2023 05:15:39 GMT</pubDate><content:encoded>&lt;p&gt;2023 年 9 月 26 日至 9 月 28 日，KubeCon + CloudNativeCon + Open Source Summit China 2023 时隔四年在上海线下召开。&lt;/p&gt;
&lt;p&gt;近期我的工作主要集中在 AI Platform 的开发以及 Ray 与 Kubernetes 的集成，因此我选择去听的议题主要集中在 Platform Engineering 与 AI，以及 Ray Ecosystem 相关的 Topic，在接下来的部分我将会对一些现场观看的议题进行简单的介绍与梳理。&lt;/p&gt;
&lt;h2&gt;Day 1&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://kccncosschn2023.sched.com/event/1PTGw/zhi-kuberayrekueuenanokubernetesfa-sailing-raydu-zhe-sailing-ray-workloads-with-kuberay-and-kueue-in-kubernetes-jason-hu-volcano-engine-kante-yin-daocloud?iframe=no&amp;amp;w=100%25&amp;amp;sidebar=yes&amp;amp;bg=no&quot;&gt;使用KubeRay和Kueue在Kubernetes中托管Sailing Ray工作负载 | Sailing Ray Workloads with KubeRay and Kueue in Kubernetes - Jason Hu, Volcano Engine &amp;amp; Kante Yin, DaoCloud&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;两位演讲者分别是来自字节跳动的火山引擎的 Jason Hu 与来自 DaoCloud 的 Kante Yin，他们分别介绍了 KubeRay 在字节跳动的应用与优化以及 KubeRay 与 Kueue 的集成。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/ray-project/kuberay&quot;&gt;KubeRay&lt;/a&gt; 是 Ray 团队所开源的 Kubernetes Operator，可通过对 Ray 相关的 CRD 的声明便利地将 Ray 集群部署到 Kubernetes 集群之上。目前 KubeRay 在行文的时间点还处于 1.0 RC 的阶段，但是在 Beta 版本时期就已经有许多公司在生产环境中使用了，如 &lt;a href=&quot;https://engineering.atspotify.com/2023/02/unleashing-ml-innovation-at-spotify-with-ray/&quot;&gt;Spotify&lt;/a&gt; 与 &lt;a href=&quot;https://shopify.engineering/merlin-shopify-machine-learning-platform&quot;&gt;Shopify&lt;/a&gt; 等，也因此字节跳动内部也基于 KubeRay 针对内部的工作负载进行了一定程度的优化，演讲者对这些优化进行了简单的介绍。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/kubernetes-sigs/kueue&quot;&gt;Kueue&lt;/a&gt; 是一个 Kubernetes 原生的作业队列系统，它的目标是与 Kubernetes 内置的 Scheduler、Job Controller 等组件集成，并形成一个端到端的作业管理的解决方案，而不是去替换掉前述的这些组件的功能。实际上 Kueue 在设计初期的时候我就有在关注，与 Volcano（kube-batch）和 Yunikorn 这些已有的针对批处理作业的调度系统相比，Kueue 的设计理念更加贴近于 Kubernetes 的原生设计，而且提供了更加细粒度的多租户弹性配额机制。&lt;/p&gt;
&lt;p&gt;当然不可否认的是从集群资源管理的角度而言实际上 Kueue 与 Volcano 的职能实际上还是有一些重叠的，目前包括 KubeRay 在内的很多大数据与机器学习框架的 Operator 都为与 Volcano 的集成提供了第一方的支持，因此如何吸引用户切换到 Kueue 我认为还有很长的一段路要走。如果读者有兴趣的话可以访问 &lt;a href=&quot;https://docs.google.com/document/d/1jFdQPlGnvjCSOrtAFxzGxEMi9z-OS0VVD1uTfSGHXts/preview&quot;&gt;Kueue 的设计文档&lt;/a&gt; 以及 Google Cloud 的 Tutorial &lt;a href=&quot;https://cloud.google.com/kubernetes-engine/docs/tutorials/kueue-intro?hl=zh-cn&quot;&gt;使用 Kueue 部署批处理系统&lt;/a&gt; 对 Kueue 更深入地了解。&lt;/p&gt;
&lt;p&gt;由于 KubeRay 和 Kueue 两者我先前都有基本的了解，所以原先希望能够听到更加深入的内容，但是实际上两部分的内容都比较偏向于介绍性质，这一点就我个人来说还是比较可惜。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;相关仓库：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ray-project/kuberay&quot;&gt;ray-project/kuberay&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kubernetes-sigs/kueue&quot;&gt;kubernetes-sigs/kueue&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;a href=&quot;https://kccncosschn2023.sched.com/event/1PTGM/xi-xin-daep27daizha-27dshu-nanokubernetes-chang-ai-from-zero-to-infinitydaephow-ai-powered-hedge-fund-build-cloud-native-ai-platform-on-kubernetes-yang-che-alibaba-cloud-zhiyi-li-metabit-trading?iframe=no&amp;amp;w=100%25&amp;amp;sidebar=yes&amp;amp;bg=no&quot;&gt;从零到无穷大：如何基于AI技术的对冲基金在Kubernetes上构建云原生AI平台 | From Zero to Infinity：How AI-Powered Hedge Fund Build Cloud-Native AI Platform on Kubernetes - Yang Che, Alibaba Cloud &amp;amp; Zhiyi Li, Metabit Trading&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;两位演讲者主要分别是来自阿里云的 Yang Che 与来自乾象投资的 Zhiyi Li，他们介绍了如何利用 Fluid 与 JuiceFS 构建弹性分布式缓存解决方案，以面对突发的工作负载的变化。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/megabit-trading-tach-landscape.png&quot; alt=&quot;megabit-trading-tach-landscape&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;juicedata/juicefs&quot;&gt;JuiceFS&lt;/a&gt; 是一个面向云原生设计的高性能分布式文件系统，其采用将数据与元数据分离的架构，从而实现文件系统的分布式设计。
乾象投资选择 JuiceFS 作为底层的存储，他们的工作负载会与研究员本身的任务有强烈的关联性，所以可能会产生突发的工作负载的变化，这个变化可能高达千倍。
因此除了存储本身，他们还需要一个能够提供数据编排与缓存管理的解决方案。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;fluid-cloudnative/fluid&quot;&gt;Fluid&lt;/a&gt; 是一个开源的 Kubernetes 原生的分布式数据集编排和加速引擎，该项目起源于南京大学的 PASALab，由国内的高校发起并且最终形成比较成熟的开源社区的项目还是挺少见的，虽然应该阿里云也有一定程度上的参与，但是我认为也是个很有意义的尝试。回归项目本身，Fluid 通过分布式缓存以及亲和性调度为加速数据访问提供了良好的优化，对 AI 和大数据的工作负载提供弹性的计算资源和数据吞吐的支持，能够有效地减少训练过程中所消耗的时间从而减少企业在云平台上的成本。&lt;/p&gt;
&lt;p&gt;由于该议题的演讲者与笔者同样来自于量化公司，我认为同为量化公司的使用场景应该相当类似，所以希望能从演讲中参考借鉴一些解决方案，而我先前也比较少关注在存储相关的优化上，希望有机会能再多多交流。&lt;/p&gt;
&lt;p&gt;JuiceFS 官方也有博客介绍其它采用 Fluid 与 JuiceFS 的解决方案，详见&lt;a href=&quot;https://juicefs.com/zh-cn/blog/solutions/fluid-with-juicefs&quot;&gt;如何在 Kubernetes 集群中玩转 Fluid + JuiceFS&lt;/a&gt; 与 JuiceFS Cloud 的&lt;a href=&quot;https://juicefs.com/docs/zh/cloud/kubernetes/fluid/&quot;&gt;在 Fluid 中使用 JuiceFS&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;相关仓库：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/juicedata/juicefs&quot;&gt;juicedata/juicefs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/fluid-cloudnative/fluid&quot;&gt;fluid-cloudnative/fluid&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;相关论文：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rong Gu, Kai Zhang, Zhihao Xu, Yang Che, Bin Fan, Haojun Hou, Haipeng Dai, Li Yi, Yu Ding, Guihai Chen, and Yihua Huang. 2022. Fluid: Dataset Abstraction and Elastic Acceleration for Cloud-native Deep Learning Training Jobs. In 2022 IEEE 38th International Conference on Data Engineering (ICDE), IEEE, Kuala Lumpur, Malaysia, 2182–2195. DOI:https://doi.org/10.1109/ICDE53745.2022.00209&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;a href=&quot;https://kccncosschn2023.sched.com/event/1PTF9/nanok8szhi-rayai-fu-jmao-chan-ml-boost-ml-on-heterogeneous-ai-accelerator-with-ray-on-k8s-tiejun-chen-vmware?iframe=yes&amp;amp;w=100%25&amp;amp;sidebar=yes&amp;amp;bg=no&quot;&gt;在k8s上使用Ray对异构AI加速器进行ML增强 | Boost ML on Heterogeneous AI Accelerator with Ray on K8s - Tiejun Chen, VMware&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;演讲者是来自 VMware OCTO 的 Tiejun Chen。他首先介绍了目前现代化的 AI 应用平台存在的问题，主要包括以下几点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;异构的 AI 硬件加速器&lt;/li&gt;
&lt;li&gt;多样的上游机器学习框架&lt;/li&gt;
&lt;li&gt;难以达到最佳的性能&lt;/li&gt;
&lt;li&gt;没有一个云原生且现代化的 AI 平台&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;images/project-yellowstone-overview.png&quot; alt=&quot;project-yellowstone-overview&quot; /&gt;&lt;/p&gt;
&lt;p&gt;演讲者提出了 Project Yellowstone，其目标在于搭建基于 Kubernetes 的从云到边缘的端到端机器学习服务。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/project-yellowstone-architecture.png&quot; alt=&quot;project-yellowstone-architecture&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Yellowstone 提供了一个 Runtime Interposer 作为抽象层衔接机器学习框架（如 Tensorflow）与图优化框架（如 Apache TVM），无缝地介入用户的代码，从而实现对不同的编译器后端的支持。&lt;/p&gt;
&lt;p&gt;Yellowstone 的思路其实挺直观的，本质上就是加了一层抽象屏蔽了编译器后端以及硬件加速器并为用户提供了统一的接口访问，也是很经典的基于 Kubernetes 的设计模式。有关 Yellowstone 目前可以搜索到的消息比较少，笔者也仅在 VMware OCTO 的一篇博客 &lt;a href=&quot;https://octo.vmware.com/vmware-edge-compute-whats-next/&quot;&gt;Let’s Go Over the Edge: What’s Next for VMware Edge Compute&lt;/a&gt; 内有看到相关内容，期待后续项目的发展。演讲者先前也在 Ray Forward 2023 上分享了相同的内容，感兴趣的读者可以先行观看&lt;a href=&quot;https://www.bilibili.com/video/BV1VV411M7jh&quot;&gt;在 Ray 上无缝使能异构 AI 加速器到主流 ML 框架提升机器学习&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;相关仓库：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ray-project/ray&quot;&gt;ray-project/ray&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/apache/tvm&quot;&gt;apache/tvm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Day 2&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://kccncosschn2023.sched.com/event/1PTF3/27dwebassemblyzha-faasdaelsjxia-webassembly-based-faas-framework-with-distributed-machine-learning-capabilities-wilson-wang-bytedance-michael-yuan-second-state?iframe=yes&amp;amp;w=100%25&amp;amp;sidebar=yes&amp;amp;bg=no&quot;&gt;基于WebAssembly的FaaS框架，具备分布式机器学习能力 | WebAssembly-Based FaaS Framework with Distributed Machine Learning Capabilities - Wilson Wang, ByteDance &amp;amp; Michael Yuan, Second State&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;演讲者为来自字节跳动的 Wilson Wang，来自 Second State 的 Michael Yuan 因故缺席。Wilson Song 由软件架构的发展展开，指出目前的软件架构（Monolithic/SOA/Microservice/Serverless）存在的问题，期望能够得到一个在 FaaS 环境中下一代云应用开发的解决方案。演讲者认为 VM、Container 与 Unikernel 这些已有的方案都无法满足他的需求，具体而言，VM 与 Container 无论如何优化在启动时间上与 Unikernal 都有数量级上的差距，而 Unikernel 对不同平台的兼容性较差，同时对并行执行的支持有限而且难以 Debug。因此演讲者最终选择了 WebAssembly 作为解决方案，WebAssembly 具有较低的开销并且易于兼容不同的硬件与操作系统，此外 WebAssembly 社区也有 &lt;a href=&quot;https://github.com/WebAssembly/wasi-nn&quot;&gt;wasi-nn&lt;/a&gt; 插件支持不同硬件进行机器学习的推理。另一方面，演讲者也引入 Ray 作为其底层的分布式计算引擎。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/warrior-faas-ray.png&quot; alt=&quot;warrior-faas-ray&quot; /&gt;&lt;/p&gt;
&lt;p&gt;以 WebAssembly 和 Ray 作为基础，演讲者提出了 Warrior，一个基于 WebAssembly 的 FaaS 框架。Warrior 使用 WasmEdge 自定义了 Ray 的 Worker，这样既支持了 WebAssembly Runtime 也可以利用 Ray 的分布式计算的能力，大部分已有的 FaaS 框架在进行一定的修改之后也能便利地与 Warrior 集成。&lt;/p&gt;
&lt;p&gt;可以看出演讲者本身有相当宏大的愿景，但是项目本身很明显属于探索性质比较强烈的项目，短时间内应该不大可能在生产环境当中使用，而且对 FaaS 框架与 Ray 本身也有一定的侵入性，后续这个项目将如何推动和发展也值得关注。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;相关仓库：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ServiceWeaver/weaver&quot;&gt;ServiceWeaver/weaver&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/WebAssembly/wasi-nn&quot;&gt;WebAssembly/wasi-nn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/WasmEdge/WasmEdge&quot;&gt;WasmEdge/WasmEdge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ray-project/ray&quot;&gt;ray-project/ray&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;相关论文：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sanjay Ghemawat, Robert Grandl, Srdjan Petrovic, Michael Whittaker, Parveen Patel, Ivan Posva, and Amin Vahdat. 2023. Towards Modern Development of Cloud Applications. In Proceedings of the 19th Workshop on Hot Topics in Operating Systems (HOTOS &apos;23). Association for Computing Machinery, New York, NY, USA, 110–117. https://doi.org/10.1145/3593856.3595909&lt;/li&gt;
&lt;li&gt;Simon Shillaker and Peter Pietzuch. 2020. Faasm: Lightweight isolation for efficient stateful serverless computing. In 2020 USENIX annual technical conference (USENIX ATC 20), USENIX Association, 419–433. https://www.usenix.org/conference/atc20/presentation/shillaker&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;a href=&quot;https://kccncosschn2023.sched.com/event/1RSk1/nanokubernetesken-yong-rexia-zha-pian-fa-lia-xu-ni-building-a-fine-grained-and-intelligent-resource-management-system-on-kubernetes-he-cao-wei-shao-bytedance?iframe=yes&amp;amp;w=100%25&amp;amp;sidebar=yes&amp;amp;bg=no&quot;&gt;在Kubernetes上构建一个精细化和智能化的资源管理系统 | Building a Fine-Grained and Intelligent Resource Management System on Kubernetes - He Cao &amp;amp; Wei Shao, ByteDance&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;两位演讲者分别是来自字节跳动的 He Cao 与 Wei Shao，他们介绍了字节跳动开源的细粒度资源管理与调度系统 &lt;a href=&quot;https://github.com/kubewharf/katalyst-core&quot;&gt;Katalyst&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/katalyst-qos-resource-manager.png&quot; alt=&quot;katalyst-qos-resource-manager&quot; /&gt;&lt;/p&gt;
&lt;p&gt;他们详细介绍了 Katalyst 的以下几个使用场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;混部
&lt;ul&gt;
&lt;li&gt;基于 Kubernetes 原有的三种 QoS 级别进行扩展&lt;/li&gt;
&lt;li&gt;通过 Kubelet Hook 的方式对 Kubelet 进行扩展（QoS Resource Manager）&lt;/li&gt;
&lt;li&gt;基于 &lt;a href=&quot;https://en.wikipedia.org/wiki/Proportional%E2%80%93integral%E2%80%93derivative_controller&quot;&gt;PID Algorithm&lt;/a&gt; 的资源预测算法&lt;/li&gt;
&lt;li&gt;多维度（CPU/Memory/SSD/Network）的资源隔离机制&lt;/li&gt;
&lt;li&gt;面向 SRE 的多层级（Cluster/Node Pool/Node/Service）动态配置&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;共享 GPU 调度（GPUShare Plugin）&lt;/li&gt;
&lt;li&gt;拓扑感知的调度（RDMA 的亲和性）&lt;/li&gt;
&lt;li&gt;资源效率套件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其中&lt;em&gt;共享 GPU 调度&lt;/em&gt;与&lt;em&gt;拓扑感知的调度&lt;/em&gt;的部分将会由 He Cao 在 CNCF-hosted Co-located Events North America 2023 上进行更详细的分享（&lt;a href=&quot;https://colocatedeventsna2023.sched.com/event/1Rj4O&quot;&gt;Improving GPU Utilization and Accelerating Model Training with Scheduling Framework and NRI - He Cao, ByteDance&lt;/a&gt;）。&lt;/p&gt;
&lt;p&gt;Katalyst 很明显是想做成一个 All-in-One 的资源管理与调度框架，对标的应该就是阿里云先前开源的 Koordinator，不过在开源社区的治理上看上去还是有着不小的差距。
对于平台开发者而言，Katalyst 的一些设计我觉得在实现企业内部的资源管理框架的时候有很大的参考价值，包括在混部场景下基于自定义 QoS 级别对资源管理的扩展与相应的调度器的扩展等。
对于用户而言，我认为还是要根据自身的工作负载来决定要使用哪些策略才会有较好的优化效果，是否真的需要采用这种 All-in-One 的调度系统还需要 Case-by-Case 地讨论。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;相关仓库：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kubewharf/katalyst-core&quot;&gt;kubewharf/katalyst-core&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;a href=&quot;https://kccncosschn2023.sched.com/event/1PTJM/ping-kubectldaelszha-zhong-shi-daepzhi-llmshu-kuberneteszhong-shi-fa-lia-forget-kubectl-and-talk-to-your-clusters-using-llms-to-simplify-kubernetes-cluster-management-qian-ding-ant-group?iframe=yes&amp;amp;w=100%25&amp;amp;sidebar=yes&amp;amp;bg=no&quot;&gt;忘记kubectl，与您的集群交流：使用LLMs简化Kubernetes集群管理 | Forget Kubectl and Talk to Your Clusters: Using LLMs to Simplify Kubernetes Cluster Management - Qian Ding, Ant Group&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;演讲者 Qian Ding 是蚂蚁集团的 Staff SRE，也是我在蚂蚁集团实习时候的 Leader。项目本身的出发点在于如何利用 LLM 来辅助 troubleshooting，目标是将&lt;em&gt;非结构化数据的自然语言转换成面向 API 的结构化数据&lt;/em&gt;。最初他们尝试自行训练 LLM，并通过模板化的 Prompt 将自然语言转换成特定的参数并通过 Kubernetes 的 API 完成相应的操作，但是面临一些更复杂的条件查询的情况时，前述方案就会失效，实际上也会存在权限管理上的隐患。因此，最终他们将目标缩小到如何将自然语言转换成形如 &lt;code&gt;kubectl get&lt;/code&gt; 的操作。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/kubectl-llm.png&quot; alt=&quot;kubectl-llm&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在 trial and error 之后，最终演讲者采用的方案就如上图所示。受到 &lt;a href=&quot;https://github.com/eosphoros-ai/DB-GPT&quot;&gt;eosphoros-ai/DB-GPT&lt;/a&gt; 的启发，演讲者引入了 Kube Query SQL Adapter，一个将 SQL 语句转换为操作 Kubernetes Controller Runtime 的指令的工具。用户通过自然语言交互后，Prompt Engine 通过先验知识以及与 LLM 交互将自然语言转换成 SQL 的查询语句，并通过前述的 Kube Query 将 SQL 语句转换成相应的查询操作，最终将结果返回给用户。&lt;/p&gt;
&lt;p&gt;LLM 与其它领域的结合一直是近期的热点，实际上在 KubeCon 一开始的 Keynote 当中，有些与 LLM 关联性并不是特别强的 Topic 也在有意识地朝着 LLM 靠拢。生成式 AI 的高速发展在各个领域都颇具争议性，作为软件工程师，我们要如何合理地利用这股热潮提高我们自身的工作效率，我认为是个有趣的话题。举例来说，在代码编写的过程中使用 GitHub Copilot 辅助以及寻求 ChatGPT 的帮助已经是个司空见惯的操作（实际上甚至在本文的行文的过程笔者就受惠于 Copilot 数次）。另一方面，很多文档类的网站也都引入了 Chat AI Bot 辅助用户去进行搜索（如 Grafana Docs 的 Grot），之前在和同事交流的过程中，也有同事提到过如果我们能够自行训练一个 LLM 辅助我们进行用户的答疑的话，是不是能节省很多我们原先耗费在这之上的时间。我认为这个演讲给我们提供了一个很好的切入点，希望在后续能够有机会去尝试一下。&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;今年是我第三次在线下参加 KubeCon，也是我成为社会人之后的第一次，很感谢公司能给我提供这个机会，希望下次能和其他几位相关的同事一起来参会，当然如果有机会的话能自己提交提案的话就更好了。虽然今年相比上次场地有所缩水，不过整体上来说还是挺有意思的，难得见到了不少许久未见的朋友，也有机会和几位新的朋友进行简单的交流。当然需要强调的是，午餐还是一如既往地难吃，以及晚餐依旧抢不到。&lt;/p&gt;
&lt;p&gt;如果读者对其它的议题感兴趣的话可以访问这次会议的&lt;a href=&quot;https://www.lfasiallc.com/kubecon-cloudnativecon-open-source-summit-china/program/schedule/&quot;&gt;日程安排&lt;/a&gt;，大部分的议题演讲者都有上传他们的 Slides，演讲的录播应该在一段时间之后也会被上传到 &lt;a href=&quot;https://www.youtube.com/c/cloudnativefdn&quot;&gt;CNCF 的 YouTube Channel&lt;/a&gt; 上。&lt;/p&gt;
&lt;p&gt;除了前文提及的议题之外，其它也有一些我个人感兴趣但是因为时间冲突或是其它因素没有成行的议题，希望在后续录像放出的时候能够再进行回顾。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://kccncosschn2023.sched.com/event/1PTII/xiao-nanokubernetestoyuzha-cncfossqi-ji-zha-wu-ke-tutorial-a-hitchhikers-guide-to-cncfoss-observability-solutions-around-kubernetes-matthias-haeussler-novatec-consulting-tiffany-jernigan-vmware?iframe=yes&amp;amp;w=100%25&amp;amp;sidebar=yes&amp;amp;bg=no&quot;&gt;教程: 一个关于在Kubernetes周围的CNCF/OSS可观测性解决方案的搭车者指南 | Tutorial: A Hitchhiker&apos;s Guide to CNCF/OSS Observability Solutions Around Kubernetes - Matthias Haeussler, Novatec Consulting &amp;amp; Tiffany Jernigan, VMware&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kccncosschn2023.sched.com/event/1PTKW/zhi-volcanosi-pan-piconanokubernetesai-chan-zha-aidu-zhe-re-run-your-ai-workloads-and-microservices-on-kubernetes-more-easily-and-efficiently-with-volcano-william-wang-huawei-cloud?iframe=yes&amp;amp;w=100%25&amp;amp;sidebar=yes&amp;amp;bg=no&quot;&gt;使用Volcano更轻松高效地在Kubernetes上运行您的AI工作负载和微服务 | Run Your AI Workloads and Microservices on Kubernetes More Easily and Efficiently with Volcano - William Wang, Huawei Cloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kccncosschn2023.sched.com/event/1RSee/fu-nfxiao-zhen-mang-dou-zhao-zha-iomin-ao-how-to-accelerate-model-training-and-eliminate-the-io-bottleneck-for-the-cloud-rui-su-juicedata?iframe=yes&amp;amp;w=100%25&amp;amp;sidebar=yes&amp;amp;bg=no&quot;&gt;如何加速模型训练并消除云计算中的I/O瓶颈 | How to Accelerate Model Training and Eliminate the I/O bottleneck for the Cloud - Rui Su, Juicedata&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kccncosschn2023.sched.com/event/1PTH8/zhi-zhong-shi-kek-yu-jlia-zha-ze-2kze-how-we-scale-up-to-2k-nodes-for-batch-jobs-using-cluster-autoscaler-lei-qian-bytedance?iframe=yes&amp;amp;w=100%25&amp;amp;sidebar=yes&amp;amp;bg=no&quot;&gt;如何使用集群自动缩放器将批处理作业的节点扩展到2k个节点 | How We Scale up to 2k Nodes for Batch Jobs Using Cluster Autoscaler - Lei Qian, ByteDance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kccncosschn2023.sched.com/event/1RT6O/nanokuberneteschang-hou-zha-jhui-zhe-container-live-migration-in-kubernetes-production-environment-yenan-lang-and-hua-liu-tencent?iframe=yes&amp;amp;w=100%25&amp;amp;sidebar=yes&amp;amp;bg=no&quot;&gt;在Kubernetes生产环境中的容器实时迁移 | Container Live Migration in Kubernetes Production Environment - Yenan Lang and Hua Liu, Tencent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kccncosschn2023.sched.com/event/1PTFR/nanonfzhong-shi-fu-pod-zha-dyags-how-can-pod-start-up-be-accelerated-on-nodes-in-large-clusters-paco-xu-daocloud-byron-wang-birentech?iframe=yes&amp;amp;w=100%25&amp;amp;sidebar=yes&amp;amp;bg=no&quot;&gt;如何在大型集群中加速 Pod 的启动？ | How Can Pod Start-up Be Accelerated on Nodes in Large Clusters? - Paco Xu, DaoCloud &amp;amp; Byron Wang, Birentech&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;[^serviceweaver]: Sanjay Ghemawat, Robert Grandl, Srdjan Petrovic, Michael Whittaker, Parveen Patel, Ivan Posva, and Amin Vahdat. 2023. Towards Modern Development of Cloud Applications. In Proceedings of the 19th Workshop on Hot Topics in Operating Systems (HOTOS &apos;23). Association for Computing Machinery, New York, NY, USA, 110–117. https://doi.org/10.1145/3593856.3595909&lt;/p&gt;
&lt;p&gt;[^faasm]: Shillaker, S. and Pietzuch, P. 2020. Faasm: Lightweight isolation for efficient stateful serverless computing. 2020 USENIX annual technical conference (USENIX ATC 20) (Jul. 2020), 419–433.&lt;/p&gt;
</content:encoded><category>Event</category></item><item><title>翻译 | Ray Walkthrough</title><link>https://rudeigerc.dev/posts/ray-walkthrough/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/ray-walkthrough/</guid><description>本文翻译自 Ray v2 Architecture 的 Appendix 部分，便于梳理 Ray 的调度与对象存储的流程。</description><pubDate>Tue, 20 Jun 2023 06:03:10 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文翻译自 &lt;a href=&quot;https://docs.google.com/document/d/1tBw9A4j62ruI5omIJbMxly-la5w4q_TjyJgJL_jN2fI/preview&quot;&gt;Ray v2 Architecture&lt;/a&gt; 的 &lt;a href=&quot;https://docs.google.com/document/d/1tBw9A4j62ruI5omIJbMxly-la5w4q_TjyJgJL_jN2fI/preview#heading=h.kw4bvjvijs8h&quot;&gt;Appendix&lt;/a&gt; 部分，便于梳理 Ray 的调度与对象存储的流程。译文如有错漏还望各位读者斧正。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;以下是 Ray 系统架构的更详细的图和示例。&lt;/p&gt;
&lt;h2&gt;架构图&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;images/architecture-and-protocols.png&quot; alt=&quot;architecture-and-protocols&quot; /&gt;&lt;/p&gt;
&lt;p&gt;协议概览（大部分基于 gRPC）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a. 作业执行、对象引用计数。&lt;/li&gt;
&lt;li&gt;b. 本地资源管理。&lt;/li&gt;
&lt;li&gt;c. 远程/分布式资源管理。&lt;/li&gt;
&lt;li&gt;d. 分布式对象传输。&lt;/li&gt;
&lt;li&gt;e. 大型对象的存储与检索。检索是通过 &lt;code&gt;ray.get&lt;/code&gt; 或是在任务执行的过程中，将任务的 ObjectID 参数替换成对象的值来完成。&lt;/li&gt;
&lt;li&gt;f. 调度器从远程节点获取对象，以满足本地排队任务的依赖。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;作业调度与对象存储的示例&lt;/h2&gt;
&lt;p&gt;我们将逐步展示一个形如如下 Ray 程序的物理执行过程：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@ray.remote
def A():
    y_id = C.remote(B.remote())
    y = ray.get(y_id)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在此示例中，Task A 提交 Task B 和 Task C，Task C 依赖 Task B 的输出。为了便于说明，假设 B 返回一个大型 Object X，而 C 返回一个小型 Object Y。这将使得我们能够展示进程内与共享内存对象存储之间的区别。我们还将展示如果 Task A、B 与 C 在不同的节点上执行会发生什么，以便展示分布式调度的工作原理。&lt;/p&gt;
&lt;h3&gt;分布式作业调度&lt;/h3&gt;
&lt;p&gt;我们由 Worker 1 执行 Task A 开始。Task B 与 Task C 已被提交到 Worker 1 之上，因此 Worker 1 本地的 Ownership 表格已经包括 X 和 Y 的条目。首先，我们会逐步展示一个调度 B 执行过程的示例。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/distributed-task-scheduling.png&quot; alt=&quot;distributed-task-scheduling&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Worker 1 向本地的调度器请求资源执行 Task B。&lt;/li&gt;
&lt;li&gt;Scheduler 1 回复，告诉 Worker 1 在 Node 2 上重试调度请求。&lt;/li&gt;
&lt;li&gt;Worker 1 更新本地 Ownership 表格，表明 Task B 在 Node 2 上 Pending。&lt;/li&gt;
&lt;li&gt;Worker 1 向 Node 2 的调度器请求资源执行 Task B。&lt;/li&gt;
&lt;li&gt;Scheduler 2 向 Worker 1 授予资源并返回 Worker 2 的地址。Scheduler 2 确保当 Worker 1 还保有资源的时候不会有其它的 Task 被安排给 Worker 2。&lt;/li&gt;
&lt;li&gt;Worker 1 将 Task B 发送给 Worker 2 执行。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;作业执行&lt;/h3&gt;
&lt;p&gt;接着，我们会展示一个 Worker 执行 Task 并将返回值存储在分布式对象存储中的例子。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/task-execution.png&quot; alt=&quot;task-execution&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Worker 2 完成执行 Task B 并将返回值 X 存储在它的本地对象存储。
&lt;ol&gt;
&lt;li&gt;Node 2 异步地更新 Object 表格，表明 X 现在在 Node 2 上（虚线箭头）。&lt;/li&gt;
&lt;li&gt;由于这是 X 所被创建的第一个拷贝，Node 2 会固定该拷贝直到 Worker 1 通知 Node 2 可以释放该 Object（未显示）。这保证了 Object 的值在被引用的时候是可达的。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Worker 2 向 Worker 1 回复表明 Task B 已完成。&lt;/li&gt;
&lt;li&gt;Worker 1 更新其本地的 Ownership 表格表明 X 被存储在分布式内存中。&lt;/li&gt;
&lt;li&gt;Worker 1 将资源归还给 Scheduler 2。Worker 2 现在可以使用这些资源执行其它 Task。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;分布式作业调度与参数解析&lt;/h3&gt;
&lt;p&gt;现在 Task B 已完成，Task C 可以开始执行。Worker 1 接着使用与调度 Task B 时相似的协议调度 task C。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/distributed-task-scheduling-and-argument-resolution-a.png&quot; alt=&quot;distributed-task-scheduling-and-argument-resolution-a&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Worker 1 向本地的调度器请求资源执行 Task C。&lt;/li&gt;
&lt;li&gt;Scheduler 1 回复，告诉 Worker 1 在 Node 3 上重试调度请求。&lt;/li&gt;
&lt;li&gt;Worker 1 更新本地 Ownership 表格，表明 Task C 在 Node 3 上 Pending。&lt;/li&gt;
&lt;li&gt;Worker 1 向 Node 3 的调度器请求资源执行 Task B。&lt;/li&gt;
&lt;li&gt;Scheduler 3 发现 Task C 依赖 X，但是它在本地对象存储中没有 X 的拷贝。Scheduler 3 将 Task C 加入队列并向 Object 表格询问 X 的位置。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Task C 需要 X 的本地拷贝开始执行，因此 Node 3 获取了 X 的拷贝。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/distributed-task-scheduling-and-argument-resolution-b.png&quot; alt=&quot;distributed-task-scheduling-and-argument-resolution-b&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Object 表格回复 Scheduler 3 表明 X 在 Node 2 上。&lt;/li&gt;
&lt;li&gt;调度器请求 Node 2 上的对象存储发送一个 X 的拷贝。&lt;/li&gt;
&lt;li&gt;X 从 Node 2 拷贝到 Node 3 上。
&lt;ol&gt;
&lt;li&gt;Node 3 也会异步地更新 Object 表格表明 X 也在 Node 3 上。&lt;/li&gt;
&lt;li&gt;Node 3 的 X 的拷贝被缓存但是不会被固定。当本地 Worker 使用它的时候，该 Object 不会被驱逐。然而，与 Node 2 上的 X 的拷贝不同，Node 3 的拷贝可能会在 Object Store 3 面临内存压力的时候根据 LRU 算法被驱逐。如果前述情况发生，而 Node 3 随后又再次需要该 Object，它可以从 Node 2 再次获取或是使用与前述相同的协议获取另一个拷贝。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Node 3 现在有了 X 的本地拷贝，因此 Scheduler 3 将资源授予 Worker 1 并返回 Worker 3 的地址。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;作业执行与对象内联&lt;/h3&gt;
&lt;p&gt;Task C 执行并返回一个足够小的 Object，可以被存储在进程内内存存储。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/task-execution-and-object-inlining.png&quot; alt=&quot;task-execution-and-object-inlining&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Worker 1 将 Task C 发送给 Worker 3 执行。&lt;/li&gt;
&lt;li&gt;Worker 3 从本地对象存储获取 X 的值（与 &lt;code&gt;ray.get()&lt;/code&gt; 相似）并运行 &lt;code&gt;C(x)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;Worker 3 完成执行 Task C 并返回 Y，这次返回的是值而不是将其存储在本地对象存储中。&lt;/li&gt;
&lt;li&gt;Worker 1 将 Y 存储在它的进程内内存存储中。由于 Task C 已完成执行，它也会消除 Task C 的规格与位置。在这个时间点，Task A 中还未完成的 &lt;code&gt;ray.get()&lt;/code&gt; 调用会从 Worker 1 的进程内存储寻找并返回 y 的值。&lt;/li&gt;
&lt;li&gt;Worker 1 将资源归还给 Scheduler 3。Worker 3 现在可以使用这些资源执行其它 Task。这可能会在步骤 4 之前完成。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;垃圾回收&lt;/h3&gt;
&lt;p&gt;最后，我们会展示内存是如何被 Worker 清理的。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;images/garbage-collection.png&quot; alt=&quot;garbage-collection&quot; /&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Worker 1 消除 Object X 的条目。这是安全的操作，因为 Pending Task C 过去有对 X 的唯一引用，而现在 Task C 已经完成了。Worker 1 保留 Y 的条目，因为应用依旧拥有对 y 的 ObjectID 的引用。
&lt;ol&gt;
&lt;li&gt;最终，所有集群中的 X 的拷贝都会被删除。这可以在步骤 1 之后的任意时间点完成。如前所述，如果 Node 3 的对象存储面临内存压力的情况下，Node 3 的 X 的拷贝可能在步骤 1 之前就被删除。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded><category>Translation</category></item><item><title>在 Github Actions 中动态地为 Matrix 赋值</title><link>https://rudeigerc.dev/posts/github-actions-dynamic-matrices/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/github-actions-dynamic-matrices/</guid><description>本文介绍了如何在 GitHub Actions 中动态地为 Matrix 赋值，以实现在不同的 Event 下运行不同的 Job。</description><pubDate>Sun, 12 Jun 2022 16:02:32 GMT</pubDate><content:encoded>&lt;p&gt;最近在参与开源项目 &lt;a href=&quot;https://github.com/tensorchord/envd&quot;&gt;tensorchord/envd&lt;/a&gt; 的时候，我们遇到了需要动态处理 GitHub Actions 中的 Matrix 的场景，因而撰写本文希望给遇到类似问题的朋友一些参考。&lt;/p&gt;
&lt;p&gt;由于该项目在创始之初是私有的，而在私有仓库中 GitHub 对 CI 的使用时间进行了限制，其中 macOS 的权重比较昂贵，因此我们希望只在 Release 的时候运行 macOS 的 CI 来节省使用限额。Release 对应的 GitHub Actions 的 Event 是 Push，可以通过 Git 的 reference 来对推送的内容进行判断，tags 的 reference 会以 &lt;code&gt;refs/tags/&lt;/code&gt; 打头。&lt;/p&gt;
&lt;h2&gt;在 Job 中使用静态的 Matrix&lt;/h2&gt;
&lt;p&gt;Matrix 是可以让用户在单个 Job 内自动地根据 Matrix 中变量的组合创建多个 Job 运行的上下文。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;jobs:
  ...
  test:
    name: test
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest]
    runs-on: ${{ matrix.os }}
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;官方文档中提供了许多静态地使用 Matrix 的例子&lt;a href=&quot;https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs&quot;&gt;^1&lt;/a&gt;。一个比较常见的使用场景就是用户希望在不同的操作系统上运行测试以确保软件在不同操作系统上的兼容性，在上述的例子中，该 Workflow 可以通过 &lt;code&gt;matrix.os&lt;/code&gt; 获取定义在 &lt;code&gt;strategy.matrix&lt;/code&gt; 中的 &lt;code&gt;os&lt;/code&gt; 的值，并将其传递给 &lt;code&gt;runs-on&lt;/code&gt;。在 CI 被触发的时候，GitHub Actions 会创建两个并行的 &lt;code&gt;test&lt;/code&gt; Job，其中一个运行在 &lt;code&gt;ubuntu-latest&lt;/code&gt; 之上，另一个运行在 &lt;code&gt;macos-latest&lt;/code&gt; 之上。&lt;/p&gt;
&lt;h2&gt;在 Job 中使用动态的 Matrix&lt;/h2&gt;
&lt;p&gt;我们的目标在于根据 Push Event 的 ref 来对 &lt;code&gt;matrix.os&lt;/code&gt; 进行动态赋值，而 Matrix 本身并不支持条件判断，我们需要另寻他法。&lt;/p&gt;
&lt;h3&gt;Trial and Error&lt;/h3&gt;
&lt;p&gt;GitHub Actions 支持行内表达式，所以原先的想法是直接利用 &lt;code&gt;AND&lt;/code&gt; 和 &lt;code&gt;OR&lt;/code&gt; 实现分支判断，这个方法有时候在编写前端代码的时候也会运用到。我们希望在 Push Tag 的时候，即 Release 的时候触发 Ubuntu 与 macOS 的测试，默认情况下仅触发在 Ubuntu 上运行测试。使用 &lt;code&gt;github.event_name == &apos;push&apos; &amp;amp;&amp;amp; startsWith(github.ref, &apos;refs/tags/&apos;)&lt;/code&gt; 表达式可以对 Push 事件的对象是否为 tags 进行判断。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;jobs:
  ...
  test:
    name: test
    strategy:
      matrix:
        os: ${{ github.event_name == &apos;push&apos; &amp;amp;&amp;amp; startsWith(github.ref, &apos;refs/tags/&apos;) &amp;amp;&amp;amp; [ubuntu-latest, macos-latest] || [ubuntu-latest] }}
    runs-on: ${{ matrix.os }}
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;The workflow is not valid. .github/workflows/CI.yml (Line: 66, Col: 13): Unexpected symbol: &apos;[&apos;. Located at position 72 within expression: github.event_name == &apos;push&apos; &amp;amp;&amp;amp; startsWith(github.ref, &apos;refs/tags/&apos;) &amp;amp;&amp;amp; [ubuntu-latest, macos-latest] || [ubuntu-latest] .github/workflows/CI.yml (Line: 66, Col: 13): Unexpected value &apos;${{ github.event_name == &apos;push&apos; &amp;amp;&amp;amp; startsWith(github.ref, &apos;refs/tags/&apos;) &amp;amp;&amp;amp; [ubuntu-latest, macos-latest] || [ubuntu-latest] }}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在进行了以上尝试之后，我们得到了 GitHub Actions 的报错，根据错误信息可以发现虽然行内表达式的判断结果是符合我们期望的，但是 GitHub Actions 将其返回值当成了普通的 string 而不是 array，因此仅仅通过行内表达式对 Matrix 进行动态赋值是无法成功的。&lt;/p&gt;
&lt;h3&gt;使用 Output 传递参数&lt;/h3&gt;
&lt;p&gt;通过参考一些文章[^2][^3][^4]，我们发现可以通过 &lt;code&gt;::set-output&lt;/code&gt;&lt;a href=&quot;https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-output-parameter&quot;&gt;^5&lt;/a&gt; 在前一个 Job 对输出赋值，将参数传递给需要动态为 Matrix 赋值的 Job。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;jobs:
  ...
  setup:
    name: setup
    runs-on: ubuntu-latest
    outputs:
      os: ${{ steps.setup.outputs.os }}
    steps:
      - name: setup
        id: setup
        run: |
          if ${{ github.event_name == &apos;push&apos; &amp;amp;&amp;amp; startsWith(github.ref, &apos;refs/tags/&apos;) }}; then
            os=&apos;[&quot;ubuntu-latest&quot;,&quot;macos-latest&quot;]&apos;
          else
            os=&apos;[&quot;ubuntu-latest&quot;]&apos;
          fi
          list=$(echo ${os} | jq -c)
          echo &quot;::set-output name=os::${list}&quot;
  test:
    name: test
    needs: setup
    strategy:
      matrix:
        os: ${{ fromJson(needs.setup.outputs.os) }}
    runs-on: ${{ matrix.os }}
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在上述的例子中，我们首先定义一个新的 Job &lt;code&gt;setup&lt;/code&gt;，在该 Job 中进行与上文相同的判断为 &lt;code&gt;os&lt;/code&gt; 赋值，接着通过 &lt;code&gt;jq&lt;/code&gt; 将 &lt;code&gt;os&lt;/code&gt; 变量从 string 转换成 JSON 格式，并将其赋值给 &lt;code&gt;setup.outputs.os&lt;/code&gt;。在 &lt;code&gt;test&lt;/code&gt; Job 中，将 &lt;code&gt;setup&lt;/code&gt; 设置成 &lt;code&gt;needs&lt;/code&gt; 即可对其输出进行访问，即通过 &lt;code&gt;needs.setup.outputs.os&lt;/code&gt; 获取先前在 &lt;code&gt;setup&lt;/code&gt; Job 中设置成输出的值。我们通过内置的 &lt;code&gt;fromJson&lt;/code&gt; 函数可以将 JSON 对象作为表达式或是从字符串转换环境变量&lt;a href=&quot;https://docs.github.com/cn/actions/learn-github-actions/expressions#fromjson&quot;&gt;^6&lt;/a&gt;，由此可以正确地将 &lt;code&gt;os&lt;/code&gt; 作为 array 对 &lt;code&gt;matrix.os&lt;/code&gt; 进行赋值。&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;通过 &lt;code&gt;::set-output&lt;/code&gt; 与 &lt;code&gt;fromJson&lt;/code&gt; 的结合，我们就可以动态地为包括 Matrix 在内的 Workflow 中的值进行动态赋值，使得 GitHub Actions 的使用更加具有扩展性。&lt;/p&gt;
&lt;p&gt;[^2]: Dynamic Matrices in GitHub Actions. https://thekevinwang.com/2021/09/19/github-actions-dynamic-matrix/&lt;/p&gt;
&lt;p&gt;[^3]: GitHubActionsでmatrixの値を動的に扱う. https://swfz.hatenablog.com/entry/2021/06/29/195359&lt;/p&gt;
&lt;p&gt;[^4]: How to make a Dynamic Matrix in GitHub Actions | Tomas Votruba. https://tomasvotruba.com/blog/2020/11/16/how-to-make-dynamic-matrix-in-github-actions/&lt;/p&gt;
</content:encoded><category>DevOps</category></item><item><title>Working as a Mentee of LFX Mentorship Program with Volcano Community</title><link>https://rudeigerc.dev/posts/lfx-mentorship-volcano/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/lfx-mentorship-volcano/</guid><description>The LFX Mentorship program is a program designed to provide an opportunity for mentees to gain exposure to the development of open-source projects. I was honored to have the opportunity to work closely with the Volcano community, a sandbox project of CNCF, for the system stability enhancement task. In this post, I would like to share my experience and lessons learned during the project as a mentee of the Volcano project.</description><pubDate>Sun, 13 Jun 2021 08:53:30 GMT</pubDate><content:encoded>&lt;p&gt;The &lt;a href=&quot;https://lfx.linuxfoundation.org/tools/mentorship/&quot;&gt;LFX Mentorship&lt;/a&gt; program is a program designed to provide an opportunity for mentees to gain exposure to the development of open-source projects. Cloud Native Computing Foundation, or CNCF, is actively using it as the mentorship platform across the CNCF projects. In spring 2021, I was honored to have the opportunity to work closely with the &lt;a href=&quot;https://github.com/volcano-sh/volcano&quot;&gt;Volcano&lt;/a&gt; community, a sandbox project of CNCF, for the &lt;a href=&quot;https://github.com/cncf/mentoring/tree/master/lfx-mentorship/2021/01-Spring#system-stability-enhancement&quot;&gt;System Stability Enhancement&lt;/a&gt; task. In this post, I would like to share my experience and lessons learned during the project as a mentee of the Volcano project.&lt;/p&gt;
&lt;h2&gt;The Beginning of the Journey&lt;/h2&gt;
&lt;p&gt;Since I was an undergraduate, encouraged by my advisor, I have started my journey to the world of cloud-native. I have also attended KubeCons offline and online several times. It was inspirational to know the latest progress of the development of CNCF projects from the community and some valuable best practices from the industry. Taking this as an opportunity, I started to contribute to open-source projects and gain precious experience working with the open-source communities.&lt;/p&gt;
&lt;p&gt;During my master&apos;s studies, I chose to combine cloud-native technologies with my current research topic, the resource management of distributed stream processing systems with Kubernetes, including elastic scheduling and autoscaling. As a result, I began to deep dive into the solutions of resource scheduling on Kubernetes, both in academia and industry.&lt;/p&gt;
&lt;p&gt;Recommended by &lt;a href=&quot;https://github.com/shinytang6&quot;&gt;Liang Tang&lt;/a&gt;, a friend of mine in university focusing on systems for deep learning related to Kubernetes, I learned of the LFX Mentorship program and the &lt;a href=&quot;https://github.com/volcano-sh/volcano&quot;&gt;Volcano&lt;/a&gt; project. He was also a mentee of this program (ex-Community Bridge) in 2020, and currently an active maintainer of Volcano. He briefly introduced the project and the community to me and I believed it would be a great chance for me to get close to the open-source community and understand the latest solutions in the industry.&lt;/p&gt;
&lt;p&gt;I remembered that was an afternoon of the weekend when I got a phone call from &lt;a href=&quot;https://github.com/Thor-wl&quot;&gt;Lei Wu&lt;/a&gt;, a member of the Volcano community. We had a nice discussion about the understanding of Kubernetes and resource scheduling. I introduced some publications previously related to cluster resource management and scheduling during the discussion. Fortunately, I was informed to be selected as a mentee of the project the next day.&lt;/p&gt;
&lt;h2&gt;Volcano 101&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.rudeigerc.dev/cdn-cgi/imagedelivery/zHp1Y4Kl9MXfXkk0kqREVw/12ac0c7b-b07b-43cf-87d5-1a3b3c32a300/public&quot; alt=&quot;volcano-logo&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.rudeigerc.dev/cdn-cgi/imagedelivery/zHp1Y4Kl9MXfXkk0kqREVw/83e3fc11-5ba6-4437-0f15-2491d2003a00/public&quot; alt=&quot;volcano-architecture&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/volcano-sh/volcano&quot;&gt;Volcano&lt;/a&gt; is a batch scheduling system built on Kubernetes, partly based on &lt;a href=&quot;https://github.com/kubernetes-sigs/kube-batch&quot;&gt;kube-batch&lt;/a&gt;. It provides powerful mechanisms for batching and elastic workloads in cloud-native architecture including machine learning, bioinformatics and big data applications such as batch processing, and stream processing. Compared with Kubernetes default scheduler, Volcano is more capable of various scenarios benefiting from the extensibility gain from its job lifecycle management via manifold plugins.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.rudeigerc.dev/cdn-cgi/imagedelivery/zHp1Y4Kl9MXfXkk0kqREVw/bf0e51dd-1b75-4c98-39f0-ecba7e6ea700/public&quot; alt=&quot;volcano-scheduler-workflow&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This is the main workflow of the scheduler of Volcano. Volcano defines five actions as extension points during the lifecycle of a scheduling session. Users could register user-defined plugins to extension points and these plugins would be executed in the corresponding sequence. The scheduler works as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Watch and then cache the &lt;code&gt;Job&lt;/code&gt; submitted to the Volcano cluster.&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;Session&lt;/code&gt; object to store data required in the current scheduling lifecycle.&lt;/li&gt;
&lt;li&gt;Transfer &lt;code&gt;Job&lt;/code&gt;s not scheduled in the cache to the (to be scheduled) queue in the session.&lt;/li&gt;
&lt;li&gt;Execute the defined actions consequently and find the most suitable node for each &lt;code&gt;Job&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Bind the &lt;code&gt;Job&lt;/code&gt; to the node.&lt;/li&gt;
&lt;li&gt;Close the &lt;code&gt;Session&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;On the Road: How to Move Forward&lt;/h2&gt;
&lt;p&gt;My task was to enhance the system stability by importing a series of unit tests and e2e tests. As for open-source projects, testing is the most important part which ensures the codes meet quality standards. In general, these tests are integrated into the process of continuous integration infrastructure when reviewing the codes and making releases, while its maintainability would be easily ignored because of its lower priority during the rapid iteration.&lt;/p&gt;
&lt;p&gt;E2e tests are designed to provide a mechanism to test the end-to-end behavior of the system and find the bugs that were not detected by unit tests or integration tests. In Kubernetes related ecosystem, developers tend to use &lt;a href=&quot;http://onsi.github.io/ginkgo/&quot;&gt;Ginkgo&lt;/a&gt; and &lt;a href=&quot;http://onsi.github.io/gomega/&quot;&gt;Gomega&lt;/a&gt; as testing frameworks with behavior-driven development (BDD) style. In terms of the Volcano, the e2e testing lacks robustness and extensibility. In particular, there are lots of homogeneous codes with the same functionality, and the structure of the tests is lacking in well-design. Consequently, I started by focusing on the enhancement of e2e tests.&lt;/p&gt;
&lt;p&gt;Since I was not quite familiar with e2e tests based on Kubernetes before, I decided to stand on the shoulders of giants - figuring out some best practices used in Kubernetes and other CNCF projects. I read about the best practices about the e2e tests from sig-testing of Kubernetes community as listed below:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kubernetes/community/blob/master/contributors/devel/sig-testing/e2e-tests.md&quot;&gt;End-to-End Testing in Kubernetes&lt;/a&gt; (with kubetest)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kubernetes/community/blob/master/contributors/devel/sig-testing/e2e-tests-kubetest2.md&quot;&gt;End-to-End Testing in Kubernetes&lt;/a&gt; (with kubetest2)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/kubernetes/community/blob/master/contributors/devel/sig-testing/writing-good-e2e-tests.md&quot;&gt;Writing good e2e tests for Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The posts pointed out that e2e tests with only 99% reliability were useless with the continuous integration environment since these tests would provide unreliable indicators and delay the process of development. This was my key point of optimization for the e2e tests of Volcano.&lt;/p&gt;
&lt;p&gt;Besides, I made connections with developers working with e2e testing based on Kubernetes in other projects. They gave me constructive suggestions about the refactoring of the testing frameworks.&lt;/p&gt;
&lt;p&gt;Based on the survey, I raised my initial proposal to my mentor, including the optimization of the testing structure and the scripts related to continuous integration. During the implementation, I had to follow up the development process of the community, since e2e tests were deeply related to the main functionality. It was impressive that my first pull request was merged after the review and discussion with the community.&lt;/p&gt;
&lt;h3&gt;Lessons What I Have Learned&lt;/h3&gt;
&lt;p&gt;Here are some lessons I have learned during the process of the program:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Share your insights.&lt;/strong&gt; It would be helpful to share the ideas or insights with mentors since they had been working on the project for a long time and more familiar with its architecture. We had meetings weekly to get synced with the latest progress. Sometimes I shared my proposal about the refactoring of the e2e testing or the obstacles met.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Join the meetings.&lt;/strong&gt; The Volcano community held the meeting weekly. It was a great opportunity to get close to the community members and grasp the latest development progress of the project (e.g., topics the community focusing on currently). The community members discussed the issues and pull requests to be processed on that week, and people from various affiliations shared their use cases with the project, though it may be a little bit tough for the beginners to follow their steps.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Discuss with your partners.&lt;/strong&gt; Besides the community meetings, my mentor also held a series of seminars via Zoom meetings with students interested in cloud-native technologies. We made discussions about the fundamentals of cloud-native technologies, including the architecture of Kubernetes and Volcano, and some design philosophies widely used on them. Transforming inputs into outputs was quite challenging but beneficial. My mentor guided us to dive deep into the realistic scenarios, and let us figure out how and why the solution was.&lt;/p&gt;
&lt;h2&gt;Outside the Ivory Tower: It&apos;s Not the End&lt;/h2&gt;
&lt;p&gt;We often say that there is a gap between &lt;em&gt;academia&lt;/em&gt; and &lt;em&gt;industry&lt;/em&gt; because of various scenarios. In terms of academia, researchers tend to gain better performance in certain metrics such as latency and throughput, while lacking the consideration of stability. On the other hand, developers consider stability as first-class citizens since they have to meet the requirements of service level objectives, or SLOs. In summary, implementing cutting-edge research from academia into the industry is quite challenging.&lt;/p&gt;
&lt;p&gt;I believe it is worth my attempt to bridge such a gap via the LFX mentorship program. I have been working closely with the community and gain valuable experience from the operation and communication with community members outside the ivory tower. This experience would also encourage me to continuously explore the world of cloud-native, and contribute to open-source projects.&lt;/p&gt;
&lt;p&gt;Finally, I would like to thank my mentors, &lt;a href=&quot;https://github.com/Thor-wl&quot;&gt;Lei Wu&lt;/a&gt; and &lt;a href=&quot;https://github.com/william-wang&quot;&gt;Leibo Wang&lt;/a&gt;, who have given me valuable suggestions and kind assistance during the program, as well as the community members of the Volcano community. Besides, I also want to thank &lt;a href=&quot;https://github.com/shinytang6&quot;&gt;Liang Tang&lt;/a&gt; and &lt;a href=&quot;https://github.com/gaocegege&quot;&gt;Ce Gao&lt;/a&gt; for their primitive guidance of the project, and &lt;a href=&quot;https://github.com/dragonly&quot;&gt;Yilong Li&lt;/a&gt; for sharing his experience about the refactoring of the e2e testing framework in the TiDB community. Thanks to Linux Foundation and Cloud Native Computing Foundation for providing the platform and organizing the mentorship program.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cncf.io/blog/2021/02/26/volcano-collision-between-containers-and-batch-computing/&quot;&gt;Volcano: Collision between containers and batch computing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Paper Reading | EuroSys &apos;20 | Borg: the Next Generation</title><link>https://rudeigerc.dev/posts/borg-the-next-generation/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/borg-the-next-generation/</guid><description>Muhammad Tirmazi, Adam Barker, Nan Deng, Md E. Haque, Zhijing Gene Qin, Steven Hand, Mor Harchol-Balter, and John Wilkes. 2020. Borg: the next generation. In Proceedings of the Fifteenth European Conference on Computer Systems (EuroSys &apos;20). Association for Computing Machinery, New York, NY, USA, Article 30, 1–14. DOI:https://doi.org/10.1145/3342195.3387517</description><pubDate>Sat, 24 Apr 2021 14:21:33 GMT</pubDate><content:encoded>&lt;p&gt;Borg 是 Google 内部使用的集群资源管理系统，也是 Kubernetes 的前身，因此可以在 Kubernetes 的架构中看见不少 Borg 的影子[^1]，有关 Borg 本身架构的介绍可以参见 Google 于 EuroSys 2015 上发表的论文[^2]。Google 在 2020 年 04 月发布了有关 Borg 集群管理系统的最新跟踪数据[^3]，这是自 2011 年以来发布的最新数据，包括 Machine events、Collection events、Instance events 与 Instance usage 四个维度的数据，总计 2.8 TiB。Google 同时也在 EuroSys 2020 上发表了对其进行分析的论文[^4]，该文将 2019 年的数据与 2011 年的数据进行了全方面的对比，包括资源利用率与资源消耗等，同时指出了两者都存在的重尾效应，并在文末给出了一些可能的研究方向。本文主要基于上述论文进行介绍，并基于文末所提出的可能的研究方向进行一些简单的讨论。&lt;/p&gt;
&lt;p&gt;[^1]: Borg: Kubernetes 的前身. https://kubernetes.io/zh/blog/2015/04/borg-predecessor-to-kubernetes/&lt;/p&gt;
&lt;p&gt;[^2]: Abhishek Verma, Luis Pedrosa, Madhukar Korupolu, David Oppenheimer, Eric Tune, and John Wilkes. 2015. Large-scale cluster management at Google with Borg. In &amp;lt;i&amp;gt;Proceedings of the Tenth European Conference on Computer Systems&amp;lt;/i&amp;gt; (&amp;lt;i&amp;gt;EuroSys &apos;15&amp;lt;/i&amp;gt;). Association for Computing Machinery, New York, NY, USA, Article 18, 1–17. DOI:https://doi.org/10.1145/2741948.2741964&lt;/p&gt;
&lt;p&gt;[^3]: John Wilkes. Yet More Google Compute Cluster Trace Data. https://ai.googleblog.com/2020/04/yet-more-google-compute-cluster-trace.html&lt;/p&gt;
&lt;p&gt;[^4]: Muhammad Tirmazi, Adam Barker, Nan Deng, Md E. Haque, Zhijing Gene Qin, Steven Hand, Mor Harchol-Balter, and John Wilkes. 2020. Borg: the next generation. In Proceedings of the Fifteenth European Conference on Computer Systems (EuroSys &apos;20). Association for Computing Machinery, New York, NY, USA, Article 30, 1–14. DOI:https://doi.org/10.1145/3342195.3387517&lt;/p&gt;
&lt;h2&gt;Borg&lt;/h2&gt;
&lt;h3&gt;基本概念&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;集群与单元
&lt;ul&gt;
&lt;li&gt;集群（cluster）
&lt;ul&gt;
&lt;li&gt;一个 Google &lt;em&gt;集群&lt;/em&gt;由一组机器组成，这些机器被放置在物理机价上，并且由高带宽的集群网络连接&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;单元（cell）
&lt;ul&gt;
&lt;li&gt;一个&lt;em&gt;单元&lt;/em&gt;由一组机器组成，通常都在一个集群之中，这些机器共享一个集群管理系统&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;作业与任务
&lt;ul&gt;
&lt;li&gt;作业（job）
&lt;ul&gt;
&lt;li&gt;一个&lt;em&gt;作业&lt;/em&gt;由一个或多个任务组成，其描述&lt;strong&gt;用户想运行的计算&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;任务（task）
&lt;ul&gt;
&lt;li&gt;一个任务代表一个将会在单个机器上运行的 Linux 程序，可能由多个进程组成&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;alloc
&lt;ul&gt;
&lt;li&gt;一个 alloc set 由一个或多个 alloc（实例）组成，其描述&lt;strong&gt;作业可以在其中运行的资源保留&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;一个作业可以指定一个 alloc set，该作业会在其中运行，在这种情况下，它的每个任务将在该 alloc set 的一个alloc instance 中运行&lt;/li&gt;
&lt;li&gt;如果一个作业没有指定 alloc set，那么它的任务将直接使用机器的资源&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;集合与实例
&lt;ul&gt;
&lt;li&gt;集合（collection）
&lt;ul&gt;
&lt;li&gt;作业与 alloc set 的总称&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;实例（instance）
&lt;ul&gt;
&lt;li&gt;任务与 alloc 的总称&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;事物（thing）
&lt;ul&gt;
&lt;li&gt;指代集合或实例&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;作业的优先级&lt;/h3&gt;
&lt;p&gt;优先级的概念被广泛应用在基于优先级的抢占式调度当中，其表达的是任务之间的相对重要性，在资源不足的情况之下高优先级的任务会对低优先级的作业进行抢占，低优先级的作业便会遭到驱逐并释放它的资源。一般而言，作业会被分成两个种类：批处理作业与服务型作业，后者的优先级会比前者要高，而负责保证基础设施可用性的监控应用通常会具有最高的优先级。下表为 Borg 基于不同阶层（tier）定义的优先级：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Priority (2011)&lt;/th&gt;
&lt;th&gt;Priority (2019)&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free tier&lt;/td&gt;
&lt;td&gt;0 与 1&lt;/td&gt;
&lt;td&gt;&amp;lt;= 99&lt;/td&gt;
&lt;td&gt;优先级最低且无内部收费的作业，无 SLO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best-effort Batch (beb) tier&lt;/td&gt;
&lt;td&gt;2-8&lt;/td&gt;
&lt;td&gt;100-115[^5]&lt;/td&gt;
&lt;td&gt;批调度器管理且低内部收费的作业，无 SLO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mid-tier&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;116-119&lt;/td&gt;
&lt;td&gt;提供比生产阶层更低的 SLO 且低内部收费的作业&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production tier&lt;/td&gt;
&lt;td&gt;9-10&lt;/td&gt;
&lt;td&gt;120-359&lt;/td&gt;
&lt;td&gt;需要高可用且内部全额收费的作业，Borg 会为了保证生产阶层的作业的服务质量驱逐低阶层的作业&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitoring tier&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;&amp;gt;= 360&lt;/td&gt;
&lt;td&gt;对于基础设施而言重要的作业，包括监控&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;[^5]: John Wilkes. Google cluster-usage traces v3. https://drive.google.com/file/d/10r6cnJ5cJ89fPWCgj7j4LtLBqYN9RiI9/view. 论文中为 110-115，实际为笔误。&lt;/p&gt;
&lt;h2&gt;跟踪数据的对比&lt;/h2&gt;
&lt;p&gt;2011 年的跟踪数据包含 40 GiB 的压缩数据，即 2.8 TiB 的完整数据；而2019 年的跟踪数据包含平均每个集群 350 GiB 的压缩数据。下表概括了 2011 年与 2019 年跟踪数据的变化：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;日期&lt;/th&gt;
&lt;th&gt;&lt;em&gt;2011-05&lt;/em&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;2019-05&lt;/em&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;持续时间&lt;/td&gt;
&lt;td&gt;30 天&lt;/td&gt;
&lt;td&gt;31 天&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;集群&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;机器&lt;/td&gt;
&lt;td&gt;12.6k&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;96.4k&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;每个集群的机器&lt;/td&gt;
&lt;td&gt;12.6k&lt;/td&gt;
&lt;td&gt;12.0k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;硬件平台&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;作业优先级的值&lt;/td&gt;
&lt;td&gt;将唯一值映射到 0-11 的区间&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;0-450&lt;/strong&gt;，详情见上节&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU 利用率直方图&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;21&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;归一化的 CPU 单位&lt;/td&gt;
&lt;td&gt;物理 CPU 核数&lt;/td&gt;
&lt;td&gt;使用抽象的 Google Compute Unit（GCU），1 GCU 在任意机器上都提供相同的算力。该数据根据跟踪数据中的最大机器大小重新调整规模进行归一化，从而得到 Normalized Compute Unit (NCU)，其值域为 0 到 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alloc sets&lt;/td&gt;
&lt;td&gt;将 alloc set 与其中的作业作为普通作业对待&lt;/td&gt;
&lt;td&gt;提供作业与 alloc set 的信息，以及任务是如何被映射到 alloc instance 的&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;作业依赖&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;提供作业依赖数据，能够进行更加精确的错误分析&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;批队列&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Borg 现在支持多调度器，包括聚合批作业工作负载的批调度器&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;垂直扩缩&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Autopilot[^6]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;格式&lt;/td&gt;
&lt;td&gt;csv 文件&lt;/td&gt;
&lt;td&gt;BigQuery 表格&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;[^6]: Krzysztof Rzadca, Pawel Findeisen, Jacek Swiderski, Przemyslaw Zych, Przemyslaw Broniek, Jarek Kusmierek, Pawel Nowak, Beata Strack, Piotr Witusowski, Steven Hand, and John Wilkes. 2020. Autopilot: workload autoscaling at Google. In &amp;lt;i&amp;gt;Proceedings of the Fifteenth European Conference on Computer Systems&amp;lt;/i&amp;gt; (&amp;lt;i&amp;gt;EuroSys &apos;20&amp;lt;/i&amp;gt;). Association for Computing Machinery, New York, NY, USA, Article 16, 1–16. DOI:https://doi.org/10.1145/3342195.3387524&lt;/p&gt;
&lt;p&gt;跟踪数据的结果以时间序列的集合与互补累计分布函数（Complementary Cumulative Distribution Functions，CDF）的形式呈现，以直观地总结数据的分布情况。下文将会从不同的维度针对 2011 年与 2019 年获得的跟踪数据进行比较，包括集群资源利用率与资源消耗。&lt;/p&gt;
&lt;h2&gt;资源利用率&lt;/h2&gt;
&lt;h3&gt;平均资源利用率（utilization）&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.rudeigerc.dev/cdn-cgi/imagedelivery/zHp1Y4Kl9MXfXkk0kqREVw/27b0b120-e118-46d3-e2fc-682509471600/public&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;上图是在 2011 年与 2019 年的的跟踪数据的每小时的平均计算与内存资源使用率，这八年间，集群的平均资源利用率有了显著增长，这主要来源于 best-effort batch 阶层，即由批调度器管理的作业，这些作业占据了集群将近 20% 的容量，包括 CPU 和内存。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.rudeigerc.dev/cdn-cgi/imagedelivery/zHp1Y4Kl9MXfXkk0kqREVw/6637fc75-ec75-445b-6384-8387b4083900/public&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;上图展示了不同节点在不同阶层的 CPU 与内存的使用情况，集群之间的工作负载有着相当大的差异，同时在一个集群内部不同的资源维度也存在着差异。&lt;/p&gt;
&lt;h3&gt;平均资源分配率（allocation）&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.rudeigerc.dev/cdn-cgi/imagedelivery/zHp1Y4Kl9MXfXkk0kqREVw/49429512-3ade-4e96-581d-64c3af2a9e00/public&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;作业通常会指定上限作为其所需的资源上限，从 2011 年至 2019 年，上限的总和显著地增加，CPU 和内存都被分配到远高于 100% 的容量，这表明 Borg 在使用统计多路复用对资源进行超售，即打赌作业会使用比其请求要少的资源。在 2011 年，CPU 比内存更激进地被超售，因为短时间超售 CPU 只会导致 CPU 节流，然后内存不足的话会导致 OOM；而在 2019 年，内存超售的程度已经和 CPU 相当了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.rudeigerc.dev/cdn-cgi/imagedelivery/zHp1Y4Kl9MXfXkk0kqREVw/f34abf2a-ff48-4945-218d-aa653b24c400/public&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;上图展示了不同节点在不同阶层的 CPU 与内存的分配情况，可以看到集群间存在相当大的差异，以及一些高度超售的集群。&lt;/p&gt;
&lt;h3&gt;机器资源利用率&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.rudeigerc.dev/cdn-cgi/imagedelivery/zHp1Y4Kl9MXfXkk0kqREVw/a86945dc-5242-45b6-5118-cca421e1c800/public&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;与 2011 年的数据相比，2019 年的机器资源利用率更高，最高的百分比除外，对于整体利用率的中位数，CPU 增加了 20-40%，而内存增加了 3-30%；&lt;/li&gt;
&lt;li&gt;与 2011 年的数据相比，2019 年的机器资源利用率变异性较小；&lt;/li&gt;
&lt;li&gt;与 2011 年的数据相比，2019 年 CPU 利用率大于 80% 的机器较少。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;综合而言，这意味着 Borg 调度器在 2019 年比 2011 年做得更好，其在整个集群中分配工作负载，避免了机器利用率过低和过高。此外，各个集群 CPU 与内存的利用率都存在较大差异，具有异构性；同时 Borg 的工作负载在昼夜周期也存在差异。&lt;/p&gt;
&lt;h2&gt;调度负载的演变&lt;/h2&gt;
&lt;h3&gt;作业提交速率&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.rudeigerc.dev/cdn-cgi/imagedelivery/zHp1Y4Kl9MXfXkk0kqREVw/6b935bb7-b239-4ef5-bd0b-93ba3a834900/public&quot; alt=&quot;每个集群每个小时给 Borg 调度器提交作业的平均速率的 CCDF&quot; /&gt;&lt;/p&gt;
&lt;p&gt;上图为每个集群每个小时给 Borg 调度器提交**作业（Job）**的平均速率的 CCDF。虽然 2011 年与 2019 年的集群规模相当，但是平均作业到达速率从 2011 年的 3360 个作业每小时增长到 2019 年的 3360 个作业每小时（3.5 倍）；而到达速率中位数由 885 增长到 3309 个作业每小时（3.7 倍）；而 90% 分位点增长了约三倍。&lt;/p&gt;
&lt;h3&gt;任务提交速率&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.rudeigerc.dev/cdn-cgi/imagedelivery/zHp1Y4Kl9MXfXkk0kqREVw/0bf6e99d-5688-4b06-d03d-9e30dd03d500/public&quot; alt=&quot;每个集群每个小时给 Borg 调度器提交任务的平均速率的 CCDF&quot; /&gt;&lt;/p&gt;
&lt;p&gt;上图为每个集群每个小时给 Borg 调度器提交**任务（Task）**的平均速率的 CCDF，包括&lt;em&gt;新的任务&lt;/em&gt;与&lt;em&gt;所有任务&lt;/em&gt;，后者包括重调度后的任务。任务调度速率中位数相比 2011 年增长了 3.6 倍。另一方面，很多调度事件是为了重调度，重提交任务率中位数与新任务率中位数之比由 0.66:1 增加到 2.26:1。&lt;/p&gt;
&lt;h3&gt;调度延迟&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.rudeigerc.dev/cdn-cgi/imagedelivery/zHp1Y4Kl9MXfXkk0kqREVw/93d87d19-47ac-4fe7-86f2-18c0e03d3200/public&quot; alt=&quot;作业调度延迟的 CCDF&quot; /&gt;&lt;/p&gt;
&lt;p&gt;考虑到作业和任务提交率的增加，Borg 调度器可能需要更长的时间来做出调度决定，和/或任务或作业可能保持停滞以等待调度决定，因此作者测量了 Borg 将 &lt;code&gt;Ready&lt;/code&gt; 的作业的第一个任务调度到机器上的时间（&lt;code&gt;Running&lt;/code&gt;），由此来排除批处理调度器造成的排队延迟。由上图可以发现调度延迟的中位数实际上已经下降，虽然最后 28% 的作业的尾部更长。大部分的长延迟都与 best-effort batch 和 mid 阶层的作业有关，而生产作业的调度明显比 2011 年快。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.rudeigerc.dev/cdn-cgi/imagedelivery/zHp1Y4Kl9MXfXkk0kqREVw/a9a2e4b6-09f3-41e6-0cdb-2298ee784100/public&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;为了理解上述现象的原因，作者根据阶层查看了每个作业的任务数量，从而得知 best-effort batch 和 mid 的任务数比其它层级的要多，因此两者的作业需要更多时间进行调度。&lt;/p&gt;
&lt;h2&gt;资源消耗&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;em&gt;Measure&lt;/em&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;2011 (NCU-hours)&lt;/em&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;2019 (NCU-hours)&lt;/em&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;2011 (NMU-hours)&lt;/em&gt;&lt;/th&gt;
&lt;th&gt;&lt;em&gt;2019 (NMU-hours)&lt;/em&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;median&lt;/td&gt;
&lt;td&gt;0.15e-3&lt;/td&gt;
&lt;td&gt;0.05e-3&lt;/td&gt;
&lt;td&gt;0.07e-3&lt;/td&gt;
&lt;td&gt;0.03e-3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;mean&lt;/td&gt;
&lt;td&gt;3.00&lt;/td&gt;
&lt;td&gt;1.19&lt;/td&gt;
&lt;td&gt;3.00&lt;/td&gt;
&lt;td&gt;0.67&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;variance&lt;/td&gt;
&lt;td&gt;75.2k&lt;/td&gt;
&lt;td&gt;33.3k&lt;/td&gt;
&lt;td&gt;99.0k&lt;/td&gt;
&lt;td&gt;19.8k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;90%ile&lt;/td&gt;
&lt;td&gt;0.03&lt;/td&gt;
&lt;td&gt;0.005&lt;/td&gt;
&lt;td&gt;0.01&lt;/td&gt;
&lt;td&gt;0.004&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99%ile&lt;/td&gt;
&lt;td&gt;10.5&lt;/td&gt;
&lt;td&gt;1.33&lt;/td&gt;
&lt;td&gt;5.2&lt;/td&gt;
&lt;td&gt;0.65&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;99.9%ile&lt;/td&gt;
&lt;td&gt;248&lt;/td&gt;
&lt;td&gt;69.67&lt;/td&gt;
&lt;td&gt;196&lt;/td&gt;
&lt;td&gt;36.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;maximum&lt;/td&gt;
&lt;td&gt;138k&lt;/td&gt;
&lt;td&gt;370k&lt;/td&gt;
&lt;td&gt;151k&lt;/td&gt;
&lt;td&gt;299k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;top 1% jobs load&lt;/td&gt;
&lt;td&gt;97.3%&lt;/td&gt;
&lt;td&gt;99.2%&lt;/td&gt;
&lt;td&gt;98.6%&lt;/td&gt;
&lt;td&gt;99.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;top 0.1% jobs load&lt;/td&gt;
&lt;td&gt;83.0%&lt;/td&gt;
&lt;td&gt;93.1%&lt;/td&gt;
&lt;td&gt;89.3%&lt;/td&gt;
&lt;td&gt;92.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$C^2$&lt;/td&gt;
&lt;td&gt;8375&lt;/td&gt;
&lt;td&gt;23312&lt;/td&gt;
&lt;td&gt;11001&lt;/td&gt;
&lt;td&gt;43476&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pareto($\alpha$)&lt;/td&gt;
&lt;td&gt;0.77&lt;/td&gt;
&lt;td&gt;0.69&lt;/td&gt;
&lt;td&gt;0.72&lt;/td&gt;
&lt;td&gt;0.72&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$R^2$&lt;/td&gt;
&lt;td&gt;99.8%&lt;/td&gt;
&lt;td&gt;99.9%&lt;/td&gt;
&lt;td&gt;99.8%&lt;/td&gt;
&lt;td&gt;99.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;平方变异系数&lt;/h3&gt;
&lt;p&gt;作者在这边使用平方&lt;a href=&quot;https://zh.wikipedia.org/zh-hans/%E5%8F%98%E5%BC%82%E7%B3%BB%E6%95%B0&quot;&gt;变异系数&lt;/a&gt;来衡量获得数据的离散程度，由于该值不会因为归一化而受到影响，因此作为衡量的标准相当合适，其定义如下:&lt;/p&gt;
&lt;p&gt;$$
C^2 = \text{variance}/\text{mean}^2
$$&lt;/p&gt;
&lt;p&gt;对于指数分布，$C^2=1$。一份 2017 年的针对缓存在 Akamai 的十亿个对象的大小的研究表明，针对香港的跟踪数据有 $C^2=143$，针对美国的跟踪数据有$C^2=760$，具有“极高的变异性”。而 2019 年的工作负载甚至比前者还要再多一到两个数量级，在计算消耗方面，平均值仅为 1.2 NCU 小时，反差约为 33.3k，因此 $C^2=23k$；在内存方面，数据更加极端，平均值为 0.67 NMU 小时，反差约为 19.8k，因此 $C^2=43k$，两者的平方变异系数都很高。&lt;/p&gt;
&lt;h3&gt;帕累托分布&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.rudeigerc.dev/cdn-cgi/imagedelivery/zHp1Y4Kl9MXfXkk0kqREVw/69cb4538-1666-4594-eb03-bea87855b400/public&quot; alt=&quot;作业的资源使用小时 CCDF（log-log）&quot; /&gt;&lt;/p&gt;
&lt;p&gt;另一方面，上图为 2011 年与 2019年的跟踪数据中的作业的资源使用小时的 log-log 维度的 CCDF，由图像可知两者满足幂定律，具体而言，两者服从&lt;a href=&quot;https://zh.wikipedia.org/zh-hans/%E5%B8%95%E7%B4%AF%E6%89%98%E5%88%86%E5%B8%83&quot;&gt;帕累托分布&lt;/a&gt;：&lt;/p&gt;
&lt;p&gt;$$
\Pr{\text{job uses} &amp;gt; x \ \text{NCU-hours}} = 1/x^\alpha
$$&lt;/p&gt;
&lt;p&gt;其中 $\alpha$ 为负斜率（NMU-hours 同）。可以将 2019 年的数据以 $\alpha = 0.69$（CPU）与 $\alpha = 0.72$（内存）拟合为帕累托分布，并且决定系数 $R^2$ 皆大于 99%。&lt;/p&gt;
&lt;p&gt;帕累托分布是以经济学家维尔弗雷多·帕累托命名的，是从大量真实世界的现象中发现的幂分布定律。对于帕累托分布，尤其是 $\alpha &amp;lt; 1$ 的情况下，代表该分布具有&lt;a href=&quot;https://zh.wikipedia.org/zh-hans/%E9%87%8D%E5%B0%BE%E5%88%86%E5%B8%83&quot;&gt;重尾&lt;/a&gt;的特性，即少数的最大的作业占据了大部分负载，这比通常所说的“二八”定律（即前两成大的作业占据了八成的工作负载）还要极端。在先前的研究中作者们观察到了重尾属性，即前 1% 的最大作业占了 50% 的负载，而在 2019 年的追踪数据中分布更加极端：&lt;strong&gt;前 1% 的最大作业占了 99.2% 的 CPU 负载，99.1% 的内存负载；前 0.1% 的最大作业占了 93.1% 的 CPU 负载，92.6% 的内存负载&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;与 2011 年的数据的对比&lt;/h3&gt;
&lt;p&gt;在 2011 年的数据中也有类似的情况，尽管没有那么极端，虽然就规模而言 2011 年的数据只有 2019 年数据的八分之一，且原始机器的规模也不同，但是可以基于平方变异系数与整体分布参数进行比较，两者在归一化后都是不变的。虽然 2011 年的数据差异程度没有那么大（是 2019 年的四分之一），其依然服从帕累托分布，且重尾现象相对不严重，但和其它研究相比依旧很高。&lt;/p&gt;
&lt;h3&gt;计算与内存资源消耗的关系&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.rudeigerc.dev/cdn-cgi/imagedelivery/zHp1Y4Kl9MXfXkk0kqREVw/fcace455-d3fa-4f70-d766-93396006a700/public&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;从上一节当中的图像可以发现，计算和内存的消耗几乎服从于相同的分布，因此考虑两者的相关性是十分合理的。上图的横坐标是 NCU 小时，纵坐标为相对应的 NMU 小时的中位数，可以发现拟合出来的几乎是一条直线（Pearson 相关系数为 0.97），实际上考虑到作业运行的时长是两者的共同因素，这个结果并不是那么令人惊讶。&lt;/p&gt;
&lt;h3&gt;计算和内存消耗对排队延迟和调度的影响&lt;/h3&gt;
&lt;p&gt;基于排队论，根据 Pollaczek-Khinchin 公式，对于 M/G/1 队列有：&lt;/p&gt;
&lt;p&gt;$$
\mathbb{E}[\text{queueing delay}] = \frac{\rho}{1-\rho} \cdot \frac{C^2+1}{2}
$$&lt;/p&gt;
&lt;p&gt;其中 $\rho$ 是负载，$C^2$ 是作业大小的平方变异系数。&lt;/p&gt;
&lt;p&gt;我们可以发现即使在负载较低的时候，如果 $C^2$ 较大就意味着排队延迟的期望依旧会很高，同时作业大小的分布会很广，可以想见一定有小型的作业被堵塞在大型的作业之后，而大规模数据中心的调度肯定会比 M/G/1 队列要复杂得多。因此在这样的情况之下，我们必须找到一种方法来进行调度，使得这些小型任务优先于大型任务，或者以某种其它方式与它们隔离。&lt;/p&gt;
&lt;p&gt;如果调度器能确保仅仅 1% 的（大型）作业不妨碍其它 99% 的（小型）作业，那么后者就可以在几乎不排队的情况下运行，同时会运行得更加快速。在上一节中也提到了计算和资源具有较强的相关性，因此这边的解决方案更倾向于排除大型任务本身，而不是分别基于 CPU 或内存进行资源隔离。&lt;/p&gt;
&lt;h2&gt;垂直扩缩&lt;/h2&gt;
&lt;p&gt;详情请见 Autopilot: workload autoscaling at Google[^4]。&lt;/p&gt;
&lt;h2&gt;讨论&lt;/h2&gt;
&lt;p&gt;作者在文末进行了在生成跟踪数据时的经验总结与针对未来可能的研究方向的展望。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;可解释的调度&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;调度器是高度复杂的实体，必须考虑到集群状态和工作负载的快速变化。如果能够为调度器做出的决定提供解释就更好了——无论是帮助系统帮助人员理解正在发生，或是即将发生的事情，还是为终端用户提供指导，告诉他们如何更好地使用集群。&lt;/li&gt;
&lt;li&gt;Google 在 2015 年发表的有关 Borg 的论文中提到，Borg 的调度流程主要分为两部分：可行性检查（feasibility checking）与打分（scoring），这与目前 Kubernetes 中的设计是一致的，包括抢占式调度的部分，因此我认为在这部分可解释性还是相当充分的。然而在更为复杂与重量级的调度场景之下，现有的调度策略可能无法满足延迟或者吞吐量的需求，尤其是对外提供服务的系统一般会定义相应的 SLO，因此会根据需要引入更加复杂的调度策略，包括分时调度或动态调度等，甚至在大多数情况下是各种不同的调度策略是在同时作用的，这对调度的可解释性而言也是个很大的挑战。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;超售可以被推进到多远？&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;统计多路复用对于实现高资源利用率很重要，但是存在一定代价。&lt;/li&gt;
&lt;li&gt;在 EuroSys 2021 中，Google 发表了一篇有关数据中心超售的优化的文章[^7]，提供了一种与 Autopilot[^4] 不同维度的优化集群资源利用率的解决方案，该文以机器为单位对未来资源的使用情况进行预测，并提出有效的超售策略，从而提高其资源利用率。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;批调度&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;在 Borg 中，只要作业中的任何一个任务开始运行，该作业就会启动，虽然用户可以要求等待更多的任务运行，但是很少有人这么做，而它的算法通常是相对简单的贪婪启发式算法。&lt;/li&gt;
&lt;li&gt;实际上社区中已经有不少项目提出了 &lt;code&gt;PodGroup&lt;/code&gt; 的概念，包括 kubernetes-sigs/scheduler-plugins 中的 coscheduling plugin&lt;a href=&quot;https://github.com/kubernetes-sigs/scheduler-plugins/blob/release-1.19/pkg/apis/scheduling/v1alpha1/types.go#L116&quot;&gt;^8&lt;/a&gt; 与 Volcano&lt;a href=&quot;https://github.com/volcano-sh/apis/blob/release-1.19/pkg/apis/scheduling/v1beta1/types.go#L144&quot;&gt;^9&lt;/a&gt;，两者都是源自 kubernetes-sigs/kube-batch。本质上我认为是给有依赖关系的批处理任务（如 Flink 的 JobManger 与 TaskManager，TensorFlow 的 Parameter Server 与 Worker 等），能够进行批量调度的抽象，以保证 all-or-nothing，从而使有限资源不会被占用而未使用，同时也能够基于作业的特性对其分布进行约束减小网络的开销。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;为什么平均资源利用率相对较低？&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;尽管做了大量的努力，计算和内存资源的平均利用率仍然较低。一种假设是，这与故障恢复协议有关，其中三分之一的外部负载（在 2+1 冗余系统中）几乎可以瞬间切换到一个目标单元；另一种假设是存在时间和/或每台机器的变化，其阻止了更紧密的装箱。&lt;/li&gt;
&lt;li&gt;在提高集群资源利用率的同时保证应用的稳定性是相当具有挑战性的，因此存在一定的冗余是必要的，当然这与数据中心的容灾设计有关。另一方面，针对实时性的调度，我认为关键在于其获取的集群状态的实时性，基于有效的数据调度器才能做出符合当前集群状态的判断，这可能和基础设施本身的成熟度有关。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;针对重尾的调度&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;前 1% 计算密集型的作业占据了 99% 的 CPU 使用，如何进行调度使剩下 99% 的作业与这些 1% 的作业部分或完全隔离，从而使他们在一个轻量的负载环境中运行会是个有趣的研究。&lt;/li&gt;
&lt;li&gt;为了有效地提高集群资源的利用率，混合部署是当前的大势所趋，但是在混合部署的情况下资源的隔离是相当有必要的，在此基础上也可以进行更加细粒度的分析，将不同类型的工作负载进行隔离，形成工作负载感知的调度，也可以引入安全容器来提高其性能隔离，如 gVisor 与 Kata Container 等，当然这会引入一定的开销，因此如何进行 trade-off 也是个有意思的部分。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跨节点的差异&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;该分析仅仅触及了新的跟踪数据的八个集群的差异的表面。&lt;/li&gt;
&lt;li&gt;实际上不同集群的工作负载应该会受到不同维度因素的影响，可能包括部署的区域、工作负载的差异、节点之间的网络传输与作业的依赖等。我认为本文主要还是从比较宏观的层面对数据进行了分析，而缺乏细粒度的横向比较，当然这也仅仅是侧重点的不同，宏观层面的比较也是相当有价值的。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;[^7]: Noman Bashir, Nan Deng, Krzysztof Rzadca, David Irwin, Sree Kodak, and Rohit Jnagal. 2021. Take it to the limit: peak prediction-driven resource overcommitment in datacenters. &amp;lt;i&amp;gt;Proceedings of the Sixteenth European Conference on Computer Systems&amp;lt;/i&amp;gt;. Association for Computing Machinery, New York, NY, USA, 556–573. DOI:https://doi.org/10.1145/3447786.3456259&lt;/p&gt;
&lt;h2&gt;Google 集群使用跟踪数据 v3&lt;/h2&gt;
&lt;p&gt;本文最后来简单地对本次 Google 所发布的跟踪数据进行概览。&lt;/p&gt;
&lt;h3&gt;Schema&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Machines
&lt;ul&gt;
&lt;li&gt;Machine events&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Collections and instances
&lt;ul&gt;
&lt;li&gt;Collection events&lt;/li&gt;
&lt;li&gt;Instance events&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Resource usage
&lt;ul&gt;
&lt;li&gt;Instance usage&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;访问跟踪数据&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ gsutil cp gs://clusterdata_2019_a/instance_usage-*.json.gz ​&amp;lt;destination dir&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ gunzip instance_usage-000000000000.json.gz
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ head -1 instance_usage-000000000000.json
{&quot;start_time&quot;:&quot;1838400000000&quot;,&quot;end_time&quot;:&quot;1838700000000&quot;,&quot;collection_id&quot;:&quot;330587238433&quot;,&quot;instance_index&quot;:&quot;111&quot;,&quot;machine_id&quot;:&quot;23624491139&quot;,&quot;alloc_collection_id&quot;:&quot;330587160469&quot;,&quot;alloc_instance_index&quot;:&quot;111&quot;,&quot;collection_type&quot;:&quot;0&quot;,&quot;average_usage&quot;:{&quot;cpus&quot;:0.008392333984375,&quot;memory&quot;:0.0094757080078125},&quot;maximum_usage&quot;:{&quot;cpus&quot;:0.04791259765625,&quot;memory&quot;:0.019378662109375},&quot;random_sample_usage&quot;:{&quot;cpus&quot;:0.016632080078125},&quot;assigned_memory&quot;:0,&quot;page_cache_memory&quot;:0.0032196044921875,&quot;cycles_per_instruction&quot;:0.89924430847167969,&quot;memory_accesses_per_instruction&quot;:0.00151212012860924,&quot;sample_rate&quot;:0.996666669845581,&quot;cpu_usage_distribution&quot;:[0.0004711151123046875,0.0006809234619140625,0.00075817108154296875,0.00083160400390625,0.00096225738525390625,0.0084381103515625,0.008880615234375,0.0133056640625,0.016448974609375,0.021942138671875,0.04779052734375],&quot;tail_cpu_usage_distribution&quot;:[0.022308349609375,0.023681640625,0.024200439453125,0.025360107421875,0.02606201171875,0.02685546875,0.028411865234375,0.030853271484375,0.0369873046875]}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;这是 Google 时隔五年之后又发表了 Borg 相关的文章，当初看见 conference program 的时候原本以为是偏向系统架构层面的介绍，没想到后面 proceeding 出来了之后才发现是介绍 tracing 的文章。如同上文所讲述的，于 EuroSys 2020 发表的论文主要是介绍本次发布的 2019 年的跟踪数据与 2011 年的数据的对比，实际上涉及到 Borg 本身的架构较少，多数与 2011 年的时间点相比新增的的部分在 EuroSys 2015[^2] 与 Autopilot[^4] 中都进行了详细的介绍。&lt;/p&gt;
&lt;p&gt;Google 作为全球拥有数一数二规模的集群的公司能够将其跟踪数据发表出来我认为是相当有价值的，不仅为数据量不充足的研究者提供了数据基础，同时也为希望在集群资源管理，包括多维度调度与容量管理进行进一步深入研究的人，通过本文的分析提供了可能的方向。&lt;/p&gt;
</content:encoded><category>Paper Reading</category></item><item><title>翻译 | 为 Kubernetes 编写良好的 e2e 测试</title><link>https://rudeigerc.dev/posts/writing-good-e2e-tests-for-kubernetes-zh-hans/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/writing-good-e2e-tests-for-kubernetes-zh-hans/</guid><description>本文为 Writing good e2e tests for Kubernetes 的译文，其主要目的在于介绍如何为 Kubernetes 编写良好的 e2e 测试。</description><pubDate>Sat, 10 Apr 2021 14:28:26 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;本文为 &lt;a href=&quot;https://github.com/kubernetes/community/blob/978aa3fb5b/contributors/devel/sig-testing/writing-good-e2e-tests.md&quot;&gt;Writing good e2e tests for Kubernetes (978aa3fb5b)&lt;/a&gt; 的译文。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;模式与反模式&lt;/h2&gt;
&lt;h3&gt;e2e 测试的目标&lt;/h3&gt;
&lt;p&gt;除了提供端到端系统的测试覆盖这个明显的目标之外，在设计、编写与调试端到端测试时，还有一些不是很明显的目标需要记住。
尤其是“不稳定的（flaky）”测试，即大部分时间通过，但是由于难以定位的原因而导致间歇性失败的的测试，这些测试在模糊我们的回归信号与减慢我们的自动合并速度方面具有高昂的代价。
花费前期的时间和努力将你的测试设计成可靠的是非常有价值的事情。
请谨记，我们有数以百计的测试，每个测试都在数十个不同环境当中运行，如果任何一个测试在任何一个测试环境中失败了，我们必须假设我们有某种潜在的退化。
所以如果大量的测试哪怕只有 1% 的时间失败了，基本的统计学决定了我们几乎永远不会有“绿色”的回归指标。
换句话说，在持续集成环境的残酷现实当中，编写一个只有 99% 可靠度的测试几乎是毫无用处的。
事实上这比无用还要糟糕，因为其不仅不能提供可靠的回归指标，而且还会消耗大量后续的调试时间，以及延迟的合并。&lt;/p&gt;
&lt;h4&gt;可调试性&lt;/h4&gt;
&lt;p&gt;如果你的测试失败了，它应该在其输出当中尽可能详细地提供其失败的原因。
“Timeout”并不是一个有用的错误信息。
“Timed out after 60 seconds waiting for pod xxx to enter running state, still in pending state”对于试图弄清楚为什么你的测试失败以及应该如何处理的人来说更为有用。
具体而言，如下所示的&lt;a href=&quot;https://onsi.github.io/gomega/#making-assertions&quot;&gt;断言&lt;/a&gt;代码会产生相当无用的错误：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Expect(err).NotTo(HaveOccurred())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;更应该用这样的方式&lt;a href=&quot;https://onsi.github.io/gomega/#annotating-assertions&quot;&gt;注解&lt;/a&gt;你的断言：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Expect(err).NotTo(HaveOccurred(), &quot;Failed to create %d foobars, only created %d&quot;, foobarsReqd, foobarsCreated)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另一方面，过于冗长的日志，尤其是针对非错误条件的，会使弄清楚测试是否失败与失败的原因产生不必要的困难，所以也不要在日志中记录太多不相关的东西。&lt;/p&gt;
&lt;h4&gt;在非专用的测试集群中运行的能力&lt;/h4&gt;
&lt;p&gt;在运行 e2e 测试的时候，为了减小端到端的言辞以及提高资源利用率，我们尽可能地尝试在相同的测试集群之中并行运行大量的测试，这意味着：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;你应该避免做出任何你的测试是唯一在集群之上运行的东西的假设（无论是隐式的还是显式的）。
举例来说，做出你的测试可以在集群当中的每一个节点上运行一个 pod 的假设并不是安全的，因为一些和你的测试同时运行的其它的测试可能已经使得集群中的一个或者多个节点饱和。
例如，在系统 namespace 当中运行一个 pod，并且假设其会使得系统 namespace 当中的 pod 的数量会增加一个也是不安全的，因为一些其它的测试可能会与你的测试同时在系统 namespace 中创建或删除 pod。如果你确实合理地需要像那样编写一个测试，确保给它打上了“&lt;a href=&quot;https://github.com/kubernetes/community/blob/978aa3fb5b5593aed4b11ee0cc45ab9dcd16e5e8/contributors/devel/sig-testing/e2e-tests.md#kinds-of-tests&quot;&gt;[Serial]”&lt;/a&gt;的标签，这样便于识别，并且不会和任何其它的测试并行运行。&lt;/li&gt;
&lt;li&gt;你应该避免在同一时间对集群做一些让其它测试难以可靠地做它们要做的事情。
例如，重启节点、断开网络接口或者升级集群软件作为测试的一部分，这些很可能会违反其它测试对合理稳定的集群环境可能做出的假设。
如果你需要编写这样的测试，请为其打上&lt;a href=&quot;https://github.com/kubernetes/community/blob/978aa3fb5b5593aed4b11ee0cc45ab9dcd16e5e8/contributors/devel/sig-testing/e2e-tests.md#kinds-of-tests&quot;&gt;“[Disruptive]”&lt;/a&gt;的标签，这样便于识别，并且不会和任何其它的测试并行运行。&lt;/li&gt;
&lt;li&gt;你应该避免对 Kubernetes API 做出不属于 API 规范的假设，因为一旦这些假设失效，你的测试就会崩溃。
例如，依赖特定的 Event、Event reason 或者 Event 消息会让你的测试变得非常脆弱。&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;执行速度&lt;/h4&gt;
&lt;p&gt;我们有数以百计的 e2e 测试，其中一些在某些情况下我们串行运行，一个接一个。
如果每个测试只需要几分钟的时间运行，这会很快累加成很多很多小时的总执行时间。
我们尽量把这样的总执行时间控制在最多几十分钟之内。
因此，尽量（非常努力地）将你的单个测试的执行时间控制在两分钟一下，最好比这个时间更短。
具体而言，在测试中加入不适当的长时间的‘sleep’语句或者其它无理由的等待是一个杀手。
如果在正常情况下你的 pod 在十秒钟内进入 running 的状态，然后 99.9% 的时间在三十秒内进入该状态，那么为此等待五分钟是没有必要的。
更应该直接在三十秒后失败，并且附上明确的错误信息，说明其失败的原因（例如，“Pod x failed to become ready after 30 seconds, it usually takes 10 seconds”）。
如果你确实有合理的理由需要等待更长的时间，或者编写运行时间超过两分钟的测试，请在代码中非常清楚地注释为什么这是必要的，并且将其标注为&lt;a href=&quot;https://github.com/kubernetes/community/blob/978aa3fb5b5593aed4b11ee0cc45ab9dcd16e5e8/contributors/devel/sig-testing/e2e-tests.md#kinds-of-tests&quot;&gt;“[Slow]”&lt;/a&gt;，这样便于识别和避免在需要及时完成的测试中运行（例如那些在被允许合并之前针对每一个代码提交运行的测试）。
需要注意的是，只有在测试通过时是在两分钟内完成一般是不够好的。
你的测试应该在合理的时间内失败。
例如，我们曾经看到一些测试，为了让每个 pod 变成 ready 的状态等待了长达十分钟的时间。
在良好的条件下这些测试可能会在几秒钟内通过，但是如果这些 pod 从未变成 ready 的状态（例如由于系统退化），这些测试会花费非常长的时间才能失败，这通常会导致整个测试运行超时，因此不会产生任何结果。
再次，这比起在系统无法正常工作时，在一两分钟内就能可靠地失败的测试要无用的多。&lt;/p&gt;
&lt;h4&gt;针对相对罕见且临时的基础设施故障或延迟的恢复能力&lt;/h4&gt;
&lt;p&gt;请记住你的测试将在白天和夜晚的不同时间，可能在不同云提供商上，在不同的负载条件下，运行成千上万次。
这些系统的底层状态往往存储在最终一致的数据存储之中。
所以，例如，如果一个创建资源的请求是理论上异步的，即使你观察到它在大多数的时间实际上是同步的，编写测试时也要假设它是异步的（例如，进行“创建”调用，并轮询或观察资源直到其处于正确的状态才继续处理）。
同样，不要假设 API 端点是 100% 可用的。
它们并非如此。
在高负载的条件下，API 调用可能会暂时失败或超时。
在这样的情况之下退避或重试数次是合适的（在这种情况下使错误信息非常清楚地说明发生了什么，例如，“Retried http://... 3 times - all failed with xxx”）。
请使用下面详细介绍的库当中提供的标准重试机制。&lt;/p&gt;
&lt;h3&gt;一些具体的工具供你使用&lt;/h3&gt;
&lt;p&gt;显然上述大部分目标适用于许多测试，不仅仅是你的。
所以我们开发了一系列可重用的测试基础设施、库与最佳实践来帮助你做正确的事情，或者至少和其它测试做相同的事情，这样如果后来发现是错误的，就可以在一个地方修复使其成为正确的，而不是上百个地方。&lt;/p&gt;
&lt;p&gt;这里有几个要点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://git.k8s.io/kubernetes/test/e2e/framework/framework.go&quot;&gt;e2e 框架&lt;/a&gt;：
使你自己熟悉这个测试框架以及如何使用它。
其中，它可以自动创建唯一命名的 namespace 使你的测试能够在其中运行以避免命名冲突，并且在测试完成之后可靠地自动清理混乱（它只是删除了 namespace 中的所有东西）。
这有助于确保测试不会泄漏资源。
需要注意的是，删除一个 namespace（也意味着其中的一切）目前是一个昂贵的操作。
因此，你创建的资源越少，框架所需要做的清理工作越少，你的测试（以及与你的测试并发运行的其它测试）完成得越快。
你的测试应该始终使用这个框架。
事实证明尝试其它自创的方法来避免命名冲突和资源泄漏是一个非常糟糕的主意。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://git.k8s.io/kubernetes/test/e2e/framework/util.go&quot;&gt;e2e 工具库&lt;/a&gt;：
这个方便的库提供了大量的可重用的代码，用于许多需要的常用测试功能，包括等待资源进行指定的状态，安全且一致地重试失败的操作，有效地报告错误等。
确保你熟悉了那里可用的内容并使用它。
同样，如果你遇到了一个普遍有用的机制，而那里还未实现，你可以将其加入库中这样其他人就能从你的智慧中受益。
尤其要注意文件顶部中德各种超时和重试相关的常量。
一定要尽量重用这些常量而不是自行定义。
即使这些值可能并不是你想要使用的（超时时间、重试次数等），但是在整个测试套件中保证其一致并且中心化配置的好处通常会超过你的个人偏好设置。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;遵循稳定的且编写良好的测试用例：&lt;/strong&gt;
我们现有的一些端到端测试比其它测试写得更好更可靠。
编写良好的测试的几个例子包括：&lt;a href=&quot;https://git.k8s.io/kubernetes/test/e2e/apps/rc.go&quot;&gt;Replication Controllers&lt;/a&gt;、&lt;a href=&quot;https://git.k8s.io/kubernetes/test/e2e/network/service.go&quot;&gt;Services&lt;/a&gt; 与 &lt;a href=&quot;https://git.k8s.io/kubernetes/test/e2e/cloud/gcp/reboot.go&quot;&gt;Reboot&lt;/a&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/onsi/ginkgo&quot;&gt;Ginkgo 测试框架&lt;/a&gt;：
这是我们的 e2e 测试基于的测试库与运行器。
在你编写或重构一个测试之前，请阅读文档并确保你了解它是如何工作的。
尤其需要注意的是每个测试都是由 &lt;code&gt;Describe&lt;/code&gt; 子句和嵌套的 &lt;code&gt;It&lt;/code&gt; 子句组合而成的，这是唯一的标识和描述（例如，在测试报告中）。所以例如，&lt;code&gt;Describe(&quot;Pods&quot;,...).... It(&quot;&quot;should be scheduled with cpu and memory limits&quot;)&lt;/code&gt; 会产生一个正常的测试标识符和描述符 &lt;code&gt;Pods should be scheduled with cpu and memory limits&lt;/code&gt;，这明确了正在被测试的是什么，以及如果因此失败的话什么是不工作的。其它好的例子包括：&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;   CAdvisor should be healthy on every node
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以及&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;   Daemon set should run and stop complex daemon
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;反之（这些都是真实的例子），以下是不大好的测试描述符：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;   KubeProxy should test kube-proxy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以及&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Nodes [Disruptive] Network when a node becomes unreachable
[replication controller] recreates pods scheduled on the
unreachable node AND allows scheduling of pods on a node after
it rejoins the cluster
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一种改进可能是&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Unreachable nodes are evacuated and then repopulated upon rejoining [Disruptive]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;需要注意的是，我们欢迎为了具体且更好的工具开启 issue，更欢迎其代码实现。&lt;/p&gt;
&lt;h3&gt;资源使用&lt;/h3&gt;
&lt;p&gt;当编写测试时，测试当中使用的资源应该被具体且理智地选择。&lt;/p&gt;
&lt;p&gt;因此，使用的资源：&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;li&gt;需要在测试结束后进行清理的&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;是重要的。例如：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;只使用适合 &lt;code&gt;test/e2e/&amp;lt;AREA&amp;gt;/&amp;lt;SPECIFIC_AREA&amp;gt;&lt;/code&gt; 的资源类型&lt;/li&gt;
&lt;li&gt;资源类型 &lt;code&gt;ConfigMap&lt;/code&gt; 是低开销、通用和无状态的。
它应该被用于获取已创建的资源&lt;/li&gt;
&lt;li&gt;虽然用于测试的集群一般都很强大，但是不应该创建过多的资源，因为是不必要的&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;afterEach&lt;/code&gt;，确保你的测试销毁了测试当中遗留的任何资源&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;日志&lt;/h3&gt;
&lt;p&gt;当编写测试时，对所发生的事情进行日志的记录（至少在给定的测试的开发过程中）通常是很有用的。
要进行日志记录，需要导入 &lt;code&gt;framework&lt;/code&gt; 模块。
一旦导入，在测试中你就可以调用 &lt;code&gt;framework.Logf&lt;/code&gt;。&lt;/p&gt;
&lt;h4&gt;使用范例&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;打印包裹在一个字符串中的单个字符串&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;testvar := &quot;Hello World&quot;
framework.Logf(&quot;Logf says: &apos;%v&apos;&quot;, testvar)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;打印一个字符串和包裹在字符串中的数字。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;testvar1 := &quot;a string&quot;
testvar2 := 1
framework.Logf(&quot;testvar1: %v; testvar2: %v&quot;, testvar1, testvar2)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;要获取更多信息，请参考&lt;a href=&quot;https://godoc.org/k8s.io/kubernetes/test/e2e/framework#Logf&quot;&gt;框架的文档&lt;/a&gt;。&lt;/p&gt;
</content:encoded><category>Translation</category></item><item><title>年终总结 | 2020 年终总结：「转」</title><link>https://rudeigerc.dev/posts/year-in-review-2020/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/year-in-review-2020/</guid><description>2020 年终总结：「转」。</description><pubDate>Wed, 30 Dec 2020 17:31:52 GMT</pubDate><content:encoded>&lt;p&gt;2020 年可谓是多灾多难的一年，由难以扑灭的澳洲野火开始，人们甚至还在讨论会不会成为今年最严重的事件的时候，紧接着就是至今为止仍在影响着人们生活的新冠疫情。
就我个人而言今年可以说是转变的一年，无论是由于疫情的影响造成的生活方式的转变，还是迈入研究生的第二年之后心态的转变。
近年来汉字文化圈的国家都很流行评选当年的年度汉字，算是对当年度的一个总结，今年中国的年度国内字是「民」，而日本的年度汉字是「密」，可能也代表着两国在疫情之下的不同现状。
回到我自身，我的年度汉字应该就是「转」吧。&lt;/p&gt;
&lt;p&gt;其实我每年都想尝试写一下年度总结，不过最终一直都没有下笔，一方面我不是很想把自己的想法暴露出来，另一方面也确实没什么好写的，抑或是没什么亮点。
尤其是 2019 年对我来说真的是充斥着痛苦与挣扎的一年，实在是不堪回首，唯一值得庆幸的只有在实验室的期间认识了不少朋友，对前几年的我来说这是相当难以想象的。
从结果而言，我在 2019 年艰难地从本科毕业，并开启了我的研究生生涯，实际上我读研并没有什么特别的目的性，硬要说的话可能只是想给予本科生涯失败的自己更多的一点希望吧。
我并没有什么远大的科研抱负，也没有什么具有前瞻性的职业生涯规划，只是想要多一点时间看看能不能够做出不同的选择。&lt;/p&gt;
&lt;p&gt;今年看到了&lt;a href=&quot;https://gaocegege.com/Blog/newyear2020&quot;&gt;高策学长的年终总结&lt;/a&gt;的时候，又兴起了这个每年都会有的想法。
我认为年度总结就是重新审视当年的自己的一个过程，可能往年不是很想提笔也是因为没有勇气直面自己，今年总算是鼓起了勇气，虽然可能最后会是一篇流水账式的文章，也算是给 2020 年的自己一个交代。&lt;/p&gt;
&lt;h2&gt;冬&lt;/h2&gt;
&lt;p&gt;从今年年初开始说起的话，年初主要是在做上半学期的收尾工作，印象中只剩下 CSP 的综述，主题是并发控制。
虽然现在说起来有点丢人，老实说那时候选了这个主题的主要原因是其它的我都看得不是那么懂，并发控制的话至少基本的 2PL 还有 OCC 还是有点基础的认识的，虽然后面证明这个基础的认识用处并不是那么大。
我那时候主要负责的是 H-Store[^1]，后续又看了一些混合并发控制的文章，如 TicToc[^2] 和 Sundial[^3]，当然那时候时间上比较紧张，所以也没有留下什么具有参考价值的笔记，最后靠着队友们的帮忙形成了一份大概勉强还算过得去的综述，我的秋季学期也就这样划上了句号。&lt;/p&gt;
&lt;p&gt;春节之后没多久，我就开始就项目的重构开始与甲方从需求上开始进行重新的磋商。
虽然熟悉我的朋友肯定知道这个项目，不过我还是简单介绍一下，简要来说这个项目就是将某线下的流程转移到线上进行的一个平台。
虽然说的是重构，不过实际上和重写也没什么太大的区别了，毕竟需求发生了大幅的变化，对方也希望能够就页面进行重新的设计。&lt;/p&gt;
&lt;p&gt;一开始我主要负责的是前端的重写，由我先搭建好大概的骨架，后续和同学们一起合作开发。
实际上和甲方的磋商给我带来了不小的压力，这意味着我必须每隔一段时间就要有成果的产出，尤其是中期的时候我还面临同时期的课程的压力，实在不是一个我愿意回忆的时期。
从软件工程的角度来讲这次重构也算是失败的，当时我没办法承受同时需要担任开发和管理，并且还要和甲方沟通的压力与间接造成的时间压力，最终舍弃了代码审查的流程，也导致了代码质量的不可控以及整体项目的暴走。&lt;/p&gt;
&lt;h2&gt;春&lt;/h2&gt;
&lt;p&gt;进入四月份春季学期正式开始之后，我的重心就转移到了课业上，虽然对于研究生来说除了奖学金以外成绩并不是特别重要，不过我想应该也没人会因此放纵自己。
今年由于疫情的原因课程全部采取线上教学，原先我为了方便选课的时候把好几门课放在了同一天，结果变成线上之后真的是折磨，需要连续坐在电脑前六七个小时。&lt;/p&gt;
&lt;p&gt;下半学期最为挣扎的除了算法就是 CSDI 了，每周大概一半以上的时间都会花费在算法的作业上，而 CSDI 则是广度比较大，基本上涉及了所有存储相关的主题，这对之前在存储方面几乎是一张白纸的我着实带来了巨大的挑战，也间接影响了这门课的 project 的选题，虽然只需要提出 proposal 就行了，我还是相当挣扎。
原先受到 CSP 的影响，打算继续研究一下混合并发控制的，后面感觉这方面很大一部分都是基于 ML/DL 去进行的（可能实际并不是这么一回事，只是当初我片面地了解之后是这种感觉），对不管是存储还是机器学习都是纸上谈兵的我而言负担过大，最后就作罢了。&lt;/p&gt;
&lt;p&gt;之后的一段时间我都在转辗于各个数据库相关的实验室或者课程的主页来找寻可能的灵感，最后给予我灵感的是 Harvard 的 &lt;a href=&quot;http://daslab.seas.harvard.edu/classes/cs265&quot;&gt;CS265: Big Data Systems&lt;/a&gt;，让我决定选择 Log-Structured Merge Tree 作为主题，这门课程最后的作业就是要学生实现 LSM-tree 并且在此基础之上进行优化，而我对 LSM-tree 也有基础的了解（主要是 RocksDB）。
之后又从这篇综述[^4]开始慢慢对学术界针对 LSM-tree 的优化有了一定的了解，最终选择针对读这个场景提出可能的优化，缝合了 Rosetta[^5]、ElasticBF[^6] 和 EvenDB[^7]，形成了也还算过得去的 proposal，当然作为缝合怪 novelty 肯定是不足的，不过姑且是在最后完成了论文。&lt;/p&gt;
&lt;p&gt;虽然最后的论文本身可能并没有什么学术价值，不过也算是我第一次从零开始调研并学习一个方向，我认为对我自己还是挺有帮助的，同时也对 LSM-tree 这个领域有了一个宏观的认识。
幸运的是，这篇论文也给实验室的学弟的毕设带来了一定的启发，也算是让它有了更好的归宿。&lt;/p&gt;
&lt;h2&gt;夏&lt;/h2&gt;
&lt;p&gt;夏季学期我主要担任本科生 &lt;strong&gt;SE128: Design and Development of Internet Products&lt;/strong&gt; 课程的助教，虽然我从大四的时候开始常年担任各种课程的助教，不过暑假大作业这种类型的应该算是第一次。
先前也提到过，今年由于疫情的原因课程全部改在线上进行，夏季学期也包括在内，不过暑假大作业这种需要团队合作的课程在线上进行无论对老师还是学生来说都相当具有挑战性，有些在线下的时候很容易解决的问题到线上需要花费很多时间。&lt;/p&gt;
&lt;p&gt;今年在授课内容上也产生了较大的变化，当然这也和学院培养计划的调整有关，在此不再多加评价，但是整体而言要求有了较为显著的提高，即需要学生对项目的开发有比较整体的认识，包括过程管理以及团队合作等，我认为这对之前没有接触过软件工程的学生来说是非常具有挑战性的。&lt;/p&gt;
&lt;p&gt;实际上我原先十分担心由于远距离协作的影响以及要求的提高可能会对完成度带来不可预知的影响，但事实上最后答辩的时候大部分同学的完成度都超出我们的预期。
毫不夸张地说，我当年自己做出来的东西放到现在的话大概只能敬陪末座吧，&lt;s&gt;尤其是现在每天到洗手间的时候都能看到当年的展板，真的是对内心的拷问，希望早日能够处理掉&lt;/s&gt;。&lt;/p&gt;
&lt;h2&gt;秋&lt;/h2&gt;
&lt;p&gt;秋季学期开始之后，我也回到了学校，从这时候开始到年底我就主要开始烦恼毕业设计的选题了，可以说是，这一年当中最为挣扎的一段时间。&lt;/p&gt;
&lt;p&gt;为了督促自己，大概也带有对自己的期许，我们在学期初的时候新建了 &lt;a href=&quot;https://github.com/sjtu-sail/seminar&quot;&gt;sjtu-sail/seminar&lt;/a&gt; 并且每周召开组会进行分享，主要的主题遍及分布式系统的各个领域，包括资源管理、批/流处理、存储和 Serverless 等，大部分情况下都是我前一个星期一拍脑子决定的，虽然仔细看的话大部分情况下只有包括我在内的两个人在不停地循环。&lt;/p&gt;
&lt;p&gt;因为之前我主要接触的是批处理相关的工作，所以我想作为其延伸的流处理可能会相对比较能形成比较好的想法，当然实际上没有那么简单，将流处理作为最后的选题也是在 11 月下旬才决定的事情。
这段时间主要是针对论文的梳理，虽然一直有想产出几片文章留下一点记录，不过最后还是没有成文，只有零散的几篇简单的记录，我认为这也是我近期开题报告进展如此缓慢的主要原因之一，可能也算是自食其果吧 &lt;s&gt;（近期打算写一篇）&lt;/s&gt;。
从结果而言，这半年感觉好像没有做成什么事情，每天处于焦虑之中，时不时再处理一下项目相关的事情，今年也就这样在没有完成开题的情况之下画上了不圆满的句号。&lt;/p&gt;
&lt;h2&gt;杂&lt;/h2&gt;
&lt;p&gt;去年在《剑/盾》的驱使之下买了 Switch，事实上今年打通了的游戏也就只有《剑/盾》而已，剩下的不管是《塞尔达》、《风花雪月》、《AI：梦境档案》、《莱莎》还是《异度之刃 DE》都只有玩到一半（虽然《莱莎》大概快通关了，《异度之刃 DE》已经打了一大半了），下半年精神上实在是没有玩游戏的心情，只有时不时玩一下手游（主要是&lt;a href=&quot;https://another-eden.jp/&quot;&gt;《アナザーエデン 時空を超える猫》&lt;/a&gt;），所以大部分的游戏都还是上半年时候的进度。最近有在动的大概只有健身环和有氧拳击，周末的时候时不时还会锻炼一下，虽然本质上还是处于三天打鱼两天晒网的状态。&lt;/p&gt;
&lt;p&gt;在地铁上的时候我有时候会读小说打发时间，今年原本想要把森博嗣老师的《すべてがFになる》读完的，不过由于是日文原版的阅读速度有一定的局限性（主要是翻译版已经绝版了），最后还是没有成功在年内结束，不过应该已经到了解谜的部分了，顺利的话近两个月应该可以读完，希望今年能多读几本 S&amp;amp;M 系列的作品。&lt;/p&gt;
&lt;h2&gt;结&lt;/h2&gt;
&lt;p&gt;我在文初的时候提到了我的年度汉字是「转」，不过并没有提到是往正面还是负面「转」的——可能处在振荡之中吧，就像预测模型可能会因为数据的瞬时变化产生震荡一样，我认为我在这一年也是处于振荡之中的，可能主要是由于对未来的迷茫。&lt;/p&gt;
&lt;p&gt;我经常开玩笑地和我身边的人说，我原先是为了逃离系统才在本科的时候选择了信息系统方向，而这个选择又成为了我选择就读研究生的契机，结果最终到了研究生的时候我的研究方向是分布式系统，所以研究生阶段的我仿佛是在为本科阶段的自己还债。
可能有些人会难以理解，不过我并不以曾经有这种逃避的想法的自己为耻，这也是我所做出的决定，有了这段经历也才成就了现在的我。
我一直认为如何准确地评价自己是一件非常难的事情，还是学会和自己和解吧，虽然现阶段的我看来还是没有很好地学会。&lt;/p&gt;
&lt;p&gt;最后来进行一个简单的展望吧，大一点的话就是希望疫情尽早得到控制，小一点的话大概就是顺利找到实习并且顺利完成毕业论文的撰写，虽然现在具体的方案还八字没有一撇，「なんとかなる」。&lt;/p&gt;
&lt;p&gt;[^1]: H-Store: A High-Performance, Distributed Main Memory Transaction Processing System, VLDB ’08.&lt;/p&gt;
&lt;p&gt;[^2]: TicToc: Time Traveling Optimistic Concurrency Control, SIGMOD ’16.&lt;/p&gt;
&lt;p&gt;[^3]: Sundial: Harmonizing Concurrency Control and Caching in a Distributed OLTP Database Management System, VLDB ’18.&lt;/p&gt;
&lt;p&gt;[^4]: LSM-based storage techniques: a survey, VLDBJ, 2020.&lt;/p&gt;
&lt;p&gt;[^5]: Rosetta: A Robust Space-Time Optimized Range Filter for Key-Value Stores, SIGMOD ’20.&lt;/p&gt;
&lt;p&gt;[^6]: ElasticBF: Elastic Bloom Filter with Hotness Awareness for Boosting Read Performance in Large Key-Value Stores, USENIX ATC ’19.&lt;/p&gt;
&lt;p&gt;[^7]: EvenDB: Optimizing Key-Value Storage for Spatial Locality, EuroSys ’20.&lt;/p&gt;
</content:encoded><category>年终总结</category></item><item><title>使用 Bcache 将 HDD 与 SSD 作为缓存的混合存储</title><link>https://rudeigerc.dev/posts/bcache/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/bcache/</guid><description>本文为使用 Bcache 将 HDD 与 SSD 作为缓存的混合存储时的笔记。</description><pubDate>Fri, 09 Oct 2020 16:12:21 GMT</pubDate><content:encoded>&lt;h2&gt;引言&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/stec-inc/EnhanceIO&quot;&gt;EnhanceIO&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/facebookarchive/flashcache&quot;&gt;flashcache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.kernel.org/doc/Documentation/device-mapper/cache.txt&quot;&gt;dm-cache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.kernel.org/doc/Documentation/bcache.txt&quot;&gt;bcache&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;现在主流的解决方案应该是 dm-cache 和 bcache，前者在 3.9 的时候被 merge 进 Linux kernel tree，而后者是在 3.10 的时候，我们在这里选择使用 bcache。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ cat /etc/redhat-release
CentOS Linux release 8.2.2004 (Core)
$ uname -r
4.18.0-193.19.1.el8_2
$ lsmod | grep bcache
$ modprobe bcache
modprobe: FATAL: Module bcache not found.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 CentOS 8 中 bcache 默认不会被开启，因此我们需要自行编译内核并开启其中的 bcache 模块。&lt;/p&gt;
&lt;h2&gt;在内核中启用 bcache 模块&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ mkdir kernel &amp;amp;&amp;amp; cd kernel
$ wget http://vault.centos.org/centos/8/BaseOS/Source/SPackages/kernel-4.18.0-80.11.2.el8_0.src.rpm
$ rpm2cpio kernel-4.18.0-80.11.2.el8_0.src.rpm | cpio -div
$ tar -xvf linux-4.18.0-80.11.2.el8_0.tar.xz
$ cd linux-4.18.0-80.11.2.el8_0
$ cp /boot/config-4.18.0-193.19.1.el8_2.x86_64 .config
$ dnf install -y gcc automake autoconf libtool make ncurses-devel bison flex
$ make menuconfig
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;D&lt;/code&gt;evice Drivers =&amp;gt; M&lt;code&gt;u&lt;/code&gt;ltiple devices driver support (RAID and LVM) =&amp;gt; &amp;lt;*&amp;gt; &lt;code&gt;B&lt;/code&gt;lock device as cache&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ dnf install -y libelf-devel elfutils-libelf-devel rpm-build openssl-devel
$ make rpm
$ cd /root/rpmbuild/RPMS/x86_64
$ dnf install -y kernel-4.18.0kernel_bcache-1.x86_64.rpm
$ dnf install -y kernel-devel-4.18.0kernel_bcache-1.x86_64.rpm
$ dnf install -y kernel-headers-4.18.0kernel_bcache-1.x86_64.rpm
$ reboot
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ modprobe bcache
$ lsmod | grep bcache
bcache                274432  0
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;bcache 设置&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;$ make &amp;amp;&amp;amp; make install
cc -O2 -Wall -g `pkg-config --cflags uuid blkid`    make-bcache.c bcache.o  `pkg-config --libs uuid blkid` -o make-bcache
/tmp/cchr1HR5.o: In function `write_sb&apos;:
/root/bcache-tools/make-bcache.c:277: undefined reference to `crc64&apos;
collect2: error: ld returned 1 exit status
make: *** [&amp;lt;builtin&amp;gt;: make-bcache] Error 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据 https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=777798, 由于 gcc 在 gcc-5 以上版本会编译失败，所以需要做出如下修改：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;diff --git a/bcache.c b/bcache.c
index 8f37445..8b4b986 100644
--- a/bcache.c
+++ b/bcache.c
@@ -115,7 +115,7 @@ static const uint64_t crc_table[256] = {
        0x9AFCE626CE85B507ULL
 };

-inline uint64_t crc64(const void *_data, size_t len)
+uint64_t crc64(const void *_data, size_t len)
 {
        uint64_t crc = 0xFFFFFFFFFFFFFFFFULL;
        const unsigned char *data = _data;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ lsblk /dev/sdc
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sdc           8:32   0  96.2T  0 disk
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ gdisk -l /dev/sdc
GPT fdisk (gdisk) version 1.0.3

Partition table scan:
  MBR: not present
  BSD: not present
  APM: not present
  GPT: not present

Creating new GPT entries.
Disk /dev/sdc: 206646018048 sectors, 96.2 TiB
Model: PERC H330 Adp
Sector size (logical/physical): 512/4096 bytes
Disk identifier (GUID): C712DA70-1BB0-45E5-A6EA-BE1CEC30DE13
Partition table holds up to 128 entries
Main partition table begins at sector 2 and ends at sector 33
First usable sector is 34, last usable sector is 206646018014
Partitions will be aligned on 2048-sector boundaries
Total free space is 206646017981 sectors (96.2 TiB)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ gdisk /dev/sdc
GPT fdisk (gdisk) version 1.0.3

Partition table scan:
  MBR: not present
  BSD: not present
  APM: not present
  GPT: not present

Creating new GPT entries.

Command (? for help): ?
b	back up GPT data to a file
c	change a partition&apos;s name
d	delete a partition
i	show detailed information on a partition
l	list known partition types
n	add a new partition
o	create a new empty GUID partition table (GPT)
p	print the partition table
q	quit without saving changes
r	recovery and transformation options (experts only)
s	sort partitions
t	change a partition&apos;s type code
v	verify disk
w	write table to disk and exit
x	extra functionality (experts only)
?	print this menu

Command (? for help): n
Partition number (1-128, default 1): 1
First sector (34-206646018014, default = 2048) or {+-}size{KMGTP}:
Last sector (2048-206646018014, default = 206646018014) or {+-}size{KMGTP}: +30T
Current type is &apos;Linux filesystem&apos;
Hex code or GUID (L to show codes, Enter = 8300):
Changed type of partition to &apos;Linux filesystem&apos;

Command (? for help): n
Partition number (2-128, default 2): 2
First sector (34-206646018014, default = 64424511488) or {+-}size{KMGTP}:
Last sector (64424511488-206646018014, default = 206646018014) or {+-}size{KMGTP}: +30T
Current type is &apos;Linux filesystem&apos;
Hex code or GUID (L to show codes, Enter = 8300):
Changed type of partition to &apos;Linux filesystem&apos;

Command (? for help): n
Partition number (3-128, default 3):
First sector (34-206646018014, default = 128849020928) or {+-}size{KMGTP}:
Last sector (128849020928-206646018014, default = 206646018014) or {+-}size{KMGTP}:
Current type is &apos;Linux filesystem&apos;
Hex code or GUID (L to show codes, Enter = 8300):
Changed type of partition to &apos;Linux filesystem&apos;

Command (? for help): p
Disk /dev/sdc: 206646018048 sectors, 96.2 TiB
Model: PERC H330 Adp
Sector size (logical/physical): 512/4096 bytes
Disk identifier (GUID): 3F595DB4-EA86-4E06-AC94-A2FF0226716B
Partition table holds up to 128 entries
Main partition table begins at sector 2 and ends at sector 33
First usable sector is 34, last usable sector is 206646018014
Partitions will be aligned on 2048-sector boundaries
Total free space is 2014 sectors (1007.0 KiB)

Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048     64424511487   30.0 TiB    8300  Linux filesystem
   2     64424511488    128849020927   30.0 TiB    8300  Linux filesystem
   3    128849020928    206646018014   36.2 TiB    8300  Linux filesystem

Command (? for help): w

Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!

Do you want to proceed? (Y/N): Y
OK; writing new GUID partition table (GPT) to /dev/sdc.
The operation has completed successfully.
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ make-bcache -B /dev/sdc1 /dev/sdc2 /dev/sdc3 -C /dev/sdb
UUID:			38dede02-5520-450a-8248-10263e15b5a1
Set UUID:		f856548a-e29b-4b89-b097-40d3b6b50ea2
version:		0
nbuckets:		1829376
block_size:		1
bucket_size:		1024
nr_in_set:		1
nr_this_dev:		0
first_bucket:		1
UUID:			f00b9417-3c93-4ffe-9b7d-d514a8e14666
Set UUID:		f856548a-e29b-4b89-b097-40d3b6b50ea2
version:		1
block_size:		1
data_offset:		16
UUID:			204774b5-79e7-4899-a3c5-bd1df37869db
Set UUID:		f856548a-e29b-4b89-b097-40d3b6b50ea2
version:		1
block_size:		1
data_offset:		16
UUID:			245818e5-a4ff-4787-b888-3165eb6bb2d7
Set UUID:		f856548a-e29b-4b89-b097-40d3b6b50ea2
version:		1
block_size:		1
data_offset:		16
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ lsblk /dev/sdb /dev/sdc
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sdb           8:16   0 893.3G  0 disk
├─bcache0   252:0    0    30T  0 disk
├─bcache1   252:128  0    30T  0 disk
└─bcache2   252:256  0  36.2T  0 disk
sdc           8:32   0  96.2T  0 disk
├─sdc1        8:33   0    30T  0 part
│ └─bcache0 252:0    0    30T  0 disk
├─sdc2        8:34   0    30T  0 part
│ └─bcache1 252:128  0    30T  0 disk
└─sdc3        8:35   0  36.2T  0 part
  └─bcache2 252:256  0  36.2T  0 disk
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ mkfs.ext4 -F /dev/bcache0
mke2fs 1.45.4 (23-Sep-2019)
Discarding device blocks: done
Creating filesystem with 8053063678 4k blocks and 503316480 inodes
Filesystem UUID: acb6fe94-3c89-482e-87bb-4f238247c51d
Superblock backups stored on blocks:
	32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
	4096000, 7962624, 11239424, 20480000, 23887872, 71663616, 78675968,
	102400000, 214990848, 512000000, 550731776, 644972544, 1934917632,
	2560000000, 3855122432, 5804752896

Allocating group tables: done
Writing inode tables: done
Creating journal (262144 blocks): done
Writing superblocks and filesystem accounting information: done

$ mkfs.ext4 -F /dev/bcache1
$ mkfs.ext4 -F /dev/bcache2
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ mount /dev/bcache0 /mnt/bcache0
$ mount /dev/bcache1 /mnt/bcache1
$ mount /dev/bcache2 /mnt/bcache2
$ lsblk /dev/sdc
NAME        MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sdc           8:32   0 96.2T  0 disk
├─sdc1        8:33   0   30T  0 part
│ └─bcache0 252:0    0   30T  0 disk /mnt/bcache0
├─sdc2        8:34   0   30T  0 part
│ └─bcache1 252:128  0   30T  0 disk /mnt/bcache1
└─sdc3        8:35   0 36.2T  0 part
  └─bcache2 252:256  0 36.2T  0 disk /mnt/bcache2
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash
# chkconfig: 2345 20 80

. /etc/init.d/functions

start() {
    modprobe bcache
    echo /dev/sdb &amp;gt; /sys/fs/bcache/register
    echo /dev/sdc1 &amp;gt; /sys/fs/bcache/register
    echo /dev/sdc2 &amp;gt; /sys/fs/bcache/register
    echo /dev/sdc3 &amp;gt; /sys/fs/bcache/register
    mount /dev/bcache0 /mnt/bcache0
    mount /dev/bcache1 /mnt/bcache1
    mount /dev/bcache2 /mnt/bcache2
}

stop() {
    unmount /dev/bcache0
    unmount /dev/bcache1
    unmount /dev/bcache2
}

case &quot;$1&quot; in
start)
    start
    ;;
stop)
    stop
    ;;
restart)
    stop
    start
    ;;
*)
    echo &quot;Usage: $0 {start|stop|restart}&quot;
esac

exit 0
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ chmod +x bcache
$ systemctl enable bcache.service
bcache.service is not a native service, redirecting to systemd-sysv-install.
Executing: /usr/lib/systemd/systemd-sysv-install enable bcache
$ reboot
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ df -h | grep bcache
/dev/bcache0          30T   24K   29T   1% /mnt/bcache0
/dev/bcache1          30T   24K   29T   1% /mnt/bcache1
/dev/bcache2          37T   24K   35T   1% /mnt/bcache2
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://evilpiepirate.org/git/linux-bcache.git/tree/Documentation/bcache.txt&quot;&gt;bcache.txt « Documentation - linux-bcache.git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.archlinux.org/index.php/Bcache&quot;&gt;Bcache - ArchWiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gist.github.com/TheAnonymous/5787963&quot;&gt;TheAnonymous/Bcache Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://markrepo.github.io/maintenance/2018/09/10/bcache/&quot;&gt;Linux 下块设备缓存之 Bcache 使用（整理）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ziyablog.com/266/bcache%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B/&quot;&gt;Bcache 使用教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://support.huaweicloud.com/prtg-kunpengsdss/kunpengbcache_02_0001.html&quot;&gt;鲲鹏分布式存储解决方案 &amp;gt; 移植指南 &amp;gt; Bcache 移植指南（CentOS 7.6）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/Kaz_K/items/395aa8bbabd5601b12a1&quot;&gt;dm-cache の技術概要と構築手順&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Storage</category></item><item><title>Getting Started with Apache Kafka on Kubernetes with Strimzi</title><link>https://rudeigerc.dev/posts/apache-kafka-on-kubernetes-with-strimzi/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/apache-kafka-on-kubernetes-with-strimzi/</guid><description>本文为在 Kubernetes 集群中使用 Strimzi 部署 Apache Kafka 的笔记。</description><pubDate>Tue, 01 Sep 2020 13:50:16 GMT</pubDate><content:encoded>&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;最近由于实验室的项目需要使用消息中间件来支撑整个系统的消息中心，后续也有将整体服务之间的交互更改为事件驱动的计划，所以决定使用 Apache Kafka&lt;a href=&quot;https://kafka.apache.org/&quot;&gt;^1&lt;/a&gt; 作为系统的消息中间件。Apache Kafka 是一个被广泛使用的基于 ZooKeeper 的分布式流处理平台，最初由 LinkedIn 进行开发，其通过定义 Topic 来进行消息的分类。&lt;/p&gt;
&lt;p&gt;在本项目中我们的集群资源是基于 Kubernetes&lt;a href=&quot;https://kubernetes.io/&quot;&gt;^2&lt;/a&gt; 进行管理的，因此我们也希望能够将 Kafka 部署在 Kubernetes 之上便于整体的管理和控制。
我们在这里采用的解决方案是由 RedHat 开源的 Strimzi&lt;a href=&quot;https://strimzi.io/&quot;&gt;^3&lt;/a&gt;，Strimzi 是 CNCF 的 Sandbox 级项目，其通过定义 Kubernetes Operator 来达到 Kubernetes-native 的 Kafka 集群管理以及相关组件的控制。&lt;/p&gt;
&lt;h2&gt;预备&lt;/h2&gt;
&lt;h3&gt;使用场景&lt;/h3&gt;
&lt;p&gt;这里介绍一下我们使用 Kafka 的具体场景，现阶段 Kafka 主要负责的是对事件的触发进行记录，随后对这些事件的消息进行持久化。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;         gRPC                                                      JDBC Sink Connector
service &amp;lt;----&amp;gt; message server ----&amp;gt; Kafka Cluster (Kafka Connect) --------------------&amp;gt; MySQL
                                                                                          |
client &amp;lt;----------------------------------------------------------------------------------
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Strimzi&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://strimzi.io/docs/operators/latest/images/operators.png&quot; alt=&quot;Strimzi architecture&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Strimzi 实际上在 repository 里已经给出了许多使用相关 CRD 的例子&lt;a href=&quot;https://github.com/strimzi/strimzi-kafka-operator/tree/master/examples&quot;&gt;^4&lt;/a&gt;，同时也在文档里详细描述了部署的过程&lt;a href=&quot;https://strimzi.io/docs/operators/latest/deploying.html&quot;&gt;^5&lt;/a&gt;。我们这里主要基于上述的材料进行修改来达到我们实际的使用需求。&lt;/p&gt;
&lt;h3&gt;Kafka Cluster&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: kafka.strimzi.io/v1beta1
kind: Kafka
metadata:
  name: kafka-cluster
  namespace: kafka
spec:
  kafka:
    version: 2.5.0
    replicas: 3
    listeners:
      plain: {}
      tls:
        authentication:
          type: tls
    template:
      pod:
        securityContext: # strimzi/strimzi-kafka-operator#1720
          runAsUser: 0
          fsGroup: 0
    config:
      offsets.topic.replication.factor: 3
      transaction.state.log.replication.factor: 3
      transaction.state.log.min.isr: 2
      log.message.format.version: &quot;2.5&quot;
    storage:
      type: jbod
      volumes:
        - id: 0
          type: persistent-claim
          size: 8Gi
          deleteClaim: false
          class: nfs-storage
  zookeeper:
    template:
      pod:
        securityContext:
          runAsUser: 0
          fsGroup: 0
    replicas: 3
    storage:
      type: persistent-claim
      size: 8Gi
      deleteClaim: false
      class: nfs-storage
  entityOperator:
    topicOperator: {}
    userOperator: {}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ kubectl get all -n kafka
NAME                                                 READY   STATUS    RESTARTS   AGE
pod/kafka-cluster-entity-operator-69cdc646f8-4lmsx   3/3     Running   0          42h
pod/kafka-cluster-kafka-0                            2/2     Running   0          42h
pod/kafka-cluster-kafka-1                            2/2     Running   0          42h
pod/kafka-cluster-kafka-2                            2/2     Running   0          42h
pod/kafka-cluster-zookeeper-0                        1/1     Running   0          42h
pod/kafka-cluster-zookeeper-1                        1/1     Running   0          42h
pod/kafka-cluster-zookeeper-2                        1/1     Running   0          42h
pod/strimzi-cluster-operator-7d6cd6bdf7-b54pc        1/1     Running   0          14d

NAME                                        TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
service/kafka-cluster-kafka-bootstrap       ClusterIP   10.103.3.225     &amp;lt;none&amp;gt;        9091/TCP,9092/TCP,9093/TCP   42h
service/kafka-cluster-kafka-brokers         ClusterIP   None             &amp;lt;none&amp;gt;        9091/TCP,9092/TCP,9093/TCP   42h
service/kafka-cluster-zookeeper-client      ClusterIP   10.97.214.15     &amp;lt;none&amp;gt;        2181/TCP                     42h
service/kafka-cluster-zookeeper-nodes       ClusterIP   None             &amp;lt;none&amp;gt;        2181/TCP,2888/TCP,3888/TCP   42h

NAME                                            READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/kafka-cluster-entity-operator   1/1     1            1           42h
deployment.apps/strimzi-cluster-operator        1/1     1            1           14d

NAME                                                       DESIRED   CURRENT   READY   AGE
replicaset.apps/kafka-cluster-entity-operator-69cdc646f8   1         1         1       42h
replicaset.apps/strimzi-cluster-operator-7d6cd6bdf7        1         1         1       14d

NAME                                       READY   AGE
statefulset.apps/kafka-cluster-kafka       3/3     42h
statefulset.apps/kafka-cluster-zookeeper   3/3     42h
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Kafka Connect&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;FROM strimzi/kafka:0.19.0-kafka-2.5.0
USER root:root
COPY ./third_party/kafka-connect-jdbc-5.5.1.jar /opt/kafka/plugins/
COPY ./third_party/mysql-connector-java-5.1.48.jar /opt/kafka/libs/
USER 1001
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: v1
kind: Secret
metadata:
  name: mysql-secret
  namespace: kafka
type: Opaque
stringData:
  password: PASSWORD
---
apiVersion: kafka.strimzi.io/v1alpha1
kind: KafkaUser
metadata:
  name: kafka-connect-cluster
  namespace: kafka
  labels:
    strimzi.io/cluster: kafka-cluster
spec:
  authentication:
    type: tls
---
apiVersion: kafka.strimzi.io/v1beta1
kind: KafkaConnect
metadata:
  name: kafka-connect-cluster
  namespace: kafka
  annotations:
    strimzi.io/use-connector-resources: &quot;true&quot;
spec:
  image: REGISTRY/rudeigerc/custom-kafka
  version: 2.5.0
  replicas: 1
  bootstrapServers: kafka-cluster-kafka-bootstrap.kafka.svc.cluster.local:9092
  tls:
    trustedCertificates:
      - secretName: kafka-cluster-cluster-ca-cert
        certificate: ca.crt
  authentication:
    type: tls
    certificateAndKey:
      secretName: kafka-connect-cluster
      certificate: user.crt
      key: user.key
  config:
    config.providers: file
    config.providers.file.class: org.apache.kafka.common.config.provider.FileConfigProvider
    group.id: connect-cluster
    offset.storage.topic: connect-cluster-offsets
    config.storage.topic: connect-cluster-configs
    status.storage.topic: connect-cluster-status
    key.converter: org.apache.kafka.connect.json.JsonConverter
    value.converter: org.apache.kafka.connect.json.JsonConverter
    key.converter.schemas.enable: true
    value.converter.schemas.enable: true
  externalConfiguration:
    volumes:
      - name: offset-config
        configMap:
          name: offset-config
      - name: mysql-sink-connector-config
        secret:
          secretName: mysql-sink-connector-config
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$ kubectl get pods \
    -n kafka \
    --selector=app.kubernetes.io/part-of=strimzi-kafka-connect-cluster,app.kubernetes.io/name=kafka-connect \
    -o jsonpath=&apos;{.items[0].metadata.name}&apos;  \
    | xargs -o -i kubectl exec {} -it \
    -n kafka \
    -- /bin/curl kafka-connect-cluster-connect-api.kafka.svc.cluster.local:8083
{&quot;version&quot;:&quot;2.5.0&quot;,&quot;commit&quot;:&quot;66563e712b0b9f84&quot;,&quot;kafka_cluster_id&quot;:&quot;4uH6peN4QoKdw7CQGvhxRA&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;MySQL Sink Connector&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: v1
kind: Secret
metadata:
  name: mysql-sink-connector-config
  namespace: kafka
type: Opaque
stringData:
  connector.properties: |-
    username: USERNAME
    password: PASSWORD
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;apiVersion: kafka.strimzi.io/v1alpha1
kind: KafkaConnector
metadata:
  name: mysql-sink-connector
  namespace: kafka
  labels:
    strimzi.io/cluster: kafka-connect-cluster
spec:
  class: io.confluent.connect.jdbc.JdbcSinkConnector
  tasksMax: 3
  config:
    topics: test
    connection.url: jdbc:mysql://mysql.kafka.svc.cluster.local:3306/demo
    connection.user: ${file:/opt/kafka/external-configuration/mysql-sink-connector-config/connector.properties:username}
    connection.password: ${file:/opt/kafka/external-configuration/mysql-sink-connector-config/connector.properties:password}
    insert.mode: upsert
    table.name.format: notification
    pk.mode: record_value
    pk.fields: id
    auto.create: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里需要注意的是 label 中的 &lt;code&gt;strimzi.io/cluster&lt;/code&gt; 指的是 connector cluster 的名称，而不是 Kafka cluster 的名称。
我们在这里采用的是 Confluent 的 JDBC Sink Connector。&lt;/p&gt;
&lt;h2&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://strimzi.io/&quot;&gt;Strimzi - Apache Kafka on Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.redhat.com/blog/2020/02/14/using-secrets-in-apache-kafka-connect-configuration/&quot;&gt;Using secrets in Kafka Connect configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.redhat.com/blog/2020/08/14/introduction-to-strimzi-apache-kafka-on-kubernetes-kubecon-europe-2020/&quot;&gt;Introduction to Strimzi: Apache Kafka on Kubernetes (KubeCon Europe 2020)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.redhat.com/blog/2020/08/21/kubernetes-native-apache-kafka-with-strimzi-debezium-and-apache-camel-kafka-summit-2020/&quot;&gt;Kubernetes-native Apache Kafka with Strimzi, Debezium, and Apache Camel (Kafka Summit 2020)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>Kubernetes</category></item><item><title>Paper Reading | 使用 SGX 对键值对存储加密</title><link>https://rudeigerc.dev/posts/secured-kv/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/secured-kv/</guid><description>本文简要介绍了两篇基于 SGX 进行的存储相关的安全优化的工作，SPEICHER 和 ShieldStore。这两篇工作发表的时间是平行的，两者具有相同的切入点，但基于两者的数据结构有不同的侧重点。</description><pubDate>Mon, 27 Jul 2020 00:33:27 GMT</pubDate><content:encoded>&lt;h2&gt;引言&lt;/h2&gt;
&lt;p&gt;随着云计算的发展，键值对存储作为持久化的存储系统被广泛使用，如 in-memory 的 Memcached 和 Redis，以及基于 LSM-tree 的 LevelDB 和 RocksDB 等，但在云端这样一个不受信任的环境之中如何保证存储系统的安全是一个很大的问题。为了提供一个能被信任的环境，
Intel 和 ARM 分别提出了基于硬件的 TEE（Trusted Execution Environment）的支持，即 Intel SGX 和 ARM TrustZone，通过 TEE 可以在不被信任的的基础设施之上有一个独立的安全的 memory space，我们称其为 enclave，来进行 shielded execution 保证了数据或代码的机密性和完整性。&lt;/p&gt;
&lt;p&gt;这篇文章主要想介绍的是基于 SGX 进行存储相关的安全优化的两篇工作，一篇是来自爱丁堡大学的 SPEICHER，另一篇是来自韩国科学技术研究院的 ShieldStore。这两篇工作发表的时间是平行的，前者发表在 FAST &apos;19 上，后者发表在 EuroSys &apos;19 上，两者具有相同的切入点，即两者同样意识到了根据现有的 SGX 的实现，由于其本身的限制，不可能将所有存储的数据结构都存储在 EPC 当中，同时频繁的 enclave exiting 会带来严重的性能开销。两者的不同之处在于前者是基于 LSM-tree 的实现，大部分的存储结构是基于 HDD / SSD 的，所以需要调整 LSM tree 的实现才能保证其安全性；后者是 in-memory 的实现，主要根据 hashing 进行存储的映射，同时为了规避 hashing collision 的情况采用了链表作为相同 bucket 时的数据结构。&lt;/p&gt;
&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;那么基于 SGX 的 shielded execution 具体存在着怎样的问题呢？首先我们可以想到的是这个 enclave 的区域肯定不是无限大的，尤其是其基于硬件的实现，肯定存在着一个存储的上限。实际上 enclave 的 memory 是位于 EPC（Enclave Page Cache）当中的，其大小只有 128 MB，除去掉一些存储 metadata 的保留空间，真正可用的空间大概只有 94 MB。&lt;/p&gt;
&lt;p&gt;为了存储超过该上限的数据，SGX 提供了 securing paging 的机制，该机制可以让操作系统通过 SGX 的指令将 EPC pages 驱逐到未受保护的内存当中，当 EPC pages 被驱逐的时候存储在里面的数据会被加密，当被驱逐的 EPC page 重新被 load 回来的时候会进行解密并验证其完整性，但是这个操作会带来很大的性能开销。通过实验可以发现在超过 EPC 大小的 workload 的情况之下，未使用 SGX 加密的 KV store 的 throughput 大幅高于使用 SGX 加密的 KV store，在使用 4 GB 的 workload 的时候两者的差距可以高达 134.4 倍。&lt;/p&gt;
&lt;h2&gt;SPEICHER&lt;/h2&gt;
&lt;p&gt;大部分基于 LSM-tree 的 KV store 的存储结构都是存储在 HDD / SSD 中的。LSM-tree 会将 write 的请求按照顺序 buffer 在 MemTable 中，其内部实现是 skiplist，与此同时会生成对应的 WAL。当其大小超过一定的阈值之后会固定为 immutable MemTable，然后 allocate 新的 MemTable 和 WAL。随后 immutable MemTable 会被 flush 到 disk 上存储在一个叫 SSTable 的结构当中，这个过程称为 minor compaction。被 flush 到 disk 上的 SSTable 会被存储在一个多层结构的第零层中，这些 SSTable 的范围是有重合的。当达到另一个设定的阈值的时候，LSM-tree 会将低层的数据和高层的数据进行合并，淘汰掉旧的数据，这个过程称为 major compaction。LSM-tree 会不停重复上述的步骤保证整体的空间利用率且每个 SSTable 对于每个 key 最多只有一个 entry。&lt;/p&gt;
&lt;p&gt;上文提到的 shielded execution 主要是被用来对 in-memory 的计算进行加密的，如何将这个针对 stateless 的 enclave 的加密扩展到这种 stateful 的存储结构上就是一个很大的挑战。因此这篇工作主要在不被信任的的基础设施上实现了一个安全的基于 LSM-tree 的 KV Store，他们基于 Intel SPDK 设计了 shielded execution 的 I/O library，针对 EPC 的特性对 LSM-tree 的数据结构进行了调整，包括 MemTable、SSTable 和 WAL 等，使其能够保证机密性和完整性，以及对应的相关操作的算法，同时实现了一个异步的单调计数器来保证数据的 freshness 防止 rollback 和 forking attack。&lt;/p&gt;
&lt;h3&gt;系统设计&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.rudeigerc.dev/cdn-cgi/imagedelivery/zHp1Y4Kl9MXfXkk0kqREVw/0e141ed1-2ed6-4a83-8cc6-6d5c4f0e8000/public&quot; alt=&quot;speicher-overview&quot; /&gt;&lt;/p&gt;
&lt;p&gt;整个系统包括 SPEICHER 的 controller，shielded execution 的 I/O library，受信任的单调计数器，存储引擎，还有改进的 LSM data structure。SPEICHER 这边利用了 SCONE 的 container support 来进行自身的部署，并且基于 SPDK 建立了一个 shielded I/O library。SPDK 是一个高性能的 user mode 的 storage library，其通过将 direct memory access（DMA）的 buffer 映射到用户地址空间实现了 zero-copy 的 I/O。&lt;/p&gt;
&lt;h4&gt;MemTable&lt;/h4&gt;
&lt;p&gt;SPEICHER 将 MemTable 中的 key 和 value 拆成了两部分，原先 key 和 value 是一起被存储在 skiplist 当中的。实际上将 key 和 value 进行拆分是一个比较直观的想法，在引入 SSD 之后也有一些优化 LSM-tree 的工作利用其 random I/O 的性能将 SSTable 中的 key 和 value 拆开了存储，只是这里的应用场景变成了在内存当中。这里将 key 存储在了 encalve 当中然后将加密后的 value 存储在 untrusted 的 host memory 当中，同时存储了指向 value 位置的 pointer 以及验证其 integrity 和 freshness 的 hash value。论文在这里介绍了在他们所使用的参数配置之下可以达到 95.2% 的空间降低。&lt;/p&gt;
&lt;h4&gt;SSTable&lt;/h4&gt;
&lt;p&gt;对于 SSTable，可以看到这里维护的是一个 key-value pair 的 array，当然这里实际上可能还会存储一些如 Bloom filter 的结构来加快其读的性能这里先忽略不计，这里为了保证其安全性 key-value 都进行了加密，同时以一个 block 为单位（32 KB）维护了其 hash value。这些 block 的 hash value 被集中存储在 SSTable 的 footer，当在读取的时候会访问对应的 key value 还有其对应的 hash value 来验证其完整性。为了保证这个 footer 的完整性 footer 也会进行一次 hashing 并将值存储在 manifest 当中，形成了一个 merkle tree。&lt;/p&gt;
&lt;h4&gt;Log 文件&lt;/h4&gt;
&lt;p&gt;最后是 WAL 和 manifest 的结构，原先 WAL 中存储的是对应的 record 的结构，这里增加了 trusted counter 和对应的 key-value 的 hash 值，这样在 restore 的时候就可以利用两者验证 kv，然后对操作进行 replay。&lt;/p&gt;
&lt;h3&gt;评估&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.rudeigerc.dev/cdn-cgi/imagedelivery/zHp1Y4Kl9MXfXkk0kqREVw/cad4d292-acb0-4f0d-c389-dfe04dd7c700/public&quot; alt=&quot;speicher-evaluation&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在 evaluation 的部分这边作者主要关注的是两个问题，其一是 SPEICHER 用来实现 shielded execution 的 I/O library 的性能，其二是 SPEICHER 所引入的 overhead。针对前者， SPEICHER 的 I/O library 和 native 的 SPDK 的实现相比，两者的 throughput 接近，性能基本上没有损失。针对后者，论文给出了三个不同的场景，包括不同读写比例的 workload，不同 value 的大小的 workload，以及在多线程情况下的性能。&lt;/p&gt;
&lt;h2&gt;ShieldStore&lt;/h2&gt;
&lt;p&gt;ShieldStore 也是基于前述的背景，他们首先产生了一个初步的设想，即可以将主要的数据结构存储在 untrusted 的部分，然后能够从 enclave 内部对其进行访问。实际上这里可以发现 ShieldStore 和 SPEICHER 的想法是十分相似的，两者都是将计算过程在 enclave 的内部执行并存储了 metadata 的部分，同时将加密过后的内容存储在 untrusted 的部分当中，从而规避了 EPC 空间不足以及相对应的 high cost 的 paging context switch 的问题。&lt;/p&gt;
&lt;h3&gt;系统设计&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.rudeigerc.dev/cdn-cgi/imagedelivery/zHp1Y4Kl9MXfXkk0kqREVw/39b1ba19-a215-44ea-f7b4-0e295cecab00/public&quot; alt=&quot;shieldstore-overview&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ShieldStore 整体的设计基本上延续了之前的设想，是将 metadata 存储在 enclave 内部，然后将主要的 data structure 的部分经过加密存储在 untrusted memory region 中，只有 secret keys 和 integrity 的metadata 会存储在 EPC 当中，主要的 hash table 的数据结构存储在 unprotected memory region。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.rudeigerc.dev/cdn-cgi/imagedelivery/zHp1Y4Kl9MXfXkk0kqREVw/e797f2b5-2894-48b1-3d12-176c3896b400/public&quot; alt=&quot;shieldstore-data-organization&quot; /&gt;&lt;/p&gt;
&lt;p&gt;为了防止 rollback attack，ShieldStore 这里和 SPEICHER 一样使用了 Merkle tree，但是这里不可能将每个 key-value pair 都维护在 merkle tree 中，这里的方法是为以 bucket 为单位的 MAC 生成 in-enclave 的 hash，这个会根据 MAC hash 的数量和 bucket 的数量进行调整，这实际上也是一个 trade-off，具体的调参在论文后面的 evaluation 有详细的解释。这里可能会有超出 EPC 大小限制的顾虑，可以通过 paging mechanism 的 eviction 来解决这个问题，虽然会带来我们先前所提到的 drawback。ShieldStore 在加密过程中主要采用的是 AES-CTR 进行加密，这里会将 key 和 value 一起加密，最后会生成包含 key-value pair size 等参数的 128 bit 的 MAC 来保证其完整性。&lt;/p&gt;
&lt;h3&gt;优化&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;MAC bucketing：这边将每个 bucket 里的 MAC 集合在了一起，然后再计算一次 MAC 和在 enclave 当中存储的 MAC 进行比较，保证了其完整性。&lt;/li&gt;
&lt;li&gt;Searching encrypted key：由于 hashing collision，在搜索一个 key 的时候需要遍历整个 hash chain 并进行解密，为了降低 searching 的开销，ShieldStore 在这里引入了一个 byte 的 hint，可以有效地降低 candidate 的数量，实际上这也是一个 leakage 和 performance 的 trade-off。&lt;/li&gt;
&lt;li&gt;自定义的 heap allocator：SGX 实际上维护了两个 memory allocator，分别负责 enclave 的内部和外部，但是对 outside heap allocator 的访问都需要退出 enclave，ShieldStore 这里通过修改 SGX 的 tcmalloc library 创建了自定义的 allocator，运行在 enclave 内部但是 allocate 的是外部的 memory。&lt;/li&gt;
&lt;li&gt;多线程的优化：每个线程会负责特定的 partition 来降低线程同步带来的开销。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;评估&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Data Set&lt;/th&gt;
&lt;th&gt;Key Size (B)&lt;/th&gt;
&lt;th&gt;Value Size (B)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Small&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;512&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;论文的 evaluation 主要有三个 metric，一个是 secured memcached，这边采用的实现是 Graphene-SGX，第二个是 ShieldBase，指的是未经过优化的 ShieldStore，第三个是 ShieldOPT，指的是经过上述策略优化的 ShieldStore。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.rudeigerc.dev/cdn-cgi/imagedelivery/zHp1Y4Kl9MXfXkk0kqREVw/4010ec60-467f-4c2d-9477-f2e93d933c00/public&quot; alt=&quot;shieldstore-evaluation-thread&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.rudeigerc.dev/cdn-cgi/imagedelivery/zHp1Y4Kl9MXfXkk0kqREVw/6fab4567-3016-4735-f156-07e87a90da00/public&quot; alt=&quot;shieldstore-evaluation-scalability&quot; /&gt;&lt;/p&gt;
&lt;p&gt;ShieldStore 和前两者相比具有显著的 throughput 的提升，可以看到在 value 的大小较小的时候性能的提升越明显。在多线程的情况下 memcached 的 throughput 甚至会有所下滑，在单线程的情况下 ShieldStore 的性能是 secure memcached 的七到八倍，在四个线程的情况下是其的 24 到 27 倍，更详细的对比实验可以参阅论文，包括进行不同优化以及与另外一篇工作 Eleos 的对比。&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;这里对两者进行一个简单的总结，可以看到两者作为同一时期发表的文章不管是 motivation 还是 solution 都是有一定程度上的重合的，只不过两者的应用场景不同，包括存储数据结构的拆分，保证 integrity 所使用的策略，merkle tree 的建立，以及保证 freshness 所采用的单调计数器的设计，只不过 SPEICHER 是基于 LSM 的，ShieldStore 是 in-memory 的，所以两者的侧重点各有不同。&lt;/p&gt;
&lt;p&gt;由于我本身的研究方向比较少涉及到 security，这两篇工作主要也是因为课程需要才进行了简单的阅读，我对 SGX 具体的机制和实现也并没有特别深入的了解，如有疏漏还望各位斧正。&lt;/p&gt;
&lt;h2&gt;参考文献&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Victor Costan and Srinivas Devadas. 2016. Intel SGX explained. (2016).&lt;/li&gt;
&lt;li&gt;Maurice Bailleu, Jörg Thalheim, Pramod Bhatotia, Christof Fetzer, Michio Honda, and Kapil Vaswani. 2019. Speicher: securing LSM-based key-value stores using shielded execution. In Proceedings of the 17th USENIX Conference on File and Storage Technologies (FAST’19). USENIX Association, USA, 173–190.&lt;/li&gt;
&lt;li&gt;Taehoon Kim, Joongun Park, Jaewook Woo, Seungheun Jeon, and Jaehyuk Huh. 2019. ShieldStore: Shielded In-memory Key-value Storage with SGX. In Proceedings of the Fourteenth EuroSys Conference 2019 (EuroSys ’19). Association for Computing Machinery, New York, NY, USA, Article 14, 1–15. DOI:https://doi.org/10.1145/3302424.3303951&lt;/li&gt;
&lt;/ol&gt;
</content:encoded><category>Paper Reading</category><category>Storage</category></item><item><title>Naive Networking with Combine in Swift</title><link>https://rudeigerc.dev/posts/naive-networking-with-combine-in-swift/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/naive-networking-with-combine-in-swift/</guid><description>Combine 是苹果在 WWDC 提出的两大开发框架之一，该框架提供了一个声明式的 Swift API 去处理异步的事件。本文主要结合基于 Moya 并结合 Combine 自定义了一个简单的网络抽象层，在结合响应式编程的同时又能优雅地对网络请求的数据流进行处理。</description><pubDate>Sat, 28 Dec 2019 13:22:51 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://developer.apple.com/documentation/combine&quot;&gt;Combine&lt;/a&gt; 和 &lt;a href=&quot;https://developer.apple.com/xcode/swiftui/&quot;&gt;SwiftUI&lt;/a&gt; 是苹果在 WWDC 2019 提出的两大新的开发框架。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Combine framework provides a declarative Swift API for processing values over time. These values can represent many kinds of asynchronous events. Combine declares publishers to expose values that can change over time, and subscribers to receive those values from the publishers.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Reactive Programming&lt;/h3&gt;
&lt;p&gt;在这里我并不准备过多地介绍响应式编程的基本概念，已经有许多出色的文章详细地介绍了响应式编程。
响应式编程是一种面向数据流和变化传播的编程范式，其核心在于异步的数据流。
在 Combine 问世之前如果想要使用响应式编程开发 iOS 应用一般采用的是 &lt;a href=&quot;https://github.com/ReactiveCocoa/ReactiveSwift&quot;&gt;ReactiveSwift&lt;/a&gt; 或 &lt;a href=&quot;https://github.com/ReactiveX/RxSwift&quot;&gt;RxSwift&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;我在先前的项目中自己尝试过使用 RxSwift 结合 MVVM 的方式，我认为这种开发方式相较于传统的 MVC 方式是相当有魅力的，开发过程中只需要集中在数据流的变化对整个系统产生的影响。&lt;/p&gt;
&lt;h2&gt;Networking&lt;/h2&gt;
&lt;p&gt;在原有的响应式编程的项目之中，我是采用 RxSwift + Moya + Alamofire 来进行网络请求，Moya 提供了一个经过封装后的网络抽象层，能够让原本复杂且混乱的网络请求都经过其来进行。&lt;/p&gt;
&lt;p&gt;由于 Moya 和 Combine 的结合目前还处于 Beta 测试的阶段[^1]，且没有完善的文档描述其使用方式，但是我们又希望能有一个统一的网络抽象层来处理网络请求，因此我决定借鉴一下 Moya 的实现方式，自行编写一个结合 Combine 使用的简单的网络抽象层。&lt;/p&gt;
&lt;p&gt;[^1]: https://github.com/Moya/Moya/issues/1870. 2019-12-29.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;protocol TargetType {
    var baseURL: URL { get }
    var path: String { get }
    var method: HTTPMethod { get }
    var task: Task { get }
    var headers: [String: String]? { get }
}

enum HTTPMethod: String {
    case GET
    case POST
    case PUT
    case DELETE
}

enum Task {
    case requestPlain
    case requestData(data: Data?)
    case requestParameters(parameters: [String: Any])
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;首先我们对 TargetType 的 Protocol 进行定义，这部分与 Moya 的定义保持基本一致。&lt;code&gt;TargetType&lt;/code&gt; 协议要求以下变量被定义：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;baseURL&lt;/code&gt;： 即该服务基于的 URL。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;path&lt;/code&gt;：即 API Endpoint 相对于 baseURL的位置。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;method&lt;/code&gt;：即该请求使用的 HTTP Method。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;task&lt;/code&gt;：即该请求的模式。&lt;code&gt;Task&lt;/code&gt; 所对应的枚举类型包括 &lt;code&gt;requestPlain&lt;/code&gt;、&lt;code&gt;requestData&lt;/code&gt; 与 &lt;code&gt;requestParameters&lt;/code&gt;。
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;requestPlain&lt;/code&gt; 代表不包含参数的请求。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;requestData&lt;/code&gt; 代表在 body 中发送 &lt;code&gt;Data&lt;/code&gt; 的请求。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;requestParameters&lt;/code&gt; 代表带有参数的请求。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;headers&lt;/code&gt;：即该请求带有的 Headers。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;基于上述协议，我们已经可以定义出一个请求的雏形，随后便可以对 ApiService 进行具体的实现。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;protocol ApiServiceType: AnyObject {
    associatedtype Target: TargetType
}

final class ApiService&amp;lt;Target: TargetType&amp;gt;: ApiServiceType {

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 ApiService 中，我们定义了一个简单的 request 函数，并打算将其和 Combine 结合在一起。
这里定义了一个泛型并让其遵守 Codable 协议，其目的在于要根据传入的泛型的具体类型才能进行解码，映射到对应的 Object 上。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;final class ApiService&amp;lt;Target: TargetType&amp;gt;: ApiServiceType {
+    func request&amp;lt;T: Codable&amp;gt;(_ target: Target, with type: T.Type) -&amp;gt; AnyPublisher&amp;lt;T, Error&amp;gt; { }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ruquest&lt;/code&gt; 函数的具体实现如下，前半部分主要是根据 TargetType 形成具体的 &lt;code&gt;URLRequest&lt;/code&gt;，并在后半部分使用 &lt;code&gt;URLSession.shared.dataTaskPublisher(for: request)&lt;/code&gt; 进行请求，最终解码之后返回带有泛型和 Error 的 AnyPublisher。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;func request&amp;lt;T: Codable&amp;gt;(_ target: Target, with type: T.Type) -&amp;gt; AnyPublisher&amp;lt;T, Error&amp;gt; {
    let decoder = JSONDecoder()
    decoder.keyDecodingStrategy = .convertFromSnakeCase
    decoder.dateDecodingStrategy = .custom { decoder in
        let container = try decoder.singleValueContainer()
        let string = try container.decode(String.self)
        let dateFormatter = ISO8601DateFormatter()
        return dateFormatter.date(from: string) ?? Date()
    }

    let pathURL = URL(string: target.path, relativeTo: target.baseURL)!
    var urlComponents = URLComponents(url: pathURL, resolvingAgainstBaseURL: true)!

    switch target.task {
    case .requestParameters(let parameters):
        urlComponents.queryItems = parameters.map { key, value in
            guard let intValue = value as? Int64 else { return URLQueryItem(name: key, value: &quot;&quot;) }
            return URLQueryItem(name: key, value: String(intValue))
        }
    default:
        break
    }

    var request = URLRequest(url: urlComponents.url!)
    request.httpMethod = target.method.rawValue
    if target.headers != nil {
        target.headers?.forEach { header in
            request.addValue(header.1, forHTTPHeaderField: header.0)
        }
    }

    switch target.task {
    case .requestData(let data):
        request.httpBody = data
    default:
        break
    }

    return URLSession.shared
        .dataTaskPublisher(for: request)
        .map { $0.data }
        .decode(type: T.self, decoder: decoder)
        .receive(on: RunLoop.main)
        .eraseToAnyPublisher()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在以上这些实现之后，我们可以简单地创建 apiService，&lt;code&gt;Request&lt;/code&gt; 遵循了 &lt;code&gt;TargetType&lt;/code&gt; 协议为其提供具体的实现。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;extension Request: TargetType { }
internal let apiService = ApiService&amp;lt;Request&amp;gt;()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在需要进行网络请求的地方只需要根据在实现中定义好的请求的 Task 以及需要解码的 Model 进行请求即可。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apiService
    .request(.RequestTask, with: Type.self)
    .sink(
        receiveCompletion: { complete in
            if case .failure(let error) = complete {
                // error handling
            }
    }, receiveValue: { _ in
        // if success
    }
)
    .add(to: self.disposeBag)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;本文主要结合基于 Moya 并结合 Combine 自定义了一个简单的网络抽象层，在结合响应式编程的同时又能优雅地对网络请求的数据流进行处理，比起将请求分散在各个 View 当中我认为这种实现方式是相当有意义的。&lt;/p&gt;
&lt;p&gt;当然这样的实现略显粗糙，有很多问题并没有考虑在内，但也算是对 Combine 与 Moya 有了进一步的认识。&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://gist.github.com/staltz/868e7e9bc2a7b8c1f754&quot;&gt;André Staltz. The introduction to Reactive Programming you&apos;ve been missing.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://objccn.io/products/swift-ui&quot;&gt;Wei Wang. SwiftUI and Combine Programming.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.apple.com/documentation/combine&quot;&gt;Combine | Apple Developer Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Moya/Moya&quot;&gt;Moya/Moya: Network abstraction layer written in Swift.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Appendix&lt;/h2&gt;
&lt;p&gt;这是在 2020 年的第一篇博客，虽然开始写的时候是 2019 年底，不过果然还是不负众望地拖到了 2020 年，形成了变相的跨年。&lt;/p&gt;
&lt;p&gt;说起来明明之前发的都是 Cloud Native 相关的，莫名突然冒出一篇 iOS 相关的感觉上就十分地突兀。
实际上我自从结束了实习之后已经一年半没有开发 iOS 应用程序了，这次写起来感觉也没有之前用 RxSwift 的时候顺手，很多操作多少会有点生疏，再加上刚发布的 SwiftUI 也有奇奇怪怪的坑，导致开发进度比现象中还要缓慢，给一起开发的同学造成了一些困扰，心中还是感到有点抱歉。&lt;/p&gt;
&lt;p&gt;若行文中出现了错误还望各位斧正。&lt;/p&gt;
</content:encoded><category>iOS</category></item><item><title>Cloud Native Infrastructure: Part 2 Monitoring</title><link>https://rudeigerc.dev/posts/cloud-native-infrastructure-monitoring/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/cloud-native-infrastructure-monitoring/</guid><description>The post introduces the combination of Prometheus and Grafana.</description><pubDate>Mon, 27 May 2019 14:44:05 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;h2&gt;Prometheus&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://prometheus.io/&quot;&gt;Prometheus&lt;/a&gt; is an open-source systems monitoring and alerting toolkit.&lt;/p&gt;
&lt;h3&gt;Installation&lt;/h3&gt;
&lt;p&gt;In &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services:
  prometheus:
    image: prom/prometheus
    restart: always
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - ./prometheus:/prometheus
    ports:
      - 9090:9090
    user: root
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Prometheus provides a UI at &lt;code&gt;:9090&lt;/code&gt; by default.&lt;/p&gt;
&lt;h3&gt;Node Exporter&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/prometheus/node_exporter&quot;&gt;&lt;strong&gt;Node Exporter&lt;/strong&gt;&lt;/a&gt; is designed ad a Prometheus exporter for hardware and OS metrics exposed by *NIX kernels.&lt;/p&gt;
&lt;p&gt;According to the documentation, it&apos;s not recommended to deploy it as a Docker container because it requires access to the host system, so you could just download the binary release version.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ wget https://github.com/prometheus/node_exporter/releases/download/v0.18.0/node_exporter-0.18.0.linux-amd64.tar.gz
$ tar -xvf node_exporter-0.18.0.linux-amd64.tar.gz
$ cd node_exporter-0.18.0.linux-amd64
# It&apos;s recommended to run the following instruction in tmux in order to keep it running after exit.
$ ./node_exporter &amp;amp;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After started, &lt;code&gt;node_exporter&lt;/code&gt; will be listening at &lt;code&gt;:9100&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Service Registry&lt;/h3&gt;
&lt;p&gt;In order to combine service &lt;code&gt;node-exporter&lt;/code&gt; with &lt;strong&gt;Consul&lt;/strong&gt;, a config directory should be configured.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;consul.d/node_exporter.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;service&quot;: {
    &quot;name&quot;: &quot;node_exporter&quot;,
    &quot;tags&quot;: [&quot;exporter&quot;],
    &quot;address&quot;: &quot;$PRIVATE_IP_ADDRESS&quot;,
    &quot;port&quot;: 9100
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In &lt;code&gt;docker-compose.yml&lt;/code&gt; of Consul:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;version: &quot;3&quot;
services:
  consul:
    image: consul
    container_name: consul
    ports:
      - &quot;8300:8300&quot;
      - &quot;8400:8400&quot;
      - &quot;8500:8500&quot;
      - &quot;8600:53/udp&quot;
    volumes:
      - ./consul.d:/etc/consul.d
    command: agent -dev -client 0.0.0.0 -config-dir=/etc/consul.d
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Configuration&lt;/h3&gt;
&lt;p&gt;In &lt;code&gt;prometheus.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;global:
  scrape_interval: 15s

scrape_configs:
  # Self-monitoring
  - job_name: &quot;prometheus&quot;
    static_configs:
      - targets: [&quot;localhost:9090&quot;]
      - labels:
          instance: prometheus
  # Without service discovery
  - job_name: &quot;node_exporter&quot;
    static_configs:
      - targets: [&quot;$PRIVATE_IP_ADDRESS:9100&quot;]
      - labels:
          instance: node_exporter
  # With service discovery, discard the job above
  - job_name: &quot;node_exporter&quot;
    consul_sd_configs:
      - server: $PRIVATE_IP_ADDRESS:8500
        services:
          - node_exporter
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Grafana&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://grafana.com/&quot;&gt;Grafana&lt;/a&gt; allows users to query, visualize, alert on and understand your metrics no matter where they are stored, supports multiple data sources and provides very powerful dashboard for displaying multiple metrics.&lt;/p&gt;
&lt;h3&gt;Installation&lt;/h3&gt;
&lt;p&gt;In &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services:
  grafana:
    image: grafana/grafana
    restart: always
    ports:
      - 3000:3000
    volumes:
      - grafana:/var/lib/grafana
volumes:
  grafana:
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Grafana provides a UI at &lt;code&gt;:3000&lt;/code&gt; by default.&lt;/p&gt;
&lt;p&gt;You could follow the instructions in Grafana to add data sources including Prometheus and import dashboards.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://grafana.com/dashboards/159&quot;&gt;This dashboard&lt;/a&gt; could show system status according to the data collected by Prometheus.&lt;/p&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;https://prometheus.io/docs/introduction/overview/&lt;/li&gt;
&lt;li&gt;https://grafana.com/docs/&lt;/li&gt;
&lt;li&gt;https://yunlzheng.gitbook.io/prometheus-book/part-ii-prometheus-jin-jie/sd/service-discovery-with-consul&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Cloud Native Infrastructure: Part 1 Service Discovery</title><link>https://rudeigerc.dev/posts/cloud-native-infrastructure-service-discovery/</link><guid isPermaLink="true">https://rudeigerc.dev/posts/cloud-native-infrastructure-service-discovery/</guid><description>Service discovery components maintain a list of service instances that are available for work within a microservice domain. The post introduces the basic usage of Consul.</description><pubDate>Mon, 27 May 2019 14:23:43 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Service discovery&lt;/strong&gt; components maintain a list of service instances that are available for work within a microservice domain, which is the most important part in microservice architecture. The advantage of service discovery components is that clients do not need to know the exact position of services, what they have are just the registered name of services if they want to call or use them.&lt;/p&gt;
&lt;p&gt;Usually, a microservice will register itself to the center of service discovery components when it was started successfully, including necessary parameters such as the unique name of service, IP address and the port bind. This process is called as &lt;strong&gt;Service Registry&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The mainstream solutions of service discovery include &lt;strong&gt;mDNS&lt;/strong&gt; (multicast Domain Name Service), &lt;strong&gt;ZooKeeper&lt;/strong&gt; and &lt;strong&gt;etcd&lt;/strong&gt;, which are based on distributed key-value storage service, &lt;strong&gt;Eureka&lt;/strong&gt;, open-sourced by Netflix, and &lt;a href=&quot;https://www.consul.io/&quot;&gt;&lt;strong&gt;Consul&lt;/strong&gt;&lt;/a&gt;, open-sourced by HashiCorp etc.&lt;/p&gt;
&lt;h2&gt;Consul&lt;/h2&gt;
&lt;p&gt;Consul is a service mesh solution providing a full featured control plane with service discovery, configuration, and segmentation functionality.&lt;/p&gt;
&lt;h3&gt;Installation&lt;/h3&gt;
&lt;h4&gt;Development Environment&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;NOTICE: You should not just use &lt;code&gt;consul agent -dev&lt;/code&gt; in production environment.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;version: &quot;3&quot;
services:
  consul:
    image: consul
    container_name: consul
    ports:
      - &quot;8300:8300&quot;
      - &quot;8400:8400&quot;
      - &quot;8500:8500&quot;
      - &quot;8600:53/udp&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just run &lt;code&gt;docker-compose up -d&lt;/code&gt; and then the container named &lt;code&gt;consul&lt;/code&gt; will be created.&lt;/p&gt;
&lt;p&gt;Consul provides HTTP API for users to check the status of nodes in the cluster, and a UI at &lt;code&gt;:8500&lt;/code&gt; by default.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ curl localhost:8500/v1/catalog/nodes
[
    {
        &quot;ID&quot;: &quot;fbd0daa0-84e4-41d4-69d2-9c446c79c5c2&quot;,
        &quot;Node&quot;: &quot;c7bc60a1e9b0&quot;,
        &quot;Address&quot;: &quot;127.0.0.1&quot;,
        &quot;Datacenter&quot;: &quot;dc1&quot;,
        &quot;TaggedAddresses&quot;: {
            &quot;lan&quot;: &quot;127.0.0.1&quot;,
            &quot;wan&quot;: &quot;127.0.0.1&quot;
        },
        &quot;Meta&quot;: {
            &quot;consul-network-segment&quot;: &quot;&quot;
        },
        &quot;CreateIndex&quot;: 9,
        &quot;ModifyIndex&quot;: 10
    }
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Service discovery is the basic of microservices, which provides the registry center in the distributed application system.&lt;/p&gt;
</content:encoded></item></channel></rss>