├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── labeler.yml └── workflows │ ├── auto-close-issues.yml │ ├── build-images.yml │ ├── docsy.yml │ ├── go.yml │ ├── issue-labeled.yml │ ├── issue-remove-inactive.yml │ ├── issues-similarity-analysis.yml │ ├── labeler.yml │ ├── lint.yml │ └── preview docsy.yml ├── .gitignore ├── .golangci.yaml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── README_zh.md ├── cmd ├── crane-agent │ ├── app │ │ ├── agent.go │ │ └── options │ │ │ └── option.go │ └── main.go ├── craned │ ├── app │ │ ├── manager.go │ │ └── options │ │ │ ├── options.go │ │ │ └── server_options.go │ └── main.go └── metric-adapter │ └── main.go ├── deploy ├── crane-agent │ ├── daemonset.yaml │ ├── rbac.yaml │ └── service.yaml ├── craned │ ├── deployment.yaml │ ├── rbac.yaml │ ├── service.yaml │ └── webhooks.yaml ├── generate-certs.sh ├── keys │ ├── ca.crt │ ├── ca.key │ ├── tls.crt │ └── tls.key ├── manifests │ ├── analysis.crane.io_analytics.yaml │ ├── analysis.crane.io_configsets.yaml │ ├── analysis.crane.io_recommendationrules.yaml │ ├── analysis.crane.io_recommendations.yaml │ ├── autoscaling.crane.io_effectivehorizontalpodautoscalers.yaml │ ├── autoscaling.crane.io_effectiveverticalpodautoscalers.yaml │ ├── autoscaling.crane.io_substitutes.yaml │ ├── ensurance.crane.io_avoidanceactions.yaml │ ├── ensurance.crane.io_nodeqoss.yaml │ ├── ensurance.crane.io_podqoss.yaml │ ├── prediction.crane.io_clusternodepredictions.yaml │ ├── prediction.crane.io_timeseriespredictions.yaml │ └── topology.crane.io_noderesourcetopologies.yaml ├── manifests_1.13 │ ├── analysis.crane.io_analytics.yaml │ ├── analysis.crane.io_configsets.yaml │ ├── analysis.crane.io_recommendationrules.yaml │ ├── analysis.crane.io_recommendations.yaml │ ├── autoscaling.crane.io_effectivehorizontalpodautoscalers.yaml │ ├── autoscaling.crane.io_effectiveverticalpodautoscalers.yaml │ ├── autoscaling.crane.io_substitutes.yaml │ ├── ensurance.crane.io_avoidanceactions.yaml │ ├── ensurance.crane.io_nodeqoss.yaml │ ├── ensurance.crane.io_podqoss.yaml │ ├── prediction.crane.io_clusternodepredictions.yaml │ ├── prediction.crane.io_timeseriespredictions.yaml │ └── topology.crane.io_noderesourcetopologies.yaml ├── metric-adapter │ ├── apiservice.yaml │ ├── deployment.yaml │ ├── rbac.yaml │ └── service.yaml └── scripts │ ├── gencerts.sh │ └── webhook.csr ├── docs ├── CNAME ├── CONTRIBUTING.md ├── README.md ├── assets │ ├── instant.page.5.1.1.js │ ├── mathjax.js │ ├── output │ │ ├── chtml.js │ │ ├── chtml │ │ │ └── fonts │ │ │ │ ├── tex.js │ │ │ │ └── woff-v2 │ │ │ │ ├── MathJax_AMS-Regular.woff │ │ │ │ ├── MathJax_Calligraphic-Bold.woff │ │ │ │ ├── MathJax_Calligraphic-Regular.woff │ │ │ │ ├── MathJax_Fraktur-Bold.woff │ │ │ │ ├── MathJax_Fraktur-Regular.woff │ │ │ │ ├── MathJax_Main-Bold.woff │ │ │ │ ├── MathJax_Main-Italic.woff │ │ │ │ ├── MathJax_Main-Regular.woff │ │ │ │ ├── MathJax_Math-BoldItalic.woff │ │ │ │ ├── MathJax_Math-Italic.woff │ │ │ │ ├── MathJax_Math-Regular.woff │ │ │ │ ├── MathJax_SansSerif-Bold.woff │ │ │ │ ├── MathJax_SansSerif-Italic.woff │ │ │ │ ├── MathJax_SansSerif-Regular.woff │ │ │ │ ├── MathJax_Script-Regular.woff │ │ │ │ ├── MathJax_Size1-Regular.woff │ │ │ │ ├── MathJax_Size2-Regular.woff │ │ │ │ ├── MathJax_Size3-Regular.woff │ │ │ │ ├── MathJax_Size4-Regular.woff │ │ │ │ ├── MathJax_Typewriter-Regular.woff │ │ │ │ ├── MathJax_Vector-Bold.woff │ │ │ │ ├── MathJax_Vector-Regular.woff │ │ │ │ └── MathJax_Zero.woff │ │ ├── svg.js │ │ └── svg │ │ │ └── fonts │ │ │ └── tex.js │ ├── polyfill.min.js │ ├── tex-mml-chtml.js │ ├── util.css │ └── util.js ├── code-standards.md ├── developer-guide.md ├── images │ ├── Crane-FinOps-Certified-Solution.png │ ├── add_cluster.png │ ├── advanced_cpuset_manager.png │ ├── algorithm │ │ └── dsp │ │ │ ├── acf.png │ │ │ ├── dsp.png │ │ │ ├── dsp_debug.png │ │ │ ├── input0.png │ │ │ ├── lft_0_001.png │ │ │ ├── lft_0_01.png │ │ │ ├── linear_regression.png │ │ │ ├── max_value.png │ │ │ ├── missing_data_fill.png │ │ │ ├── remove_outliers.png │ │ │ └── spectrum.png │ ├── analytics-arch.png │ ├── crane-arch.png │ ├── crane-architecture.png │ ├── crane-dashboard.png │ ├── crane-ehpa-metrics-chart.png │ ├── crane-ehpa-replicas-chart.png │ ├── crane-ehpa.png │ ├── crane-keda-ali-compare-cron.png │ ├── crane-overview.png │ ├── crane-qos-ensurance.png │ ├── crane.png │ ├── crane.svg │ ├── crane_recommendation_framework.jpg │ ├── dashboard.png │ ├── dashboard_nodeport.png │ ├── developer-guide │ │ ├── make_all_binaries_result.jpg │ │ ├── make_all_finish.jpg │ │ ├── make_image_docker_images.jpg │ │ ├── make_image_finish.jpg │ │ └── make_image_start.jpg │ ├── disablescheduling-example.png │ ├── dynamic-scheduler-plugin.png │ ├── first_cluster.png │ ├── remote-adapter.png │ └── wechat.jpeg ├── index.md ├── index.zh.md ├── index.zh_TW.md ├── installation.md ├── installation.zh.md ├── installation.zh_TW.md ├── mirror.md ├── mirror.zh.md ├── mirror.zh_TW.md ├── proposals │ ├── 20220228-advanced-cpuset-manger.md │ ├── 20220402-policy-based-abnomal-detection.md │ ├── 20220706-recommendation-definition.md │ ├── 20220706-universal-resource-optimization.md │ ├── 20220712-recommendation-framework-internal.md │ ├── Pod-Sorting-And-Precise-Execution-For-Crane-Agent.md │ ├── Pod-Sorting-And-Precise-Execution-For-Crane-Agent.zh.md │ ├── cpu-usage-water-line.png │ ├── images │ │ ├── opa.png │ │ └── tsdb.png │ └── waterline-construct.png ├── roadmaps │ └── roadmap-2022.md ├── slides │ └── crane-introduction.pptx └── tutorials │ ├── analytics-and-recommendation.md │ ├── analytics-and-recommendation.zh.md │ ├── analytics-and-recommendation.zh_TW.md │ ├── dynamic-scheduler-plugin.md │ ├── dynamic-scheduler-plugin.zh.md │ ├── dynamic-scheduler-plugin.zh_TW.md │ ├── effective-hpa-with-prometheus-adapter.md │ ├── effective-hpa-with-prometheus-adapter.zh.md │ ├── qos-accurately-perform-avoidance-actions.md │ ├── qos-accurately-perform-avoidance-actions.zh.md │ ├── qos-customized-metrics-interference-detection-avoidance-and-sorting.md │ ├── qos-customized-metrics-interference-detection-avoidance-and-sorting.zh.md │ ├── qos-dynamic-resource-oversold-and-limit.md │ ├── qos-dynamic-resource-oversold-and-limit.zh.md │ ├── qos-enhanced-bypass-cpuset-management.md │ ├── qos-enhanced-bypass-cpuset-management.zh.md │ ├── qos-interference-detection-and-active-avoidance.md │ ├── qos-interference-detection-and-active-avoidance.zh.md │ ├── replicas-recommendation.md │ ├── replicas-recommendation.zh.md │ ├── replicas-recommendation.zh_TW.md │ ├── resource-recommendation.md │ ├── resource-recommendation.zh.md │ ├── scheduling-pods-based-on-actual-node-load.md │ ├── scheduling-pods-based-on-actual-node-load.zh.md │ ├── timeseriees-forecasting-by-dsp.md │ ├── using-effective-hpa-to-scaling-with-effectiveness.md │ ├── using-effective-hpa-to-scaling-with-effectiveness.zh.md │ ├── using-qos-ensurance.md │ ├── using-qos-ensurance.zh.md │ ├── using-time-series-prediction.md │ └── using-time-series-prediction.zh.md ├── examples ├── analytics │ ├── config_set.yaml │ ├── nginx-deployment.yaml │ ├── preinstall-rule.yaml │ └── recommendation-configuration.yaml ├── autoscaling │ ├── effective-hpa-cron-local.yaml │ ├── effective-hpa-cron-shanghai.yaml │ ├── effective-hpa-cron.yaml │ ├── effective-hpa.yaml │ └── php-apache.yaml ├── ensurance │ ├── disablescheduling-when-ext-cpu-total-distribute.yaml │ ├── disablescheduling.yaml │ ├── evict-on-cpu-usage-percent │ │ ├── elastic-pod-qos.yaml │ │ ├── eviction-action.yaml │ │ ├── pod.yaml │ │ └── watermark.yaml │ ├── evict-on-cpu-usage-total │ │ └── be-rules.yaml │ ├── evict-on-mem-usage-percent │ │ ├── elastic-pod-qos.yaml │ │ ├── eviction-action.yaml │ │ ├── pod.yaml │ │ └── watermark.yaml │ ├── evict-on-mem-usage-total │ │ ├── elastic-pod-qos.yaml │ │ ├── eviction-action.yaml │ │ ├── pod.yaml │ │ └── watermark.yaml │ ├── eviction.yaml │ ├── low.yaml │ ├── middle.yaml │ ├── nep-schedule-delay.yaml │ ├── throttle.yaml │ ├── waterline1.yaml │ ├── waterline2.yaml │ └── waterline3.yaml ├── grafana.conf ├── load-test.yaml ├── noderesource-tsp-template.yaml ├── php-apache.yaml ├── tsp-node-resource-dsp.yaml ├── tsp-node-resource-percent.yaml ├── tsp-nodes-dsp.yaml ├── tsp-nodes-percent.yaml ├── tsp-pods-dsp.yaml ├── tsp-workload-dsp.yaml └── tsp-workload-resource-dsp.yaml ├── go.mod ├── go.sum ├── hack ├── boilerplate.go.txt ├── install-tools.sh └── local-env-setup.sh ├── mkdocs.yml ├── overrides └── main.html ├── pkg ├── agent │ └── agent.go ├── autoscaling │ └── estimator │ │ ├── estimator.go │ │ ├── external.go │ │ ├── oom.go │ │ ├── percentile.go │ │ └── proportional.go ├── common │ └── types.go ├── controller │ ├── analytics │ │ ├── analytics_controller.go │ │ ├── analytics_controller_test.go │ │ └── config.go │ ├── cnp │ │ └── cnp_controller.go │ ├── ehpa │ │ ├── config.go │ │ ├── effective_hpa_controller.go │ │ ├── hpa.go │ │ ├── hpa_event_handler.go │ │ ├── hpa_observer_controller.go │ │ ├── predict.go │ │ ├── substitute.go │ │ └── substitute_controller.go │ ├── evpa │ │ ├── config.go │ │ ├── container_policy.go │ │ ├── container_policy_test.go │ │ └── effective_vpa_controller.go │ ├── recommendation │ │ ├── recommendation_checker.go │ │ ├── recommendation_controller.go │ │ ├── recommendation_rule_controller.go │ │ ├── recommendation_rule_controller_test.go │ │ ├── recommendation_trigger_controller.go │ │ └── updater.go │ └── timeseriesprediction │ │ ├── config.go │ │ ├── status.go │ │ └── time_series_prediction_controller.go ├── ensurance │ ├── analyzer │ │ ├── analyzer.go │ │ └── evaluator │ │ │ ├── basic.go │ │ │ ├── interface.go │ │ │ └── opa.go │ ├── cache │ │ ├── detection.go │ │ └── nodeqos.go │ ├── cm │ │ └── cpumanager │ │ │ ├── cpu_assignment.go │ │ │ ├── cpu_manager.go │ │ │ ├── policy.go │ │ │ └── util.go │ ├── collector │ │ ├── cadvisor │ │ │ ├── cadvisor_linux.go │ │ │ ├── cadvisor_unsupported.go │ │ │ └── types.go │ │ ├── collector.go │ │ ├── ebpf │ │ │ └── ebpf.go │ │ ├── interface.go │ │ ├── metricsserver │ │ │ └── metricsserver.go │ │ ├── nodelocal │ │ │ ├── cpu.go │ │ │ ├── disk.go │ │ │ ├── memory.go │ │ │ ├── net.go │ │ │ └── nodelocal.go │ │ ├── noderesource │ │ │ └── noderesource.go │ │ ├── noderesourcetopology │ │ │ ├── noderesourcetopology.go │ │ │ └── noderesourcetopology_test.go │ │ └── types │ │ │ ├── constants.go │ │ │ └── types.go │ ├── config │ │ └── config.go │ ├── executor │ │ ├── cpu_usage.go │ │ ├── cpu_usage_percent.go │ │ ├── evict.go │ │ ├── executor.go │ │ ├── interface.go │ │ ├── mem_usage.go │ │ ├── mem_usage_percent.go │ │ ├── metric.go │ │ ├── podinfo │ │ │ └── pod_info.go │ │ ├── release_resource.go │ │ ├── schedule.go │ │ ├── sort │ │ │ ├── cpu_usage_sort.go │ │ │ ├── cpu_usage_sort_test.go │ │ │ ├── general_sort.go │ │ │ ├── mem_usage_sort.go │ │ │ ├── mem_usage_sort_test.go │ │ │ └── sort.go │ │ ├── throttle.go │ │ ├── watermark.go │ │ └── watermark_test.go │ ├── grpc │ │ └── connection.go │ ├── manager │ │ └── interface.go │ ├── runtime │ │ ├── container.go │ │ ├── runtime.go │ │ ├── runtime_unix.go │ │ └── sandbox.go │ └── util │ │ └── match_qos.go ├── features │ └── features.go ├── known │ ├── annotation.go │ ├── const.go │ ├── finalizer.go │ ├── label.go │ ├── reason.go │ ├── types.go │ └── vars.go ├── metricnaming │ └── naming.go ├── metricprovider │ ├── cron_trigger.go │ ├── cron_trigger_test.go │ ├── custom_metric_provider.go │ ├── external_metric_provider.go │ ├── external_metric_provider_test.go │ └── remote.go ├── metricquery │ └── type.go ├── metrics │ ├── analysis.go │ ├── autoscaling.go │ ├── ensuarance.go │ ├── health.go │ ├── health_test.go │ └── metric_collector.go ├── oom │ └── recorder.go ├── prediction │ ├── accuracy │ │ ├── accuracy.go │ │ └── accuracy_test.go │ ├── config │ │ ├── config.go │ │ ├── config_test.go │ │ └── types.go │ ├── dsp │ │ ├── aggregate_signal.go │ │ ├── aggregate_signals.go │ │ ├── aggregate_signals_test.go │ │ ├── auto_correlation.go │ │ ├── auto_correlation_test.go │ │ ├── config.go │ │ ├── debug.go │ │ ├── estimators.go │ │ ├── estimators_test.go │ │ ├── peak.go │ │ ├── peak_test.go │ │ ├── prediction.go │ │ ├── prediction_test.go │ │ ├── preprocessing.go │ │ ├── preprocessing_test.go │ │ ├── signal.go │ │ ├── signal_test.go │ │ └── test_data │ │ │ ├── input0.csv │ │ │ ├── input1.csv │ │ │ ├── input10.csv │ │ │ ├── input11.csv │ │ │ ├── input12.csv │ │ │ ├── input13.csv │ │ │ ├── input14.csv │ │ │ ├── input15.csv │ │ │ ├── input2.csv │ │ │ ├── input3.csv │ │ │ ├── input4.csv │ │ │ ├── input5.csv │ │ │ ├── input6.csv │ │ │ ├── input7.csv │ │ │ ├── input8.csv │ │ │ └── input9.csv │ ├── generic.go │ ├── interface.go │ └── percentile │ │ ├── aggregate_signal.go │ │ ├── aggregate_signals.go │ │ ├── config.go │ │ ├── estimator.go │ │ └── prediction.go ├── predictor │ └── predictor.go ├── prometheus-adapter │ ├── config_fetcher.go │ ├── expression.go │ └── expression_test.go ├── providers │ ├── config.go │ ├── csv │ │ └── csv.go │ ├── grpc │ │ ├── grpc.go │ │ └── pb │ │ │ ├── provider.pb.go │ │ │ ├── provider.proto │ │ │ └── provider_grpc.pb.go │ ├── interfaces.go │ ├── metricserver │ │ ├── metricserver.go │ │ └── rest_metric_client.go │ ├── mock │ │ ├── data.csv │ │ └── mock.go │ ├── prom │ │ ├── ctx.go │ │ ├── ctx_test.go │ │ ├── prom.go │ │ ├── prom_test.go │ │ ├── prometheus.go │ │ └── prometheus_test.go │ └── proxy.go ├── querybuilder-providers │ ├── grpc │ │ └── builder.go │ ├── metricserver │ │ ├── builder.go │ │ └── builder_test.go │ └── prometheus │ │ ├── builder.go │ │ └── builder_test.go ├── querybuilder │ └── query.go ├── recommend │ ├── advisor │ │ ├── advisor.go │ │ ├── replicas.go │ │ ├── replicas_test.go │ │ └── resource_request.go │ ├── config.go │ ├── config_test.go │ ├── inspector │ │ ├── inspector.go │ │ ├── resource_request.go │ │ ├── workload.go │ │ └── workload_pods.go │ ├── recommender.go │ └── types │ │ └── types.go ├── recommendation │ ├── config │ │ └── config.go │ ├── framework │ │ ├── context.go │ │ ├── filter.go │ │ ├── observe.go │ │ ├── prepare.go │ │ └── recommend.go │ ├── manager.go │ └── recommender │ │ ├── .DS_Store │ │ ├── apis │ │ ├── recommender.go │ │ └── types.go │ │ ├── base │ │ ├── filter.go │ │ ├── observe.go │ │ ├── prepare.go │ │ ├── recommend.go │ │ └── registry.go │ │ ├── const.go │ │ ├── hpa │ │ ├── filter.go │ │ ├── observe.go │ │ ├── prepare.go │ │ ├── recommend.go │ │ └── registry.go │ │ ├── idlenode │ │ ├── filter.go │ │ ├── observe.go │ │ ├── prepare.go │ │ ├── recommend.go │ │ └── registry.go │ │ ├── interfaces.go │ │ ├── recommenders.go │ │ ├── replicas │ │ ├── filter.go │ │ ├── observe.go │ │ ├── prepare.go │ │ ├── recommend.go │ │ └── registry.go │ │ ├── resource │ │ ├── filter.go │ │ ├── observe.go │ │ ├── prepare.go │ │ ├── recommend.go │ │ ├── registry.go │ │ ├── resource_spec.go │ │ └── resource_spec_test.go │ │ ├── service │ │ ├── filter.go │ │ ├── observe.go │ │ ├── prepare.go │ │ ├── recommend.go │ │ └── registry.go │ │ └── volume │ │ ├── filter.go │ │ ├── observe.go │ │ ├── prepare.go │ │ ├── recommend.go │ │ └── registry.go ├── resource │ ├── node_resource_manager.go │ └── pod_resource_manger.go ├── server │ ├── config │ │ └── config.go │ ├── ginwrapper │ │ └── core.go │ ├── handler │ │ ├── clusters │ │ │ └── cluster.go │ │ ├── dashboards │ │ │ └── grafana.go │ │ ├── prediction │ │ │ └── prediction.go │ │ ├── prometheus │ │ │ └── prometheus.go │ │ └── recommendation │ │ │ └── recommendation.go │ ├── middleware │ │ ├── cors.go │ │ ├── log.go │ │ └── middleware.go │ ├── router.go │ ├── server.go │ ├── service │ │ ├── cluster │ │ │ ├── cluster.go │ │ │ └── cluster_test.go │ │ └── dashboard │ │ │ ├── dashboard.go │ │ │ └── dashboard_test.go │ └── store │ │ ├── cluster.go │ │ ├── factory.go │ │ ├── mock_store.go │ │ └── secret │ │ ├── cluster.go │ │ ├── cluster_test.go │ │ └── k8s.go ├── topology │ ├── node_resource_topology.go │ └── node_resource_topology_test.go ├── utils │ ├── cgroup.go │ ├── cgroup_test.go │ ├── cpuset.go │ ├── dialer.go │ ├── ehpa.go │ ├── expression_prom_deafult_test.go │ ├── expression_prom_default.go │ ├── hpa.go │ ├── labels.go │ ├── node.go │ ├── pod.go │ ├── pod_template.go │ ├── pod_template_test.go │ ├── pod_test.go │ ├── prediction.go │ ├── recommend.go │ ├── ref.go │ ├── ref_test.go │ ├── resource.go │ ├── scale.go │ ├── schema.go │ ├── slice.go │ ├── slice_test.go │ ├── string.go │ ├── string_test.go │ ├── target │ │ └── fetcher.go │ ├── time.go │ ├── time_test.go │ ├── timestamp.go │ ├── timestamp_test.go │ ├── util.go │ └── vpa.go ├── version │ └── version.go ├── web │ ├── .dockerignore │ ├── .editorconfig │ ├── .eslintignore │ ├── .eslintrc.js │ ├── .gitignore │ ├── .prettierrc.js │ ├── Dockerfile │ ├── README.md │ ├── i18next-scanner.config.js │ ├── index.html │ ├── mock │ │ └── index.js │ ├── nginx │ │ └── default.conf │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── crane.svg │ │ ├── favicon.ico │ │ └── robots.txt │ ├── src │ │ ├── assets │ │ │ └── svg │ │ │ │ ├── assets-result-403.svg │ │ │ │ ├── assets-result-404.svg │ │ │ │ ├── assets-result-500.svg │ │ │ │ ├── assets-result-browser-incompatible.svg │ │ │ │ ├── assets-result-maintenance.svg │ │ │ │ ├── assets-result-network-error.svg │ │ │ │ ├── assets-setting-auto.svg │ │ │ │ ├── assets-setting-dark.svg │ │ │ │ ├── assets-setting-light.svg │ │ │ │ ├── crane-logo-full.svg │ │ │ │ ├── crane-logo-mini.svg │ │ │ │ └── trans.svg │ │ ├── components │ │ │ ├── BoardChart │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ ├── DatePicker │ │ │ │ └── index.tsx │ │ │ ├── ErrorPage │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ ├── PieChart │ │ │ │ └── index.tsx │ │ │ ├── SeriesLineChart │ │ │ │ └── index.tsx │ │ │ └── common │ │ │ │ ├── Card.tsx │ │ │ │ ├── ContentHeader.tsx │ │ │ │ ├── Editor.tsx │ │ │ │ └── HeadBubble.tsx │ │ ├── configs │ │ │ ├── color.ts │ │ │ └── host.ts │ │ ├── global.d.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useCraneDiscount.ts │ │ │ ├── useCraneUrl.ts │ │ │ ├── useDashboardControl.ts │ │ │ ├── useDynamicChart.ts │ │ │ ├── useGrafanaQueyStr.ts │ │ │ ├── useIsIntersecting.ts │ │ │ ├── useIsNeedSelectNamespace.ts │ │ │ ├── useIsValidPanel.ts │ │ │ └── useSelector.ts │ │ ├── i18n │ │ │ ├── index.ts │ │ │ └── resources │ │ │ │ ├── en │ │ │ │ └── translation.json │ │ │ │ └── zh │ │ │ │ └── translation.json │ │ ├── layouts │ │ │ ├── components │ │ │ │ ├── AppLayout.module.less │ │ │ │ ├── AppLayout.tsx │ │ │ │ ├── AppRouter.module.less │ │ │ │ ├── AppRouter.tsx │ │ │ │ ├── Footer.tsx │ │ │ │ ├── Header │ │ │ │ │ ├── HeaderIcon.module.less │ │ │ │ │ ├── HeaderIcon.tsx │ │ │ │ │ ├── Search.module.less │ │ │ │ │ ├── Search.tsx │ │ │ │ │ ├── index.module.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── Menu.module.less │ │ │ │ ├── Menu.tsx │ │ │ │ ├── MenuLogo.tsx │ │ │ │ ├── Page.module.less │ │ │ │ ├── Page.tsx │ │ │ │ └── Setting │ │ │ │ │ ├── RadioColor.module.less │ │ │ │ │ ├── RadioColor.tsx │ │ │ │ │ ├── RadioRect.module.less │ │ │ │ │ ├── RadioRect.tsx │ │ │ │ │ ├── index.module.less │ │ │ │ │ └── index.tsx │ │ │ ├── index.module.less │ │ │ └── index.tsx │ │ ├── main.tsx │ │ ├── models │ │ │ └── index.ts │ │ ├── modules │ │ │ ├── configSlice.ts │ │ │ ├── editClusterSlice.ts │ │ │ ├── global │ │ │ │ └── index.ts │ │ │ ├── insightSlice.ts │ │ │ ├── overviewSlice.ts │ │ │ └── store.ts │ │ ├── pages │ │ │ ├── Cost │ │ │ │ ├── CarbonInsight │ │ │ │ │ ├── Index.tsx │ │ │ │ │ └── components │ │ │ │ │ │ ├── CarbonChart.module.less │ │ │ │ │ │ └── CarbonChart.tsx │ │ │ │ ├── ClusterOverview │ │ │ │ │ ├── ClusterOverviewPanel.tsx │ │ │ │ │ ├── OverviewSearchPanel.tsx │ │ │ │ │ └── PanelWrapper.tsx │ │ │ │ ├── CostsByDimension │ │ │ │ │ ├── CostsByDimensionPanel.tsx │ │ │ │ │ ├── CostsByDimensionSearchPanel.tsx │ │ │ │ │ └── PanelWrapper.tsx │ │ │ │ ├── NamespaceCosts │ │ │ │ │ ├── NamespaceCostSearchPanel.tsx │ │ │ │ │ ├── NamespaceCostsPanel.tsx │ │ │ │ │ └── PanelWrapper.tsx │ │ │ │ ├── WorkloadInsight │ │ │ │ │ ├── InsightSearchPanel.tsx │ │ │ │ │ ├── PanelWrapper.tsx │ │ │ │ │ └── WorkloadInsightPanel.tsx │ │ │ │ └── WorkloadOverview │ │ │ │ │ ├── OverviewSearchPanel.tsx │ │ │ │ │ ├── PanelWrapper.tsx │ │ │ │ │ └── WorkloadOverviewPanel.tsx │ │ │ ├── Dashboard │ │ │ │ └── Base │ │ │ │ │ ├── chart.ts │ │ │ │ │ ├── components │ │ │ │ │ ├── CpuChart.module.less │ │ │ │ │ ├── CpuChart.tsx │ │ │ │ │ ├── MemoryChart.module.less │ │ │ │ │ ├── MemoryChart.tsx │ │ │ │ │ ├── MiddleChart.module.less │ │ │ │ │ ├── MiddleChart.tsx │ │ │ │ │ ├── TopPanel.module.less │ │ │ │ │ └── TopPanel.tsx │ │ │ │ │ ├── constant.ts │ │ │ │ │ └── index.tsx │ │ │ ├── Recommend │ │ │ │ ├── IdleNode │ │ │ │ │ ├── components │ │ │ │ │ │ └── SearchForm.tsx │ │ │ │ │ ├── index.module.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── RecommendationRule │ │ │ │ │ ├── components │ │ │ │ │ │ └── SearchForm.tsx │ │ │ │ │ ├── consts.ts │ │ │ │ │ ├── index.module.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── ReplicaRecommend │ │ │ │ │ ├── components │ │ │ │ │ │ └── SearchForm.tsx │ │ │ │ │ ├── consts.ts │ │ │ │ │ ├── index.module.less │ │ │ │ │ └── index.tsx │ │ │ │ └── ResourcesRecommend │ │ │ │ │ ├── consts.ts │ │ │ │ │ ├── index.module.less │ │ │ │ │ └── index.tsx │ │ │ ├── Result │ │ │ │ ├── 403 │ │ │ │ │ └── index.tsx │ │ │ │ ├── 404 │ │ │ │ │ └── index.tsx │ │ │ │ ├── 500 │ │ │ │ │ └── index.tsx │ │ │ │ ├── BrowserIncompatible │ │ │ │ │ ├── index.module.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── Fail │ │ │ │ │ ├── index.module.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── Maintenance │ │ │ │ │ ├── index.module.less │ │ │ │ │ └── index.tsx │ │ │ │ ├── NetworkError │ │ │ │ │ └── index.tsx │ │ │ │ ├── Success │ │ │ │ │ ├── index.module.less │ │ │ │ │ └── index.tsx │ │ │ │ └── index.module.less │ │ │ └── Settings │ │ │ │ └── cluster │ │ │ │ ├── EditClusterModal.tsx │ │ │ │ ├── OverviewActionPanel.tsx │ │ │ │ ├── OverviewPanel.tsx │ │ │ │ └── OverviewTablePanel.tsx │ │ ├── router │ │ │ ├── index.ts │ │ │ └── modules │ │ │ │ ├── cost.ts │ │ │ │ ├── dashboard.ts │ │ │ │ ├── others.ts │ │ │ │ ├── recommend.ts │ │ │ │ └── settings.ts │ │ ├── services │ │ │ ├── clusterApi.ts │ │ │ ├── grafanaApi.ts │ │ │ ├── namespaceApi.ts │ │ │ ├── prometheusApi.ts │ │ │ ├── recommendationApi.ts │ │ │ ├── recommendationRuleApi.ts │ │ │ └── retryFetchBaseQuery.ts │ │ ├── styles │ │ │ ├── common.module.less │ │ │ ├── index.less │ │ │ └── theme.less │ │ ├── types │ │ │ └── index.d.ts │ │ └── utils │ │ │ ├── chart.ts │ │ │ ├── color.ts │ │ │ ├── copyToClipboard.ts │ │ │ ├── getErrorMsg.ts │ │ │ ├── normalizeNumber.ts │ │ │ ├── path.ts │ │ │ ├── rangeMap.ts │ │ │ ├── request.ts │ │ │ └── transformK8sUnit.ts │ ├── tsconfig.json │ ├── values.yaml │ └── vite.config.js └── webhooks │ ├── analytics │ └── validating.go │ ├── autoscaling │ ├── validate.go │ └── validate_test.go │ ├── config.go │ ├── ensurance │ ├── validate_test.go │ └── validating.go │ ├── pod │ ├── mutating.go │ └── mutating_test.go │ ├── prediction │ └── validating.go │ ├── recommendation │ └── validating.go │ └── webhook.go ├── site ├── .DS_Store ├── Dockerfile ├── README.md ├── assets │ ├── icons │ │ └── logo.svg │ └── scss │ │ └── _variables_project.scss ├── config.toml ├── content │ ├── en │ │ ├── _index.html │ │ ├── blog │ │ │ ├── _index.md │ │ │ └── release-0.7.md │ │ ├── community │ │ │ └── _index.md │ │ ├── docs │ │ │ ├── Best Practices │ │ │ │ ├── _index.md │ │ │ │ ├── effective-hpa-with-prometheus-adapter.md │ │ │ │ ├── how-kujiale-adopt-ehpa.md │ │ │ │ └── how-to-optimize-your-application-resource.md │ │ │ ├── Contributing │ │ │ │ ├── CONTRIBUTING.md │ │ │ │ ├── _index.md │ │ │ │ ├── code-standards.md │ │ │ │ └── developer-guide.md │ │ │ ├── Core Concept │ │ │ │ ├── _index.md │ │ │ │ ├── architecture.md │ │ │ │ ├── resource-optimize-model.md │ │ │ │ └── timeseries-forecasting-by-dsp.md │ │ │ ├── Getting started │ │ │ │ ├── Installation │ │ │ │ │ ├── _index.md │ │ │ │ │ ├── installation-cli-tool.md │ │ │ │ │ ├── installation.md │ │ │ │ │ └── quick-start.md │ │ │ │ ├── _index.md │ │ │ │ └── introduction.md │ │ │ ├── Mirror Resources │ │ │ │ └── _index.md │ │ │ ├── Proposals │ │ │ │ ├── 20220228-advanced-cpuset-manger.md │ │ │ │ ├── 20220402-policy-based-abnomal-detection.md │ │ │ │ ├── 20220706-recommendation-framework.md │ │ │ │ ├── Pod-Sorting-And-Precise-Execution-For-Crane-Agent.md │ │ │ │ └── _index.md │ │ │ ├── Roadmap │ │ │ │ ├── _index.md │ │ │ │ ├── roadmap-2022.md │ │ │ │ └── roadmap-2023.md │ │ │ ├── Tutorials │ │ │ │ ├── Colocation with Enhanced QOS │ │ │ │ │ ├── _index.md │ │ │ │ │ ├── qos-accurately-perform-avoidance-actions.md │ │ │ │ │ ├── qos-customized-metrics-interference-detection-avoidance-and-sorting.md │ │ │ │ │ ├── qos-dynamic-resource-oversold-and-limit.md │ │ │ │ │ ├── qos-enhanced-bypass-cpuset-management.md │ │ │ │ │ ├── qos-interference-detection-and-active-avoidance.md │ │ │ │ │ └── using-qos-ensurance.md │ │ │ │ ├── Recommendation │ │ │ │ │ ├── _index.md │ │ │ │ │ ├── how-to-develop-recommender.md │ │ │ │ │ ├── hpa-recommendation.md │ │ │ │ │ ├── idlenode-recommendation.md │ │ │ │ │ ├── pv-recommendation.md │ │ │ │ │ ├── recommendation-framework.md │ │ │ │ │ ├── replicas-recommendation.md │ │ │ │ │ ├── resource-recommendation.md │ │ │ │ │ └── service-recommendation.md │ │ │ │ ├── _index.md │ │ │ │ ├── dynamic-scheduler-plugin.md │ │ │ │ ├── scheduling-pods-based-on-actual-node-load.md │ │ │ │ ├── using-effective-hpa-to-scaling-with-effectiveness.md │ │ │ │ └── using-time-series-prediction.md │ │ │ └── _index.md │ │ ├── featured-background.jpg │ │ └── search.md │ └── zh │ │ ├── _index.html │ │ ├── blog │ │ ├── _index.md │ │ └── release-0.7.md │ │ ├── community │ │ └── _index.md │ │ ├── docs │ │ ├── Best Practices │ │ │ ├── _index.md │ │ │ ├── effective-hpa-with-prometheus-adapter.md │ │ │ ├── how-kujiale-adopt-ehpa.md │ │ │ └── how-to-optimize-your-application-resource.md │ │ ├── Contributing │ │ │ ├── CONTRIBUTING.md │ │ │ ├── _index.md │ │ │ ├── code-standards.md │ │ │ └── developer-guide.md │ │ ├── Core Concept │ │ │ ├── _index.md │ │ │ ├── architecture.md │ │ │ ├── resource-optimize-model.md │ │ │ └── timeseries-forecasting-by-dsp.md │ │ ├── Getting started │ │ │ ├── Installation │ │ │ │ ├── _index.md │ │ │ │ ├── installation-cli-tool.md │ │ │ │ ├── installation.md │ │ │ │ └── quick-start.md │ │ │ ├── _index.md │ │ │ └── introduction.md │ │ ├── Mirror Resources │ │ │ └── _index.md │ │ ├── Proposals │ │ │ ├── 20220228-advanced-cpuset-manger.md │ │ │ ├── 20220402-policy-based-abnomal-detection.md │ │ │ ├── 20220706-recommendation-framework.md │ │ │ ├── Pod-Sorting-And-Precise-Execution-For-Crane-Agent.md │ │ │ └── _index.md │ │ ├── Roadmap │ │ │ ├── _index.md │ │ │ ├── roadmap-2022.md │ │ │ └── roadmap-2023.md │ │ ├── Tutorials │ │ │ ├── Colocation with Enhanced QOS │ │ │ │ ├── _index.md │ │ │ │ ├── qos-accurately-perform-avoidance-actions.zh.md │ │ │ │ ├── qos-customized-metrics-interference-detection-avoidance-and-sorting.zh.md │ │ │ │ ├── qos-dynamic-resource-oversold-and-limit.zh.md │ │ │ │ ├── qos-enhanced-bypass-cpuset-management.zh.md │ │ │ │ ├── qos-interference-detection-and-active-avoidance.zh.md │ │ │ │ └── using-qos-ensurance.zh.md │ │ │ ├── Recommendation │ │ │ │ ├── _index.md │ │ │ │ ├── how-to-develop-recommender.md │ │ │ │ ├── hpa-recommendation.md │ │ │ │ ├── idlenode-recommendation.md │ │ │ │ ├── pv-recommendation.md │ │ │ │ ├── recommendation-framework.md │ │ │ │ ├── replicas-recommendation.md │ │ │ │ ├── resource-recommendation.md │ │ │ │ └── service-recommendation.md │ │ │ ├── _index.md │ │ │ ├── dynamic-scheduler-plugin.md │ │ │ ├── node-resource-tpolology-scheduler-plugins.md │ │ │ ├── scheduling-pods-based-on-actual-node-load.md │ │ │ ├── using-effective-hpa-to-scaling-with-effectiveness.md │ │ │ └── using-time-series-prediction.md │ │ └── _index.md │ │ ├── featured-background.jpg │ │ └── search.md ├── docker-compose.yaml ├── go.mod ├── go.sum ├── layouts │ ├── 404.html │ └── partials │ │ ├── footer.html │ │ └── helpers │ │ └── katex.html ├── netlify.toml ├── package.json ├── static │ ├── CNAME │ └── images │ │ ├── Crane-FinOps-Certified-Solution.png │ │ ├── add_cluster.png │ │ ├── advanced_cpuset_manager.png │ │ ├── algorithm │ │ └── dsp │ │ │ ├── acf.png │ │ │ ├── dsp.png │ │ │ ├── dsp_debug.png │ │ │ ├── input0.png │ │ │ ├── lft_0_001.png │ │ │ ├── lft_0_01.png │ │ │ ├── linear_regression.png │ │ │ ├── max_value.png │ │ │ ├── missing_data_fill.png │ │ │ ├── remove_outliers.png │ │ │ └── spectrum.png │ │ ├── analytics-arch.png │ │ ├── cpu-usage-water-line.png │ │ ├── crane-arch.png │ │ ├── crane-architecture.png │ │ ├── crane-dashboard.png │ │ ├── crane-ehpa-metrics-chart.png │ │ ├── crane-ehpa-replicas-chart.png │ │ ├── crane-ehpa.png │ │ ├── crane-keda-ali-compare-cron.png │ │ ├── crane-overview.png │ │ ├── crane-qos-ensurance.png │ │ ├── crane.png │ │ ├── crane.svg │ │ ├── crane_recommendation_framework.jpg │ │ ├── dashboard.png │ │ ├── dashboard_nodeport.png │ │ ├── developer-guide │ │ ├── make_all_binaries_result.jpg │ │ ├── make_all_finish.jpg │ │ ├── make_image_docker_images.jpg │ │ ├── make_image_finish.jpg │ │ └── make_image_start.jpg │ │ ├── disablescheduling-example.png │ │ ├── dynamic-scheduler-plugin.png │ │ ├── first_cluster.png │ │ ├── opa.png │ │ ├── recommendation-framework.png │ │ ├── remote-adapter.png │ │ ├── resource-flow.png │ │ ├── resource-model.png │ │ ├── resource-waste.jpg │ │ ├── topology-awareness-architecture.png │ │ ├── topology-awareness-details.png │ │ ├── tsdb.png │ │ ├── users │ │ ├── netease.png │ │ ├── shushu.png │ │ ├── tencent-cloud.png │ │ └── xiaohongshu.png │ │ ├── waterline-construct.png │ │ └── wechat.jpeg └── training.zip └── tools └── initializer ├── Dockerfile ├── podinfo ├── qos-checking.sh └── resource.yaml /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: kind/bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 12 | **Reproduce steps** 13 | 14 | 15 | **Expected behavior** 16 | 17 | **Screenshots** 18 | 19 | **Environment (please complete the following information):** 20 | - K8S Version: [e.g. 1.19] 21 | - Crane Version: [e.g. 0.1.0] 22 | - Browser [e.g. chrome, safari] 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: kind/feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Describe the feature 11 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | #### What type of PR is this? 6 | 7 | 8 | #### What this PR does / why we need it: 9 | 10 | #### Which issue(s) this PR fixes: 11 | 15 | Fixes # 16 | 17 | #### Special notes for your reviewer: 18 | 19 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | documentation: 2 | - docs/* 3 | - docs/**/* 4 | - site/* 5 | - site/**/* 6 | 7 | workflows: 8 | - .github/workflows/* 9 | - .github/workflows/**/* 10 | 11 | deploy: 12 | - deploy/* 13 | - deploy/**/* 14 | 15 | examples: 16 | - examples/* 17 | - examples/**/* 18 | 19 | "area:dashboard": 20 | - pkg/server/* 21 | - pkg/server/**/* 22 | - pkg/web/* 23 | - pkg/web/**/* 24 | 25 | "area:autoscaling": 26 | - pkg/prediction/* 27 | - pkg/prediction/**/* 28 | - pkg/metricprovider/* 29 | - pkg/metricprovider/**/* 30 | - pkg/controller/evpa/* 31 | - pkg/controller/evpa/**/* 32 | - pkg/controller/ehpa/* 33 | - pkg/controller/ehpa/**/* 34 | 35 | "area:algorithm": 36 | - pkg/prediction/* 37 | - pkg/prediction/**/* 38 | - pkg/predictor/* 39 | - pkg/predictor/**/* 40 | - pkg/controller/timeseriesprediction/* 41 | - pkg/controller/timeseriesprediction/**/* 42 | 43 | "area:recommendation": 44 | - pkg/recommend/* 45 | - pkg/recommend/**/* 46 | - pkg/recommendation/* 47 | - pkg/recommendation/**/* 48 | - pkg/controller/analytics/* 49 | - pkg/controller/analytics/**/* 50 | - pkg/controller/recommendation/* 51 | - pkg/controller/recommendation/**/* 52 | -------------------------------------------------------------------------------- /.github/workflows/issue-remove-inactive.yml: -------------------------------------------------------------------------------- 1 | name: Issue Remove Inactive 2 | 3 | on: 4 | issues: 5 | types: [edited] 6 | issue_comment: 7 | types: [created, edited] 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | issue-remove-inactive: 14 | permissions: 15 | issues: write # for actions-cool/issues-helper to update issues 16 | pull-requests: write # for actions-cool/issues-helper to update PRs 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: remove inactive 20 | if: github.event.issue.state == 'open' && github.actor == github.event.issue.user.login 21 | uses: actions-cool/issues-helper@v3 22 | with: 23 | actions: 'remove-labels' 24 | issue-number: ${{ github.event.issue.number }} 25 | labels: 'Inactive, needs-more-info, complete' 26 | -------------------------------------------------------------------------------- /.github/workflows/issues-similarity-analysis.yml: -------------------------------------------------------------------------------- 1 | name: Issues Similarity Analysis 2 | 3 | on: 4 | issues: 5 | types: [opened, edited] 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | similarity-analysis: 12 | permissions: 13 | issues: write # for actions-cool/issues-similarity-analysis to create issue comments 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: analysis 17 | uses: actions-cool/issues-similarity-analysis@v1 18 | with: 19 | filter-threshold: 0.5 20 | title-excludes: '' 21 | comment-title: '### You may look for issues:' 22 | comment-body: '${index}. ${similarity} #${number}' 23 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: "Pull Request Labeler" 2 | on: 3 | - pull_request_target 4 | 5 | jobs: 6 | triage: 7 | permissions: 8 | contents: read 9 | pull-requests: write 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/labeler@v4 13 | with: 14 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 15 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | eslint: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: mrdivyansh/eslint-action@v1.0.7 11 | # GITHUB_TOKEN in forked repositories is read-only 12 | # https://help.github.com/en/actions/reference/events-that-trigger-workflows#pull-request-event-pull_request 13 | if: ${{ github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository }} 14 | with: 15 | repo-token: ${{secrets.GITHUB_TOKEN}} 16 | eslint-rc: /pkg/web/.eslintrc.js 17 | execute-on-files: /pkg/web/src 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | vendor/ 16 | .idea/ 17 | 18 | pkg/web/build 19 | pkg/web/.env 20 | pkg/web/node_modules 21 | 22 | site/resources 23 | site/public 24 | site/.hugo_build.lock 25 | site/node_modules 26 | site/package-lock.json -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 30m 3 | 4 | linters: 5 | disable-all: true 6 | enable: 7 | - deadcode 8 | - unused 9 | - varcheck 10 | - ineffassign 11 | - goimports 12 | - gofmt 13 | - misspell 14 | - unparam 15 | - unconvert 16 | - govet 17 | - errcheck 18 | - structcheck 19 | - staticcheck 20 | 21 | linters-settings: 22 | staticcheck: 23 | go: "1.17" 24 | checks: [ 25 | "all", 26 | "-SA1019", # TODO(fix) Using a deprecated function, variable, constant or field 27 | ] 28 | unused: 29 | go: "1.17" 30 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Crane Community Code of Conduct 2 | 3 | Crane adopts [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG PKGNAME 2 | 3 | # Build the manager binary 4 | FROM golang:1.17.2-alpine as builder 5 | 6 | ARG LDFLAGS 7 | ARG PKGNAME 8 | ARG BUILD 9 | 10 | WORKDIR /go/src/github.com/gocrane/crane 11 | 12 | # Add build deps 13 | RUN apk add build-base 14 | 15 | # Copy the Go Modules manifests 16 | COPY go.mod go.mod 17 | COPY go.sum go.sum 18 | # cache deps before building and copying source so that we don't need to re-download as much 19 | # and so that source changes don't invalidate our downloaded layer 20 | RUN if [[ "${BUILD}" != "CI" ]]; then go env -w GOPROXY=https://goproxy.io,direct; fi 21 | RUN go env 22 | RUN go mod download 23 | 24 | # Copy the go source 25 | COPY pkg pkg/ 26 | COPY cmd cmd/ 27 | 28 | # Build 29 | RUN env 30 | RUN go build -ldflags="${LDFLAGS}" -a -o ${PKGNAME} /go/src/github.com/gocrane/crane/cmd/${PKGNAME}/main.go 31 | FROM alpine:3.13.5 32 | RUN apk add --no-cache tzdata 33 | WORKDIR / 34 | ARG PKGNAME 35 | COPY --from=builder /go/src/github.com/gocrane/crane/${PKGNAME} . 36 | -------------------------------------------------------------------------------- /cmd/crane-agent/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "math/rand" 7 | "os" 8 | "time" 9 | 10 | "github.com/spf13/pflag" 11 | "k8s.io/klog/v2" 12 | "sigs.k8s.io/controller-runtime/pkg/manager/signals" 13 | 14 | "github.com/gocrane/crane/cmd/crane-agent/app" 15 | ) 16 | 17 | // crane-agent main. 18 | func main() { 19 | klog.InitFlags(nil) 20 | pflag.CommandLine.AddGoFlagSet(flag.CommandLine) 21 | rand.Seed(time.Now().UnixNano()) 22 | 23 | ctx := signals.SetupSignalHandler() 24 | 25 | if err := app.NewAgentCommand(ctx).Execute(); err != nil { 26 | fmt.Fprintf(os.Stderr, "%v\n", err) 27 | os.Exit(1) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cmd/craned/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "k8s.io/component-base/logs" 8 | "sigs.k8s.io/controller-runtime/pkg/manager/signals" 9 | 10 | "github.com/gocrane/crane/cmd/craned/app" 11 | ) 12 | 13 | // craned main. 14 | func main() { 15 | logs.InitLogs() 16 | defer logs.FlushLogs() 17 | 18 | ctx := signals.SetupSignalHandler() 19 | 20 | if err := app.NewManagerCommand(ctx).Execute(); err != nil { 21 | fmt.Fprintf(os.Stderr, "%v\n", err) 22 | os.Exit(1) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /deploy/crane-agent/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | app: crane-agent 6 | name: crane-agent 7 | namespace: crane-system 8 | spec: 9 | ports: 10 | - name: health-metric-port 11 | port: 8081 12 | protocol: TCP 13 | targetPort: 8081 14 | selector: 15 | app: crane-agent -------------------------------------------------------------------------------- /deploy/craned/service.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: craned 6 | namespace: crane-system 7 | spec: 8 | ports: 9 | - port: 443 10 | protocol: TCP 11 | targetPort: 9443 12 | name: craned 13 | - port: 8082 14 | protocol: TCP 15 | targetPort: 8082 16 | name: crane-server 17 | - port: 9090 18 | protocol: TCP 19 | targetPort: 9090 20 | name: dashboard-service 21 | - name: metrics-http 22 | port: 8080 23 | protocol: TCP 24 | targetPort: 8080 25 | selector: 26 | app: craned 27 | -------------------------------------------------------------------------------- /deploy/generate-certs.sh: -------------------------------------------------------------------------------- 1 | workdir=`pwd`/deploy 2 | ${workdir}/scripts/gencerts.sh ${workdir} 3 | -------------------------------------------------------------------------------- /deploy/keys/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICnDCCAYQCCQCovCBNnTUHfjANBgkqhkiG9w0BAQsFADAQMQ4wDAYDVQQDDAVj 3 | cmFuZTAeFw0yMjAyMjIxNDMyMTVaFw0zMjAyMjAxNDMyMTVaMBAxDjAMBgNVBAMM 4 | BWNyYW5lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4uCY9tc8bpPq 5 | oklXEl3YvMWOo4n27EeCMmkFj+lA5hlg1mDjteqHWEU3P7I1DlNJ8aw+hGm7hKuC 6 | fQMOlBA2xjsN6//KKn5Nta0uqf6OCIyqWBkBG5uXdPFJ285fSQQKUjxppZja+sJ/ 7 | W8rdZqYW3yx75Ky/dpXUyVT5cnLZSEwwnrh5PyYYCfMiSxwezO53WOY6ae/h6Rkc 8 | 87VjLNKVj7amx2YLVV/3droj1LFjgSH3QMnMY7k/1ymVyc9bl+PqY2hDen4B/9wn 9 | yf112evverI9o2waZOBdTYYqUPQBL32aXAbZSZGOt22OUw2Jt6phgP8eXnk2700Z 10 | LbVyLR6H0QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBSXqAFvYZ8uqGfR2Za5LFs 11 | /xjcWtZYSJiVWT3e1mQYk+ppwG1D5dYsy7t3hZP3PF2wzleOxzeGIHgjwmnsjPZS 12 | jJG+dj1ow8KtIV6X7OGXDilgVrjk9hEaI8rSHYTxjeOe5ryKUVw0DQQpfrH5TUgT 13 | uXzXLwThyLZqyd32i07P0Qpln9IeDUs2GoNKlPNMTqKIybAX6Yo7Qbq1GKOlpmua 14 | IXmOeqbm3CTcQX23iVKOg5Dgtty025dEsk9r2DWoMV6mekJbj0S22wjzkd69H9Rk 15 | STenbymKwmCYQbSwdHgbxZ9UIE0p29BKmGjTuJxwPt4M2HEanpUo6jK5Uuflwgim 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /deploy/keys/tls.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDOzCCAiOgAwIBAgIJAOoH7DJcyRC8MA0GCSqGSIb3DQEBBQUAMBAxDjAMBgNV 3 | BAMMBWNyYW5lMB4XDTIyMDIyMjE0MzIxNVoXDTMyMDIyMDE0MzIxNVowIjEgMB4G 4 | A1UEAwwXY3JhbmVkLmNyYW5lLXN5c3RlbS5zdmMwggEiMA0GCSqGSIb3DQEBAQUA 5 | A4IBDwAwggEKAoIBAQDKCUNsbTOCMQc2/JsY/TUZv4M+3xy8a7+FSJCE182ieIBp 6 | qcSAvJckBV6htm2Q/2rtv3D3X2M/CUbX83wKcklIkmh/hFU+JG6MWJrh0UD3sVcN 7 | nLYCh1Ds54heNWvoMrbgSQfAXMSaFepUM53sXur3pJ3BeBa6CLbJ3JZxWFGIv/mW 8 | FJTTa9/4129MCGf2+O3zh6mHGeepXCHPz45NTJngxCMg7p1i66jz9QDK2OIDGklm 9 | KVhLHVSkE/ULSuVeP8Z+yoTIbwNW+4uRE9+2taOhfeHW2I81lvxTVUeTCnqPkMfL 10 | 85ZJNtNRrz9+3IrgbjFi8RtvC6QuBGd7BGtd5a1RAgMBAAGjgYUwgYIwCQYDVR0T 11 | BAIwADALBgNVHQ8EBAMCBeAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMB 12 | MEkGA1UdEQRCMECCF2NyYW5lZC5jcmFuZS1zeXN0ZW0uc3ZjgiVjcmFuZWQuY3Jh 13 | bmUtc3lzdGVtLnN2Yy5jbHVzdGVyLmxvY2FsMA0GCSqGSIb3DQEBBQUAA4IBAQBY 14 | kAp5gX457l4LZC6B2aK1UjGK4Ejn/Q4nHHTXcFUm/7KYyRy7WvR3fwWwNqM43k1r 15 | b0+Kf8H5oAEyseIyuUanYUD4HcGGWtbM9Jxil8xkWPG/rhBIbHDRGXJJWE245U9W 16 | NpllFj/OlQan9cn+dcj0ds9lGPqsMSe5ryyFjIThxMYkAifRJjM2fLFja1e7nGig 17 | xJfAY+DXmbbA2K7FtvbGeBcMxBlT0PGeQhIeY6ETIP3Qlpg2ymt2iz0/Ur3O9uVb 18 | PPI9a3b5tXmgEC7+50YUXls0eJDfXV/7eY/yIn9Tfcj2DogtpV33rLXz/5/Wqiw9 19 | eALeHCfh8fc8ewDYORKR 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /deploy/metric-adapter/apiservice.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiregistration.k8s.io/v1 2 | kind: APIService 3 | metadata: 4 | name: v1beta1.external.metrics.k8s.io 5 | spec: 6 | service: 7 | name: metric-adapter 8 | namespace: crane-system 9 | group: external.metrics.k8s.io 10 | version: v1beta1 11 | insecureSkipTLSVerify: true 12 | groupPriorityMinimum: 100 13 | versionPriority: 100 -------------------------------------------------------------------------------- /deploy/metric-adapter/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: metric-adapter 5 | namespace: crane-system 6 | spec: 7 | ports: 8 | - name: https 9 | port: 443 10 | targetPort: 6443 11 | - name: http 12 | port: 80 13 | targetPort: 8080 14 | selector: 15 | app: metric-adapter -------------------------------------------------------------------------------- /deploy/scripts/gencerts.sh: -------------------------------------------------------------------------------- 1 | workdir=${1} 2 | keydir=$workdir/keys 3 | mkdir -p $keydir 4 | 5 | echo Generating the CA cert and private key to ${keydir} 6 | openssl req -days 3650 -nodes -new -x509 -keyout ${keydir}/ca.key -out ${keydir}/ca.crt -subj "/CN=crane" 7 | 8 | echo Generating the private key for the webhook server 9 | openssl genrsa -out ${keydir}/tls.key 2048 10 | 11 | # Generate a Certificate Signing Request (CSR) for the private key, and sign it with the private key of the CA. 12 | echo Signing the CSR, and generating cert into ${keydir} 13 | openssl req -new -key ${keydir}/tls.key -subj "/CN=craned.crane-system.svc" -config ${workdir}/scripts/webhook.csr \ 14 | | openssl x509 -req -days 3650 -CA ${keydir}/ca.crt -CAkey ${keydir}/ca.key -CAcreateserial -out ${keydir}/tls.crt -extensions v3_req -extfile ${workdir}/scripts/webhook.csr -------------------------------------------------------------------------------- /deploy/scripts/webhook.csr: -------------------------------------------------------------------------------- 1 | [req] 2 | req_extensions = v3_req 3 | distinguished_name = req_distinguished_name 4 | prompt = no 5 | [req_distinguished_name] 6 | CN = craned.crane-system.svc 7 | [ v3_req ] 8 | basicConstraints = CA:FALSE 9 | keyUsage = nonRepudiation, digitalSignature, keyEncipherment 10 | extendedKeyUsage = clientAuth, serverAuth 11 | subjectAltName = @alt_names 12 | [alt_names] 13 | DNS.1 = craned.crane-system.svc 14 | DNS.2 = craned.crane-system.svc.cluster.local -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | docs.gocrane.io 2 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | The documents here are **deprecated!** Please referring to [https://gocrane.io/](https://gocrane.io/). 2 | -------------------------------------------------------------------------------- /docs/assets/mathjax.js: -------------------------------------------------------------------------------- 1 | window.MathJax = { 2 | tex: { 3 | inlineMath: [["\\(", "\\)"]], 4 | displayMath: [["\\[", "\\]"]], 5 | processEscapes: true, 6 | processEnvironments: true 7 | }, 8 | options: { 9 | ignoreHtmlClass: ".*|", 10 | processHtmlClass: "arithmatex" 11 | } 12 | }; 13 | 14 | document$.subscribe(() => { 15 | MathJax.typesetPromise() 16 | }) 17 | -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_AMS-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_AMS-Regular.woff -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_Calligraphic-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_Calligraphic-Bold.woff -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_Calligraphic-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_Calligraphic-Regular.woff -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_Fraktur-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_Fraktur-Bold.woff -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_Fraktur-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_Fraktur-Regular.woff -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_Main-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_Main-Bold.woff -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_Main-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_Main-Italic.woff -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_Main-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_Main-Regular.woff -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_Math-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_Math-BoldItalic.woff -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_Math-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_Math-Italic.woff -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_Math-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_Math-Regular.woff -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_SansSerif-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_SansSerif-Bold.woff -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_SansSerif-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_SansSerif-Italic.woff -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_SansSerif-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_SansSerif-Regular.woff -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_Script-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_Script-Regular.woff -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_Size1-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_Size1-Regular.woff -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_Size2-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_Size2-Regular.woff -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_Size3-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_Size3-Regular.woff -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_Size4-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_Size4-Regular.woff -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_Typewriter-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_Typewriter-Regular.woff -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_Vector-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_Vector-Bold.woff -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_Vector-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_Vector-Regular.woff -------------------------------------------------------------------------------- /docs/assets/output/chtml/fonts/woff-v2/MathJax_Zero.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/assets/output/chtml/fonts/woff-v2/MathJax_Zero.woff -------------------------------------------------------------------------------- /docs/assets/util.css: -------------------------------------------------------------------------------- 1 | .md-nav__title { 2 | display: none; 3 | } 4 | 5 | .arithmatex { 6 | font-size: 0.85rem; 7 | } 8 | 9 | foreignObject > div { 10 | font-size: 0.85rem; 11 | } -------------------------------------------------------------------------------- /docs/assets/util.js: -------------------------------------------------------------------------------- 1 | setTimeout(function () { 2 | const requestAnimationFrame = window.requestAnimationFrame; 3 | const requestIdleCallback = window.requestIdleCallback; 4 | requestIdleCallback(()=> { 5 | requestAnimationFrame(()=> { 6 | // Remove # when use markdown annotations 7 | const mdAnnotations = document.querySelectorAll(".md-annotation") 8 | for (let i = 0; i < mdAnnotations.length; i++) { 9 | let tmp = mdAnnotations[i] 10 | let parentChinldNodes = tmp.parentElement.childNodes 11 | if (parentChinldNodes[0].data === '#'){ 12 | parentChinldNodes[0].remove() 13 | } 14 | } 15 | 16 | // Handle tab label click 17 | const labels = document.querySelectorAll("div.tabbed-labels > label") 18 | for (let i = 0; i < labels.length; i++) { 19 | let tmp = labels[i] 20 | tmp.onclick = () => { 21 | for (const label of labels) { 22 | if (label.textContent === tmp.textContent) { 23 | label.click() 24 | } 25 | } 26 | } 27 | } 28 | }) 29 | }) 30 | },1) 31 | -------------------------------------------------------------------------------- /docs/images/Crane-FinOps-Certified-Solution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/Crane-FinOps-Certified-Solution.png -------------------------------------------------------------------------------- /docs/images/add_cluster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/add_cluster.png -------------------------------------------------------------------------------- /docs/images/advanced_cpuset_manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/advanced_cpuset_manager.png -------------------------------------------------------------------------------- /docs/images/algorithm/dsp/acf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/algorithm/dsp/acf.png -------------------------------------------------------------------------------- /docs/images/algorithm/dsp/dsp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/algorithm/dsp/dsp.png -------------------------------------------------------------------------------- /docs/images/algorithm/dsp/dsp_debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/algorithm/dsp/dsp_debug.png -------------------------------------------------------------------------------- /docs/images/algorithm/dsp/input0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/algorithm/dsp/input0.png -------------------------------------------------------------------------------- /docs/images/algorithm/dsp/lft_0_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/algorithm/dsp/lft_0_001.png -------------------------------------------------------------------------------- /docs/images/algorithm/dsp/lft_0_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/algorithm/dsp/lft_0_01.png -------------------------------------------------------------------------------- /docs/images/algorithm/dsp/linear_regression.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/algorithm/dsp/linear_regression.png -------------------------------------------------------------------------------- /docs/images/algorithm/dsp/max_value.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/algorithm/dsp/max_value.png -------------------------------------------------------------------------------- /docs/images/algorithm/dsp/missing_data_fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/algorithm/dsp/missing_data_fill.png -------------------------------------------------------------------------------- /docs/images/algorithm/dsp/remove_outliers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/algorithm/dsp/remove_outliers.png -------------------------------------------------------------------------------- /docs/images/algorithm/dsp/spectrum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/algorithm/dsp/spectrum.png -------------------------------------------------------------------------------- /docs/images/analytics-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/analytics-arch.png -------------------------------------------------------------------------------- /docs/images/crane-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/crane-arch.png -------------------------------------------------------------------------------- /docs/images/crane-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/crane-architecture.png -------------------------------------------------------------------------------- /docs/images/crane-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/crane-dashboard.png -------------------------------------------------------------------------------- /docs/images/crane-ehpa-metrics-chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/crane-ehpa-metrics-chart.png -------------------------------------------------------------------------------- /docs/images/crane-ehpa-replicas-chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/crane-ehpa-replicas-chart.png -------------------------------------------------------------------------------- /docs/images/crane-ehpa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/crane-ehpa.png -------------------------------------------------------------------------------- /docs/images/crane-keda-ali-compare-cron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/crane-keda-ali-compare-cron.png -------------------------------------------------------------------------------- /docs/images/crane-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/crane-overview.png -------------------------------------------------------------------------------- /docs/images/crane-qos-ensurance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/crane-qos-ensurance.png -------------------------------------------------------------------------------- /docs/images/crane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/crane.png -------------------------------------------------------------------------------- /docs/images/crane_recommendation_framework.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/crane_recommendation_framework.jpg -------------------------------------------------------------------------------- /docs/images/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/dashboard.png -------------------------------------------------------------------------------- /docs/images/dashboard_nodeport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/dashboard_nodeport.png -------------------------------------------------------------------------------- /docs/images/developer-guide/make_all_binaries_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/developer-guide/make_all_binaries_result.jpg -------------------------------------------------------------------------------- /docs/images/developer-guide/make_all_finish.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/developer-guide/make_all_finish.jpg -------------------------------------------------------------------------------- /docs/images/developer-guide/make_image_docker_images.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/developer-guide/make_image_docker_images.jpg -------------------------------------------------------------------------------- /docs/images/developer-guide/make_image_finish.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/developer-guide/make_image_finish.jpg -------------------------------------------------------------------------------- /docs/images/developer-guide/make_image_start.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/developer-guide/make_image_start.jpg -------------------------------------------------------------------------------- /docs/images/disablescheduling-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/disablescheduling-example.png -------------------------------------------------------------------------------- /docs/images/dynamic-scheduler-plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/dynamic-scheduler-plugin.png -------------------------------------------------------------------------------- /docs/images/first_cluster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/first_cluster.png -------------------------------------------------------------------------------- /docs/images/remote-adapter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/remote-adapter.png -------------------------------------------------------------------------------- /docs/images/wechat.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/images/wechat.jpeg -------------------------------------------------------------------------------- /docs/proposals/cpu-usage-water-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/proposals/cpu-usage-water-line.png -------------------------------------------------------------------------------- /docs/proposals/images/opa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/proposals/images/opa.png -------------------------------------------------------------------------------- /docs/proposals/images/tsdb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/proposals/images/tsdb.png -------------------------------------------------------------------------------- /docs/proposals/waterline-construct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/proposals/waterline-construct.png -------------------------------------------------------------------------------- /docs/slides/crane-introduction.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/docs/slides/crane-introduction.pptx -------------------------------------------------------------------------------- /docs/tutorials/analytics-and-recommendation.zh.md: -------------------------------------------------------------------------------- 1 | # 智能推荐(已废弃),新版请前往:https://gocrane.io/zh-cn/docs/tutorials/recommendation/ 2 | 3 | 智能推荐能够帮助用户自动分析集群并给出优化建议。就像手机助手一样,智能推荐会定期的扫描、分析你的集群并给出推荐建议。目前,我们提供了两种优化能力: 4 | 5 | - [**资源推荐**](resource-recommendation.zh.md): 通过资源推荐的算法分析应用的真实用量推荐更合适的资源配置,您可以参考并采纳它提升集群的资源利用率。 6 | - [**副本数推荐**](replicas-recommendation.zh.md): 通过副本数推荐的算法分析应用的真实用量推荐更合适的副本和 EHPA 配置,您可以参考并采纳它提升集群的资源利用率。 7 | 8 | 应用可以根据资源推荐调整 request 也可以根据副本数推荐调整副本数,这两种优化都能帮助您降低成本,您可以根据您的需求选择采用相应的优化建议。 9 | 10 | ## 架构 11 | 12 | ![analytics-arch](../images/analytics-arch.png) 13 | 14 | ## 一次分析的过程 15 | 16 | 1. 用户创建 Analytics 对象,通过 ResourceSelector 选择需要分析的资源,支持选择多类型(基于Group,Kind,Version)的批量选择 17 | 2. 并行分析每个选择的资源,尝试进行分析推荐,每次分析过程分成筛选和推荐两个阶段: 18 | 1. 筛选:排除不满足推荐条件的资源。比如对于弹性推荐,排除没有 running pod 的 workload 19 | 2. 推荐:通过算法计算分析,给出推荐结果 20 | 3. 如果通过筛选,创建 Recommendation 对象,将推荐结果展示在 Recommendation.Status 21 | 4. 未通过筛选的原因和状态展示在 Analytics.Status 22 | 5. 根据运行间隔等待下次分析 23 | 24 | ## 名词解释 25 | 26 | ### 分析 27 | 28 | 分析定义了一个扫描分析任务。支持两种任务类型:资源推荐和弹性推荐。Crane 定期运行分析任务,并产生推荐结果。 29 | 30 | ### 推荐 31 | 32 | 推荐展示了一个优化推荐的结果。推荐的结果是一段 YAML 配置,根据结果用户可以进行相应的优化动作,比如调整应用的资源配置。 33 | 34 | ### 参数配置 35 | 36 | 不同的分析采用不同的计算模型,Crane 提供了一套默认的计算模型以及一套配套的配置,用户可以通过修改配置来定制推荐的效果。支持修改全局的默认配置和修改单个分析任务的配置。 37 | -------------------------------------------------------------------------------- /docs/tutorials/analytics-and-recommendation.zh_TW.md: -------------------------------------------------------------------------------- 1 | # 智能推薦 2 | 3 | 智能推薦能夠幫助用戶自動分析集群並給出優化建議。就像手機助手一樣,智能推薦會定期的掃描、分析你的集群並給出推薦建議。目前,我們提供了兩種優化能力: 4 | 5 | - [**資源推薦**](resource-recommendation.zh.md): 通過資源推薦的算法分析應用的真實用量推薦更合適的資源配置,您可以參考並採納它提升集群的資源利用率。 6 | - [**副本數推薦**](replicas-recommendation.zh.md): 通過副本數推薦的算法分析應用的真實用量推薦更合適的副本和 EHPA 配置,您可以參考並採納它提升集群的資源利用率。 7 | 8 | 應用可以根據資源推薦調整 request 也可以根據副本數推薦調整副本數,這兩種優化都能幫助您降低成本,您可以根據您的需求選擇採用相應的優化建議。 9 | 10 | ## 架構 11 | 12 | ![analytics-arch](../images/analytics-arch.png) 13 | 14 | ## 一次分析的過程 15 | 16 | 1. 用戶創建 Analytics 對象,通過 ResourceSelector 選擇需要分析的資源,支持選擇多類型(基於Group,Kind,Version)的批量選擇 17 | 2. 並行分析每個選擇的資源,嘗試進行分析推薦,每次分析過程分成篩选和推薦兩個階段: 18 | 1. 篩選:排除不滿足推薦條件的資源。比如對於彈性推薦,排除沒有 running pod 的 workload 19 | 2. 推薦:通過算法計算分析,給出推薦結果 20 | 3. 如果通過篩選,創建 Recommendation 對象,將推薦結果展示在 Recommendation.Status 21 | 4. 未通過篩選的原因和狀態展示在 Analytics.Status 22 | 5. 根據運行間隔等待下次分析 23 | 24 | ## 名詞解釋 25 | 26 | ### 分析 27 | 28 | 分析定義了一個掃描分析任務。支持兩種任務類型:資源推薦和彈性推薦。 Crane 定期運行分析任務,並產生推薦結果。 29 | 30 | ### 推薦 31 | 32 | 推薦展示了一個優化推薦的結果。推薦的結果是一段 YAML 配置,根據結果用戶可以進行相應的優化動作,比如調整應用的資源配置。 33 | 34 | ### 參數配置 35 | 36 | 不同的分析採用不同的計算模型,Crane 提供了一套默認的計算模型以及一套配套的配置,用戶可以通過修改配置來定制推薦的效果。支持修改全局的默認配置和修改單個分析任務的配置。 -------------------------------------------------------------------------------- /docs/tutorials/dynamic-scheduler-plugin.zh.md: -------------------------------------------------------------------------------- 1 | # Dynamic Scheduler:负载感知调度器插件 2 | 3 | ## 介绍 4 | kubernetes 的原生调度器只能通过资源请求来调度 pod,这很容易造成一系列负载不均的问题: 5 | 6 | - 对于某些节点,实际负载与资源请求相差不大,这会导致很大概率出现稳定性问题。 7 | - 对于其他节点来说,实际负载远小于资源请求,这将导致资源的巨大浪费。 8 | 9 | 为了解决这些问题,动态调度器根据实际的节点利用率构建了一个简单但高效的模型,并过滤掉那些负载高的节点来平衡集群。 10 | 11 | ## 设计细节 12 | 13 | ### 架构 14 | ![](/images/dynamic-scheduler-plugin.png) 15 | 16 | 17 | 如上图,动态调度器依赖于`Prometheus`和`Node-exporter`收集和汇总指标数据,它由两个组件组成: 18 | 19 | !!! note "Note" 20 | `Node-annotator` 目前是 `Crane-scheduler-controller`的一个模块. 21 | 22 | - `Node-annotator`定期从 Prometheus 拉取数据,并以注释的形式在节点上用时间戳标记它们。 23 | - `Dynamic plugin`直接从节点的注释中读取负载数据,过滤并基于简单的算法对候选节点进行评分。 24 | 25 | ### 调度策略 26 | 动态调度器提供了一个默认值[调度策略](../deploy/manifests/policy.yaml)并支持用户自定义策略。默认策略依赖于以下指标: 27 | 28 | - `cpu_usage_avg_5m` 29 | - `cpu_usage_max_avg_1h` 30 | - `cpu_usage_max_avg_1d` 31 | - `mem_usage_avg_5m` 32 | - `mem_usage_max_avg_1h` 33 | - `mem_usage_max_avg_1d` 34 | 35 | 在调度的`Filter`阶段,如果该节点的实际使用率大于上述任一指标的阈值,则该节点将被过滤。而在`Score`阶段,最终得分是这些指标值的加权和。 36 | 37 | ### Hot Value 38 | 39 | 在生产集群中,可能会频繁出现调度热点,因为创建 Pod 后节点的负载不能立即增加。因此,我们定义了一个额外的指标,名为`Hot Value`,表示节点最近几次的调度频率。并且节点的最终优先级是最终得分减去`Hot Value`。 40 | -------------------------------------------------------------------------------- /docs/tutorials/dynamic-scheduler-plugin.zh_TW.md: -------------------------------------------------------------------------------- 1 | # Dynamic Scheduler:負載感知調度器插件 2 | 3 | ## 介紹 4 | kubernetes 的原生調度器只能通過資源請求來調度 pod,這很容易造成一系列負載不均的問題: 5 | 6 | - 對於某些節點,實際負載與資源請求相差不大,這會導致很大概率出現穩定性問題。 7 | - 對於其他節點來說,實際負載遠小於資源請求,這將導致資源的巨大浪費。 8 | 9 | 為了解決這些問題,動態調度器根據實際的節點利用率構建了一個簡單但高效的模型,並過濾掉那些負載高的節點來平衡集群。 10 | 11 | ## 設計細節 12 | 13 | ### 架構 14 | ![](./../images/dynamic-scheduler-plugin.png) 15 | 16 | 17 | 如上圖,動態調度器依賴於`Prometheus`和`Node-exporter`收集和匯總指標數據,它由兩個組件組成: 18 | 19 | !!! note "Note" 20 | `Node-annotator` 目前是 `Crane-scheduler-controller`的一個模塊. 21 | 22 | - `Node-annotator`定期從 Prometheus 拉取數據,並以註釋的形式在節點上用時間戳標記它們。 23 | - `Dynamic plugin`直接從節點的註釋中讀取負載數據,過濾並基於簡單的算法對候選節點進行評分。 24 | 25 | ### 調度策略 26 | 動態調度器提供了一個默認值[調度策略](../deploy/manifests/policy.yaml)並支持用戶自定義策略。默認策略依賴於以下指標: 27 | 28 | - `cpu_usage_avg_5m` 29 | - `cpu_usage_max_avg_1h` 30 | - `cpu_usage_max_avg_1d` 31 | - `mem_usage_avg_5m` 32 | - `mem_usage_max_avg_1h` 33 | - `mem_usage_max_avg_1d` 34 | 35 | 在調度的`Filter`階段,如果該節點的實際使用率大於上述任一指標的閾值,則該節點將被過濾。而在`Score`階段,最終得分是這些指標值的加權和。 36 | 37 | ### Hot Value 38 | 39 | 在生產集群中,可能會頻繁出現調度熱點,因為創建 Pod 後節點的負載不能立即增加。因此,我們定義了一個額外的指標,名為`Hot Value`,表示節點最近幾次的調度頻率。並且節點的最終優先級是最終得分減去`Hot Value`。 -------------------------------------------------------------------------------- /docs/tutorials/qos-enhanced-bypass-cpuset-management.zh.md: -------------------------------------------------------------------------------- 1 | ## 增强的旁路cpuset管理能力 2 | kubelet支持static的cpu manager策略,当guaranteed pod运行在节点上时,kebelet会为该pod分配指定的专属cpu,其他进程无法占用,这保证了guaranteed pod的cpu独占,但是也造成了cpu和节点的的利用率较低,造成了一定的浪费。 3 | crane agent为cpuset管理提供了新的策略,允许pod和其他pod共享cpu当其指定了cpu绑核时,可以在利用绑核更少的上下文切换和更高的缓存亲和性的优点的前提下,还能让其他workload部署共用,提升资源利用率。 4 | 5 | 1. 提供了3种pod cpuset类型: 6 | 7 | - exclusive:绑核后其他container不能再使用该cpu,独占cpu 8 | - share:绑核后其他container可以使用该cpu 9 | - none:选择没有被exclusive pod的container占用的cpu,可以使用share类型的绑核 10 | 11 | share类型的绑核策略可以在利用绑核更少的上下文切换和更高的缓存亲和性的优点的前提下,还能让其他workload部署共用,提升资源利用率 12 | 13 | 2. 放宽了kubelet中绑核的限制 14 | 15 | 原先需要所有container的CPU limit与CPU request相等 ,这里只需要任意container的CPU limit大于或等于1且等于CPU request即可为该container设置绑核 16 | 17 | 18 | 3. 支持在pod运行过程中修改pod的 cpuset policy,会立即生效 19 | 20 | pod的cpu manager policy从none转换到share,从exclusive转换到share,均无需重启 21 | 22 | 使用方法: 23 | 1. 设置kubelet的cpuset manager为"none" 24 | 2. 通过pod annotation设置cpu manager policy 25 | 26 | `qos.gocrane.io/cpu-manager: none/exclusive/share` 27 | ```yaml 28 | apiVersion: v1 29 | kind: Pod 30 | metadata: 31 | annotations: 32 | qos.gocrane.io/cpu-manager: none/exclusive/share 33 | ``` -------------------------------------------------------------------------------- /docs/tutorials/using-qos-ensurance.zh.md: -------------------------------------------------------------------------------- 1 | # Qos Ensurance 2 | Qos Ensurance 保证了运行在 Kubernetes 上的 Pod 的稳定性。 3 | 4 | 具有干扰检测和主动回避能力,当较高优先级的 Pod 受到资源竞争的影响时,Disable Schedule、Throttle以及Evict 将应用于低优先级的 Pod,以保证节点整体的稳定, 5 | 目前已经支持节点的cpu/mem负载绝对值/百分比作为水位线,具体可以参考[干扰检测和主动回避](qos-interference-detection-and-active-avoidance.zh.md) 6 | 在发生干扰进行驱逐或压制时,会进行精确计算,将负载降低到略低于水位线即停止操作,防止误伤和过渡操作,具体内容可以参照[精确执行回避动作](qos-accurately-perform-avoidance-actions.zh.md)。 7 | 8 | 同时,crane支持自定义指标适配整个干扰检测框架,只需要完成排序定义等一些操作,即可复用包含精确操作在内的干扰检测和回避流程,具体内容可以参照[定义自己的水位线指标](qos-customized-metrics-interference-detection-avoidance-and-sorting.zh.md)。 9 | 10 | 具有预测算法增强的弹性资源超卖能力,将集群内的空闲资源复用起来,同时结合crane的预测能力,更好地复用闲置资源,当前已经支持cpu和mem的空闲资源回收。同时具有弹性资源限制功能,限制使用弹性资源的workload最大和最小资源使用量,避免对高优业务的影响和饥饿问题。具体内容可以参照[弹性资源超卖和限制](qos-dynamic-resource-oversold-and-limit.zh.md)。 11 | 12 | 同时具备增强的旁路cpuset管理能力,在绑核的同时提升资源利用效率,具体内容可以参照[增强的旁路cpuset管理能力](qos-enhanced-bypass-cpuset-management.zh.md)。 -------------------------------------------------------------------------------- /examples/analytics/config_set.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: analysis.crane.io/v1alpha1 2 | kind: ConfigSet 3 | configs: 4 | - targets: [] 5 | properties: 6 | resource.cpu-request-percentile: "0.98" 7 | replicas.workload-min-replicas: "3" 8 | replicas.pod-min-ready-seconds: "30" 9 | replicas.pod-available-ratio: "0.5" 10 | replicas.cpu-target-utilization: "0.5" 11 | replicas.cpu-percentile: "95" -------------------------------------------------------------------------------- /examples/analytics/nginx-deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: nginx-deployment 5 | labels: 6 | app: nginx 7 | spec: 8 | replicas: 5 9 | selector: 10 | matchLabels: 11 | app: nginx 12 | template: 13 | metadata: 14 | labels: 15 | app: nginx 16 | spec: 17 | containers: 18 | - name: nginx 19 | image: nginx:1.14.2 20 | ports: 21 | - containerPort: 80 22 | resources: 23 | limits: 24 | cpu: "100m" 25 | memory: 500Mi 26 | requests: 27 | cpu: "100m" 28 | memory: 500Mi -------------------------------------------------------------------------------- /examples/analytics/recommendation-configuration.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: analysis.crane.io/v1alpha1 2 | kind: RecommendationConfiguration 3 | recommenders: 4 | - name: Replicas 5 | acceptedResources: 6 | - kind: Deployment 7 | apiVersion: apps/v1 8 | - kind: StatefulSet 9 | apiVersion: apps/v1 10 | - name: Resource 11 | acceptedResources: 12 | - kind: Deployment 13 | apiVersion: apps/v1 14 | - kind: StatefulSet 15 | apiVersion: apps/v1 16 | - name: IdleNode 17 | acceptedResources: 18 | - kind: Node 19 | apiVersion: v1 20 | - name: HPA 21 | acceptedResources: 22 | - kind: Deployment 23 | apiVersion: apps/v1 24 | - kind: StatefulSet 25 | apiVersion: apps/v1 26 | - name: Volume 27 | acceptedResources: 28 | - kind: PersistentVolume 29 | apiVersion: v1 30 | - name: Service 31 | acceptedResources: 32 | - kind: Service 33 | apiVersion: v1 -------------------------------------------------------------------------------- /examples/autoscaling/php-apache.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: php-apache 5 | spec: 6 | selector: 7 | matchLabels: 8 | run: php-apache 9 | replicas: 1 10 | template: 11 | metadata: 12 | labels: 13 | run: php-apache 14 | spec: 15 | containers: 16 | - name: php-apache 17 | image: gocrane/hpa-sample:v1.0.0 18 | ports: 19 | - containerPort: 80 20 | resources: 21 | limits: 22 | cpu: 500m 23 | requests: 24 | cpu: 200m 25 | 26 | --- 27 | 28 | apiVersion: v1 29 | kind: Service 30 | metadata: 31 | name: php-apache 32 | labels: 33 | run: php-apache 34 | spec: 35 | ports: 36 | - port: 80 37 | selector: 38 | run: php-apache -------------------------------------------------------------------------------- /examples/ensurance/disablescheduling-when-ext-cpu-total-distribute.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ensurance.crane.io/v1alpha1 2 | kind: NodeQOS 3 | metadata: 4 | name: "disablescheduling-when-ext-cpu-total-distribute" 5 | labels: 6 | app: "system" 7 | spec: 8 | nodeQualityProbe: 9 | timeoutSeconds: 10 10 | nodeLocalGet: 11 | localCacheTTLSeconds: 60 12 | rules: 13 | - name: "ext_cpu_total_distribute" 14 | avoidanceThreshold: 2 15 | restoreThreshold: 2 16 | actionName: "disablescheduling" 17 | strategy: "None" 18 | metricRule: 19 | name: "ext_cpu_total_distribute" 20 | value: 110 -------------------------------------------------------------------------------- /examples/ensurance/disablescheduling.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ensurance.crane.io/v1alpha1 2 | kind: AvoidanceAction 3 | metadata: 4 | labels: 5 | app: system 6 | name: disablescheduling 7 | spec: 8 | ##coolDownSeconds: 300 9 | description: disable schedule new pods to the node -------------------------------------------------------------------------------- /examples/ensurance/evict-on-cpu-usage-percent/elastic-pod-qos.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ensurance.crane.io/v1alpha1 2 | kind: PodQOS 3 | metadata: 4 | name: all-elastic-pods 5 | spec: 6 | allowedActions: 7 | - eviction 8 | labelSelector: 9 | matchLabels: 10 | preemptible_job: "true" -------------------------------------------------------------------------------- /examples/ensurance/evict-on-cpu-usage-percent/eviction-action.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ensurance.crane.io/v1alpha1 2 | kind: AvoidanceAction 3 | metadata: 4 | name: eviction 5 | spec: 6 | coolDownSeconds: 300 7 | description: evict low priority pods 8 | eviction: 9 | terminationGracePeriodSeconds: 30 -------------------------------------------------------------------------------- /examples/ensurance/evict-on-cpu-usage-percent/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: low 5 | labels: 6 | k8s-app: low 7 | preemptible_job: "true" 8 | spec: 9 | containers: 10 | - command: 11 | - stress-ng 12 | - -c 13 | - "2" 14 | - --cpu-method 15 | - cpuid 16 | image: docker.io/gocrane/stress-ng:v0.12.09 17 | imagePullPolicy: IfNotPresent 18 | name: low 19 | resources: 20 | limits: 21 | gocrane.io/cpu: "2" 22 | gocrane.io/memory: "2000Mi" 23 | requests: 24 | gocrane.io/cpu: "2" 25 | gocrane.io/memory: "2000Mi" 26 | terminationMessagePath: /dev/termination-log 27 | terminationMessagePolicy: File 28 | dnsPolicy: ClusterFirst 29 | restartPolicy: Always 30 | schedulerName: default-scheduler 31 | serviceAccount: default 32 | serviceAccountName: default 33 | terminationGracePeriodSeconds: 30 -------------------------------------------------------------------------------- /examples/ensurance/evict-on-cpu-usage-percent/watermark.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ensurance.crane.io/v1alpha1 2 | kind: NodeQOS 3 | metadata: 4 | name: eviction-on-high-cpu-usage-percent 5 | spec: 6 | nodeQualityProbe: 7 | nodeLocalGet: 8 | localCacheTTLSeconds: 60 9 | timeoutSeconds: 10 10 | rules: 11 | - actionName: eviction 12 | avoidanceThreshold: 2 13 | metricRule: 14 | name: cpu_total_utilization 15 | value: 50 16 | name: cpu-usage-percent 17 | restoreThreshold: 2 18 | strategy: None -------------------------------------------------------------------------------- /examples/ensurance/evict-on-cpu-usage-total/be-rules.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ensurance.crane.io/v1alpha1 2 | kind: PodQOS 3 | metadata: 4 | name: all-be-pods 5 | spec: 6 | allowedActions: 7 | - eviction 8 | scopeSelector: 9 | matchExpressions: 10 | - operator: In 11 | scopeName: QOSClass 12 | values: 13 | - BestEffort 14 | --- 15 | apiVersion: ensurance.crane.io/v1alpha1 16 | kind: NodeQOS 17 | metadata: 18 | name: eviction-on-high-usage 19 | spec: 20 | nodeQualityProbe: 21 | nodeLocalGet: 22 | localCacheTTLSeconds: 60 23 | timeoutSeconds: 10 24 | rules: 25 | - actionName: eviction 26 | avoidanceThreshold: 2 27 | metricRule: 28 | name: cpu_total_usage 29 | value: 5000 30 | name: cpu-usage 31 | restoreThreshold: 2 32 | strategy: None 33 | --- 34 | apiVersion: ensurance.crane.io/v1alpha1 35 | kind: AvoidanceAction 36 | metadata: 37 | name: eviction 38 | spec: 39 | coolDownSeconds: 300 40 | description: evict low priority pods 41 | eviction: 42 | terminationGracePeriodSeconds: 30 -------------------------------------------------------------------------------- /examples/ensurance/evict-on-mem-usage-percent/elastic-pod-qos.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ensurance.crane.io/v1alpha1 2 | kind: PodQOS 3 | metadata: 4 | name: all-elastic-pods 5 | spec: 6 | allowedActions: 7 | - eviction 8 | labelSelector: 9 | matchLabels: 10 | preemptible_job: "true" -------------------------------------------------------------------------------- /examples/ensurance/evict-on-mem-usage-percent/eviction-action.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ensurance.crane.io/v1alpha1 2 | kind: AvoidanceAction 3 | metadata: 4 | name: eviction 5 | spec: 6 | coolDownSeconds: 300 7 | description: evict low priority pods 8 | eviction: 9 | terminationGracePeriodSeconds: 30 -------------------------------------------------------------------------------- /examples/ensurance/evict-on-mem-usage-percent/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: low-pi1 5 | labels: 6 | k8s-app: low 7 | preemptible_job: "true" 8 | spec: 9 | containers: 10 | - image: docker.io/gocrane/stress-ng:v0.12.09 11 | command: 12 | - stress-ng 13 | - --vm-hang 14 | - "3600" 15 | - --vm 16 | - "2" 17 | - --vm-bytes 18 | - "2G" 19 | name: stress 20 | --- 21 | apiVersion: v1 22 | kind: Pod 23 | metadata: 24 | name: low-pi2 25 | labels: 26 | k8s-app: low 27 | preemptible_job: "true" 28 | spec: 29 | containers: 30 | - image: docker.io/gocrane/stress-ng:v0.12.09 31 | command: 32 | - stress-ng 33 | - --vm-hang 34 | - "3600" 35 | - --vm 36 | - "2" 37 | - --vm-bytes 38 | - "3.5G" 39 | name: stress 40 | 41 | -------------------------------------------------------------------------------- /examples/ensurance/evict-on-mem-usage-percent/watermark.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ensurance.crane.io/v1alpha1 2 | kind: NodeQOS 3 | metadata: 4 | name: eviction-on-high-mem-usage-percent 5 | spec: 6 | nodeQualityProbe: 7 | nodeLocalGet: 8 | localCacheTTLSeconds: 60 9 | timeoutSeconds: 10 10 | rules: 11 | - actionName: eviction 12 | avoidanceThreshold: 2 13 | metricRule: 14 | name: memory_total_utilization 15 | value: 50 16 | name: mem-usage-percent 17 | restoreThreshold: 2 18 | strategy: None -------------------------------------------------------------------------------- /examples/ensurance/evict-on-mem-usage-total/elastic-pod-qos.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ensurance.crane.io/v1alpha1 2 | kind: PodQOS 3 | metadata: 4 | name: all-elastic-pods 5 | spec: 6 | allowedActions: 7 | - eviction 8 | labelSelector: 9 | matchLabels: 10 | preemptible_job: "true" -------------------------------------------------------------------------------- /examples/ensurance/evict-on-mem-usage-total/eviction-action.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ensurance.crane.io/v1alpha1 2 | kind: AvoidanceAction 3 | metadata: 4 | name: eviction 5 | spec: 6 | coolDownSeconds: 300 7 | description: evict low priority pods 8 | eviction: 9 | terminationGracePeriodSeconds: 30 -------------------------------------------------------------------------------- /examples/ensurance/evict-on-mem-usage-total/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: low-pi1 5 | labels: 6 | k8s-app: low 7 | preemptible_job: "true" 8 | spec: 9 | containers: 10 | - image: docker.io/gocrane/stress-ng:v0.12.09 11 | command: 12 | - stress-ng 13 | - --vm-hang 14 | - "3600" 15 | - --vm 16 | - "2" 17 | - --vm-bytes 18 | - "2G" 19 | name: stress 20 | --- 21 | apiVersion: v1 22 | kind: Pod 23 | metadata: 24 | name: low-pi2 25 | labels: 26 | k8s-app: low 27 | preemptible_job: "true" 28 | spec: 29 | containers: 30 | - image: docker.io/gocrane/stress-ng:v0.12.09 31 | command: 32 | - stress-ng 33 | - --vm-hang 34 | - "3600" 35 | - --vm 36 | - "2" 37 | - --vm-bytes 38 | - "3.5G" 39 | name: stress 40 | 41 | -------------------------------------------------------------------------------- /examples/ensurance/evict-on-mem-usage-total/watermark.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ensurance.crane.io/v1alpha1 2 | kind: NodeQOS 3 | metadata: 4 | name: eviction-on-high-mem-usage 5 | spec: 6 | nodeQualityProbe: 7 | nodeLocalGet: 8 | localCacheTTLSeconds: 60 9 | timeoutSeconds: 10 10 | rules: 11 | - actionName: eviction 12 | avoidanceThreshold: 2 13 | metricRule: 14 | name: memory_total_usage 15 | value: 5000000000 # means 5Gi 16 | name: mem-usage 17 | restoreThreshold: 2 18 | strategy: None -------------------------------------------------------------------------------- /examples/ensurance/eviction.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ensurance.crane.io/v1alpha1 2 | kind: AvoidanceAction 3 | metadata: 4 | name: eviction 5 | labels: 6 | app: system 7 | spec: 8 | coolDownSeconds: 300 9 | eviction: 10 | terminationGracePeriodSeconds: 30 11 | description: "evict low priority pods" 12 | 13 | -------------------------------------------------------------------------------- /examples/ensurance/low.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scheduling.k8s.io/v1 2 | kind: PriorityClass 3 | metadata: 4 | name: greedy 5 | description: Priority for low level. 6 | value: -100 7 | 8 | --- 9 | 10 | apiVersion: v1 11 | kind: Pod 12 | metadata: 13 | name: low 14 | spec: 15 | containers: 16 | - image: docker.io/gocrane/stress-ng:v0.12.09 17 | imagePullPolicy: Always 18 | name: low 19 | command: 20 | - stress-ng 21 | - -c 22 | - "3" 23 | - --cpu-method 24 | - cpuid 25 | priorityClassName: greedy -------------------------------------------------------------------------------- /examples/ensurance/middle.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scheduling.k8s.io/v1 2 | kind: PriorityClass 3 | metadata: 4 | name: middle 5 | description: Priority for middle level. 6 | value: -1 7 | 8 | --- 9 | 10 | apiVersion: v1 11 | kind: Pod 12 | metadata: 13 | name: middle 14 | spec: 15 | containers: 16 | - image: docker.io/gocrane/stress-ng:v0.12.09 17 | imagePullPolicy: Always 18 | name: middle 19 | command: 20 | - /bin/bash 21 | - -c 22 | - "sleep 36000" 23 | resources: 24 | requests: 25 | memory: 2Gi 26 | cpu: 1 27 | limits: 28 | memory: 15Gi 29 | cpu: 8 30 | priorityClassName: middle 31 | -------------------------------------------------------------------------------- /examples/ensurance/nep-schedule-delay.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ensurance.crane.io/v1alpha1 2 | kind: NodeQOS 3 | metadata: 4 | name: "schedule-delay" 5 | labels: 6 | app: "system" 7 | spec: 8 | nodeQualityProbe: 9 | timeoutSeconds: 10 10 | nodeLocalGet: 11 | localCacheTTLSeconds: 60 12 | rules: 13 | - name: "container-schedule-delay-time" 14 | avoidanceThreshold: 1 15 | restoreThreshold: 10 16 | actionName: "throttle" 17 | strategy: "None" 18 | metricRule: 19 | name: "container_sched_run_queue_time" 20 | value: 500 -------------------------------------------------------------------------------- /examples/ensurance/throttle.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ensurance.crane.io/v1alpha1 2 | kind: AvoidanceAction 3 | metadata: 4 | name: throttle 5 | labels: 6 | app: system 7 | spec: 8 | coolDownSeconds: 300 9 | throttle: 10 | cpuThrottle: 11 | minCPURatio: 10 12 | stepCPURatio: 10 13 | description: "throttle low priority pods" -------------------------------------------------------------------------------- /examples/ensurance/waterline1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ensurance.crane.io/v1alpha1 2 | kind: NodeQOS 3 | metadata: 4 | name: "waterline1" 5 | labels: 6 | app: "system" 7 | spec: 8 | nodeQualityProbe: 9 | timeoutSeconds: 10 10 | nodeLocalGet: 11 | localCacheTTLSeconds: 60 12 | rules: 13 | - name: "cpu-usage" 14 | avoidanceThreshold: 2 15 | restoreThreshold: 2 16 | actionName: "disablescheduling" 17 | strategy: "None" 18 | metricRule: 19 | name: "cpu_total_usage" 20 | value: 4000 21 | -------------------------------------------------------------------------------- /examples/ensurance/waterline2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ensurance.crane.io/v1alpha1 2 | kind: NodeQOS 3 | metadata: 4 | name: "waterline2" 5 | labels: 6 | app: "system" 7 | spec: 8 | nodeQualityProbe: 9 | timeoutSeconds: 10 10 | nodeLocalGet: 11 | localCacheTTLSeconds: 60 12 | rules: 13 | - name: "cpu-usage" 14 | avoidanceThreshold: 2 15 | restoreThreshold: 2 16 | actionName: "throttle" 17 | strategy: "None" 18 | metricRule: 19 | name: "cpu_total_usage" 20 | value: 6000 -------------------------------------------------------------------------------- /examples/ensurance/waterline3.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ensurance.crane.io/v1alpha1 2 | kind: NodeQOS 3 | metadata: 4 | name: "waterline3" 5 | labels: 6 | app: "system" 7 | spec: 8 | nodeQualityProbe: 9 | timeoutSeconds: 10 10 | nodeLocalGet: 11 | localCacheTTLSeconds: 60 12 | rules: 13 | - name: "cpu-usage" 14 | avoidanceThreshold: 2 15 | restoreThreshold: 2 16 | actionName: "eviction" 17 | strategy: "Preview" 18 | metricRule: 19 | name: "cpu_total_usage" 20 | value: 6000 21 | -------------------------------------------------------------------------------- /examples/grafana.conf: -------------------------------------------------------------------------------- 1 | [service] 2 | Scheme= 3 | Host=127.0.0.1:3000 4 | [auth] 5 | APIKey= 6 | Username=admin 7 | Password=admin -------------------------------------------------------------------------------- /examples/load-test.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: autoscaling.crane.io/v1alpha1 2 | kind: EffectiveHorizontalPodAutoscaler 3 | metadata: 4 | name: hpa-load 5 | spec: 6 | scaleTargetRef: 7 | apiVersion: apps/v1 8 | kind: Deployment 9 | name: ahpa-load 10 | minReplicas: 20 11 | maxReplicas: 150 12 | metrics: 13 | - type: Resource 14 | resource: 15 | name: cpu 16 | target: 17 | type: Utilization 18 | averageUtilization: 50 19 | predictionConfig: 20 | predictionWindow: 3600 21 | predictionAlgorithm: 22 | algorithmType: dsp 23 | dsp: 24 | sampleInterval: "60s" 25 | historyLength: "3d" 26 | -------------------------------------------------------------------------------- /examples/noderesource-tsp-template.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | data: 3 | spec: | 4 | predictionMetrics: 5 | - algorithm: 6 | algorithmType: dsp 7 | dsp: 8 | estimators: 9 | fft: 10 | - highFrequencyThreshold: "0.05" 11 | lowAmplitudeThreshold: "1.0" 12 | marginFraction: "0.2" 13 | maxNumOfSpectrumItems: 20 14 | minNumOfSpectrumItems: 10 15 | historyLength: 3d 16 | sampleInterval: 60s 17 | resourceIdentifier: cpu 18 | type: ExpressionQuery 19 | expressionQuery: 20 | expression: 'sum(count(node_cpu_seconds_total{mode="idle",instance=~"({{.metadata.name}})(:\\d+)?"}) by (mode, cpu)) - sum(irate(node_cpu_seconds_total{mode="idle",instance=~"({{.metadata.name}})(:\\d+)?"}[5m]))' 21 | predictionWindowSeconds: 3600 22 | kind: ConfigMap 23 | metadata: 24 | name: noderesource-tsp-template 25 | namespace: default -------------------------------------------------------------------------------- /examples/php-apache.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: php-apache 5 | spec: 6 | selector: 7 | matchLabels: 8 | run: php-apache 9 | replicas: 1 10 | template: 11 | metadata: 12 | labels: 13 | run: php-apache 14 | spec: 15 | containers: 16 | - name: php-apache 17 | image: k8s.gcr.io/hpa-example 18 | ports: 19 | - containerPort: 80 20 | resources: 21 | limits: 22 | cpu: 500m 23 | requests: 24 | cpu: 200m 25 | --- 26 | apiVersion: v1 27 | kind: Service 28 | metadata: 29 | name: php-apache 30 | labels: 31 | run: php-apache 32 | spec: 33 | ports: 34 | - port: 80 35 | selector: 36 | run: php-apache -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ -------------------------------------------------------------------------------- /hack/install-tools.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | GO_MINOR_VERSION=$(go env GOVERSION | cut -c 2- | cut -d' ' -f1 | cut -d'.' -f2) 8 | function go::install() { 9 | set -e 10 | GO_INSTALL_TMP_DIR=$(mktemp -d) 11 | cd "$GO_INSTALL_TMP_DIR" 12 | go mod init tmp 13 | if [ "$GO_MINOR_VERSION" -gt "16" ]; then 14 | GO111MODULE=on go install "$@" 15 | else 16 | GO111MODULE=on go get "$@" 17 | fi 18 | rm -rf "$GO_INSTALL_TMP_DIR" 19 | } 20 | 21 | go::install "$@" 22 | -------------------------------------------------------------------------------- /overrides/main.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block outdated %} 4 | You're not viewing the latest version. 5 | 6 | Click here to go to latest. 7 | 8 | {% endblock %} 9 | -------------------------------------------------------------------------------- /pkg/controller/ehpa/config.go: -------------------------------------------------------------------------------- 1 | package ehpa 2 | 3 | type EhpaControllerConfig struct { 4 | PropagationConfig EhpaControllerPropagationConfig 5 | } 6 | 7 | type EhpaControllerPropagationConfig struct { 8 | LabelPrefixes []string 9 | AnnotationPrefixes []string 10 | Labels []string 11 | Annotations []string 12 | } 13 | -------------------------------------------------------------------------------- /pkg/ensurance/analyzer/evaluator/basic.go: -------------------------------------------------------------------------------- 1 | package evaluator 2 | 3 | type ExpressionEvaluator struct { 4 | } 5 | 6 | func NewExpressionEvaluator() Evaluator { 7 | return &ExpressionEvaluator{} 8 | } 9 | 10 | func (c *ExpressionEvaluator) EvalWithMetric(metricName string, targetValue float64, value float64) bool { 11 | return value > targetValue 12 | } 13 | 14 | func (c *ExpressionEvaluator) EvalWithRawQuery(input string, rule string) bool { 15 | return false 16 | } 17 | -------------------------------------------------------------------------------- /pkg/ensurance/analyzer/evaluator/interface.go: -------------------------------------------------------------------------------- 1 | package evaluator 2 | 3 | type Evaluator interface { 4 | EvalWithMetric(metricName string, targetValue float64, value float64) bool 5 | EvalWithRawQuery(input string, rule string) bool 6 | } 7 | -------------------------------------------------------------------------------- /pkg/ensurance/analyzer/evaluator/opa.go: -------------------------------------------------------------------------------- 1 | package evaluator 2 | 3 | type OpaEvaluator struct { 4 | } 5 | 6 | func NewOpaEvaluator() Evaluator { 7 | return &OpaEvaluator{} 8 | } 9 | 10 | func (c *OpaEvaluator) EvalWithMetric(metricName string, targetValue float64, value float64) bool { 11 | 12 | // step1 splice policy and input strings 13 | // step2 opa.New().WithPolicyBytes 14 | // step3 rego.Eval 15 | // step4 transfer opa.result to bool 16 | // return 17 | 18 | return false 19 | } 20 | 21 | func (c *OpaEvaluator) EvalWithRawQuery(input string, rule string) bool { 22 | return false 23 | } 24 | -------------------------------------------------------------------------------- /pkg/ensurance/collector/cadvisor/types.go: -------------------------------------------------------------------------------- 1 | package cadvisor 2 | 3 | import ( 4 | info "github.com/google/cadvisor/info/v1" 5 | cadvisorapiv2 "github.com/google/cadvisor/info/v2" 6 | ) 7 | 8 | type Manager interface { 9 | GetContainerInfoV2(containerName string, options cadvisorapiv2.RequestOptions) (map[string]cadvisorapiv2.ContainerInfo, error) 10 | GetContainerInfo(containerName string, query *info.ContainerInfoRequest) (*info.ContainerInfo, error) 11 | GetMachineInfo() (*info.MachineInfo, error) 12 | GetCgroupDriver() string 13 | } 14 | -------------------------------------------------------------------------------- /pkg/ensurance/collector/ebpf/ebpf.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/gocrane/crane/pkg/ensurance/collector/types" 7 | 8 | "github.com/gocrane/crane/pkg/common" 9 | ) 10 | 11 | type EBPF struct { 12 | name types.CollectType 13 | StatusCache sync.Map 14 | } 15 | 16 | func NewEBPF() *EBPF { 17 | e := EBPF{ 18 | name: types.EbpfCollectorType, 19 | StatusCache: sync.Map{}, 20 | } 21 | return &e 22 | } 23 | 24 | func (e *EBPF) GetType() types.CollectType { 25 | return e.name 26 | } 27 | 28 | func (e *EBPF) Collect() (map[string][]common.TimeSeries, error) { 29 | return nil, nil 30 | } 31 | 32 | func (e *EBPF) Stop() error { 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /pkg/ensurance/collector/interface.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "github.com/gocrane/crane/pkg/common" 5 | "github.com/gocrane/crane/pkg/ensurance/collector/types" 6 | ) 7 | 8 | type Collector interface { 9 | GetType() types.CollectType 10 | Collect() (map[string][]common.TimeSeries, error) 11 | Stop() error 12 | } 13 | -------------------------------------------------------------------------------- /pkg/ensurance/collector/metricsserver/metricsserver.go: -------------------------------------------------------------------------------- 1 | package metricsserver 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/gocrane/crane/pkg/common" 7 | 8 | "github.com/gocrane/crane/pkg/ensurance/collector/types" 9 | ) 10 | 11 | type MetricsServer struct { 12 | name types.CollectType 13 | statusCache sync.Map 14 | } 15 | 16 | func NewMetricsServer() *MetricsServer { 17 | m := MetricsServer{ 18 | name: types.MetricsServerCollectorType, 19 | statusCache: sync.Map{}, 20 | } 21 | return &m 22 | } 23 | 24 | func (m *MetricsServer) GetType() types.CollectType { 25 | return m.name 26 | } 27 | 28 | func (m *MetricsServer) Collect() (map[string][]common.TimeSeries, error) { 29 | return nil, nil 30 | } 31 | 32 | func (m *MetricsServer) Stop() error { 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /pkg/ensurance/collector/types/constants.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | const ( 4 | MaxPercentage = 100 5 | MinPercentage = 0 6 | 7 | UintConversionStep1024 = 1024.0 8 | UintConversionStep1000 = 1000.0 9 | ) 10 | 11 | const ( 12 | CollectInitErrorText = "collect_init" 13 | ) 14 | -------------------------------------------------------------------------------- /pkg/ensurance/executor/release_resource.go: -------------------------------------------------------------------------------- 1 | package executor 2 | 3 | type ReleaseResource map[WatermarkMetric]float64 4 | 5 | func (r ReleaseResource) Add(new ReleaseResource) { 6 | for metric, value := range new { 7 | if _, ok := r[metric]; !ok { 8 | r[metric] = 0.0 9 | } 10 | r[metric] += value 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /pkg/ensurance/executor/sort/general_sort.go: -------------------------------------------------------------------------------- 1 | package sort 2 | 3 | import "github.com/gocrane/crane/pkg/ensurance/executor/podinfo" 4 | 5 | func GeneralSorter(pods []podinfo.PodContext) { 6 | orderedBy(ComparePriority, ComparePodQOSClass, CompareRunningTime).Sort(pods) 7 | } 8 | -------------------------------------------------------------------------------- /pkg/ensurance/executor/watermark_test.go: -------------------------------------------------------------------------------- 1 | package executor 2 | 3 | import ( 4 | "container/heap" 5 | "strconv" 6 | "testing" 7 | 8 | "k8s.io/apimachinery/pkg/api/resource" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func (w Watermark) verify(t *testing.T, i int) { 14 | t.Helper() 15 | n := w.Len() 16 | j1 := 2*i + 1 17 | j2 := 2*i + 2 18 | if j1 < n { 19 | if w.Less(j1, i) { 20 | t.Errorf("heap invariant invalidated [%d] = %d > [%d] = %d", i, w[i].Value(), j1, w[j1].Value()) 21 | return 22 | } 23 | w.verify(t, j1) 24 | } 25 | if j2 < n { 26 | if w.Less(j2, i) { 27 | t.Errorf("heap invariant invalidated [%d] = %d > [%d] = %d", i, w[i].Value(), j1, w[j2].Value()) 28 | return 29 | } 30 | w.verify(t, j2) 31 | } 32 | } 33 | 34 | // TestPopSmallest make sure that we can get the smallest value 35 | func TestPopSmallest(t *testing.T) { 36 | h := Watermark{} 37 | 38 | for i := 20; i > 0; i-- { 39 | heap.Push(&h, resource.MustParse(strconv.Itoa(i)+"m")) 40 | assert.Equal(t, strconv.Itoa(i)+"m", h.PopSmallest().String()) 41 | } 42 | 43 | h.verify(t, 0) 44 | 45 | t.Logf("watetline is %s", h.String()) 46 | for i := 1; h.Len() > 0; i++ { 47 | heap.Pop(&h) 48 | h.verify(t, 0) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /pkg/ensurance/manager/interface.go: -------------------------------------------------------------------------------- 1 | package manager 2 | 3 | type Manager interface { 4 | Name() string 5 | Run(stop <-chan struct{}) 6 | } 7 | -------------------------------------------------------------------------------- /pkg/ensurance/runtime/runtime_unix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package runtime 5 | 6 | var defaultRuntimeEndpoints = []string{"unix:///var/run/dockershim.sock", "unix:///run/containerd/containerd.sock", "unix:///run/crio/crio.sock", "unix:///run/k3s/containerd/containerd.sock"} 7 | -------------------------------------------------------------------------------- /pkg/known/annotation.go: -------------------------------------------------------------------------------- 1 | package known 2 | 3 | const ( 4 | HPARecommendationValueAnnotation = "analysis.crane.io/hpa-recommendation" 5 | ReplicasRecommendationValueAnnotation = "analysis.crane.io/replicas-recommendation" 6 | ResourceRecommendationValueAnnotation = "analysis.crane.io/resource-recommendation" 7 | RunNumberAnnotation = "analysis.crane.io/run-number" 8 | AnalyticsConversionAnnotation = "analysis.crane.io/analytics-conversion" 9 | LastStartTimeAnnotation = "analysis.crane.io/last-start-time" 10 | MessageAnnotation = "analysis.crane.io/message" 11 | ) 12 | 13 | const ( 14 | EffectiveHorizontalPodAutoscalerCurrentMetricsAnnotation = "autoscaling.crane.io/effective-hpa-current-metrics" 15 | EffectiveHorizontalPodAutoscalerExternalMetricsAnnotationPrefix = "metric-query.autoscaling.crane.io" 16 | ) 17 | -------------------------------------------------------------------------------- /pkg/known/const.go: -------------------------------------------------------------------------------- 1 | package known 2 | 3 | const ( 4 | MetricNamePrediction = "crane_autoscaling_prediction" 5 | MetricNameCron = "crane_autoscaling_cron" 6 | MetricNamePodCpuUsage = "crane_pod_cpu_usage" 7 | ) 8 | 9 | const ( 10 | DefaultCoolDownSeconds = 300 11 | DefaultRestoredThreshold = 1 12 | DefaultAvoidedThreshold = 1 13 | DefaultDeletionGracePeriodSeconds = 30 14 | MaxMinCPURatio = 100 15 | MaxStepCPURatio = 100 16 | ) 17 | -------------------------------------------------------------------------------- /pkg/known/finalizer.go: -------------------------------------------------------------------------------- 1 | package known 2 | 3 | const ( 4 | AutoscalingFinalizer = "autoscaling.crane.io/finalizer" 5 | ) 6 | -------------------------------------------------------------------------------- /pkg/known/label.go: -------------------------------------------------------------------------------- 1 | package known 2 | 3 | const ( 4 | EffectiveHorizontalPodAutoscalerUidLabel = "autoscaling.crane.io/effective-hpa-uid" 5 | EffectiveHorizontalPodAutoscalerManagedBy = "effective-hpa-controller" 6 | ) 7 | 8 | const ( 9 | EnsuranceAnalyzedPressureTaintKey = "interference.crane.io" 10 | EnsuranceAnalyzedPressureConditionKey = "interference-identified" 11 | ) 12 | 13 | const ( 14 | AnalyticsNameLabel = "analysis.crane.io/analytics-name" 15 | AnalyticsUidLabel = "analysis.crane.io/analytics-uid" 16 | AnalyticsTypeLabel = "analysis.crane.io/analytics-type" 17 | ) 18 | 19 | const ( 20 | RecommendationRuleNameLabel = "analysis.crane.io/recommendation-rule-name" 21 | RecommendationRuleUidLabel = "analysis.crane.io/recommendation-rule-uid" 22 | RecommendationRuleRecommenderLabel = "analysis.crane.io/recommendation-rule-recommender" 23 | RecommendationRuleTargetKindLabel = "analysis.crane.io/recommendation-target-kind" 24 | RecommendationRuleTargetVersionLabel = "analysis.crane.io/recommendation-target-version" 25 | RecommendationRuleTargetNameLabel = "analysis.crane.io/recommendation-target-name" 26 | RecommendationRuleTargetNamespaceLabel = "analysis.crane.io/recommendation-target-namespace" 27 | ) 28 | -------------------------------------------------------------------------------- /pkg/known/reason.go: -------------------------------------------------------------------------------- 1 | package known 2 | 3 | const ( 4 | ReasonTimeSeriesPredictFailed = "PredictFailed" 5 | ReasonTimeSeriesPredictPartial = "PredictPartial" 6 | ReasonTimeSeriesPredictSucceed = "PredictSucceed" 7 | ) 8 | -------------------------------------------------------------------------------- /pkg/known/types.go: -------------------------------------------------------------------------------- 1 | package known 2 | 3 | type Module string 4 | 5 | const ( 6 | ModuleAnomalyAnalyzer Module = "AnomalyAnalyzer" 7 | ModuleStateCollector Module = "StateCollector" 8 | ModuleActionExecutor Module = "ActionExecutor" 9 | ModuleNodeResourceManager Module = "ModuleNodeResourceManager" 10 | ModulePodResourceManager Module = "ModulePodResourceManager" 11 | ) 12 | -------------------------------------------------------------------------------- /pkg/known/vars.go: -------------------------------------------------------------------------------- 1 | package known 2 | 3 | import "os" 4 | 5 | var ( 6 | CraneSystemNamespace = "crane-system" 7 | ) 8 | 9 | func init() { 10 | if namespace, ok := os.LookupEnv("CRANE_SYSTEM_NAMESPACE"); ok { 11 | CraneSystemNamespace = namespace 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /pkg/metricprovider/cron_trigger.go: -------------------------------------------------------------------------------- 1 | package metricprovider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/robfig/cron/v3" 9 | ) 10 | 11 | type CronTrigger struct { 12 | Name string 13 | Location *time.Location 14 | Start string 15 | End string 16 | } 17 | 18 | // IsActive return true if the now is in cron trigger schedule start and end, false if else 19 | func (c *CronTrigger) IsActive(ctx context.Context, now time.Time) (bool, error) { 20 | nowWithLocation := now.In(c.Location) 21 | 22 | schedStart, err := cron.ParseStandard(c.Start) 23 | if err != nil { 24 | return false, fmt.Errorf("cron %v unparseable schedule: %v. err: %v", c.Name, c.Start, err) 25 | } 26 | schedEnd, err := cron.ParseStandard(c.End) 27 | if err != nil { 28 | return false, fmt.Errorf("cron %v unparseable schedule: %v. err: %v", c.Name, c.End, err) 29 | } 30 | nextStart := schedStart.Next(nowWithLocation) 31 | nextEnd := schedEnd.Next(nowWithLocation) 32 | 33 | nowTimestamp := nowWithLocation.Unix() 34 | switch { 35 | case nextStart.Unix() < nextEnd.Unix() && nowTimestamp < nextStart.Unix(): 36 | return false, nil 37 | case nowTimestamp <= nextEnd.Unix(): 38 | return true, nil 39 | default: 40 | return false, nil 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /pkg/prediction/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | 8 | predictionapi "github.com/gocrane/api/prediction/v1alpha1" 9 | ) 10 | 11 | const TargetKindNode = "Node" 12 | 13 | func metricSelectorToQueryExpr(m *predictionapi.MetricQuery) string { 14 | conditions := make([]string, 0, len(m.QueryConditions)) 15 | for _, cond := range m.QueryConditions { 16 | values := make([]string, 0, len(cond.Value)) 17 | values = append(values, cond.Value...) 18 | sort.Strings(values) 19 | conditions = append(conditions, fmt.Sprintf("%s%s[%s]", cond.Key, cond.Operator, strings.Join(values, ","))) 20 | } 21 | sort.Strings(conditions) 22 | return fmt.Sprintf("%s{%s}", m.MetricName, strings.Join(conditions, ",")) 23 | } 24 | -------------------------------------------------------------------------------- /pkg/prediction/config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gocrane/api/prediction/v1alpha1" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestMetricSelector_String(t *testing.T) { 11 | m := &v1alpha1.MetricQuery{ 12 | MetricName: "xyz", 13 | QueryConditions: []v1alpha1.QueryCondition{ 14 | {Key: "c", Operator: v1alpha1.OperatorEqual, Value: []string{"3", "2", "1"}}, 15 | {Key: "b", Operator: v1alpha1.OperatorNotEqual, Value: []string{"a"}}, 16 | {Key: "a", Operator: v1alpha1.OperatorRegexMatch, Value: []string{"1.5"}}, 17 | }, 18 | } 19 | assert.Equal(t, "xyz{a=~[1.5],b!=[a],c=[1,2,3]}", metricSelectorToQueryExpr(m)) 20 | } 21 | -------------------------------------------------------------------------------- /pkg/prediction/config/types.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gocrane/api/prediction/v1alpha1" 7 | ) 8 | 9 | type AlgorithmModelConfig struct { 10 | UpdateInterval time.Duration 11 | } 12 | 13 | type ModelInitMode string 14 | 15 | const ( 16 | // means recover or init the algorithm model directly from history datasource, this process may block because it is time consuming for data fetching & model gen 17 | ModelInitModeHistory ModelInitMode = "history" 18 | // means recover or init the algorithm model from real time datasource async, predictor can not do predicting before the data is accumulating to window length 19 | // this is more safe to do some data accumulating and make the prediction data is robust. 20 | ModelInitModeLazyTraining ModelInitMode = "lazytraining" 21 | // means recover or init the model from a checkpoint, it can be restored directly and immediately to do predict. 22 | ModelInitModeCheckpoint ModelInitMode = "checkpoint" 23 | ) 24 | 25 | type Config struct { 26 | InitMode *ModelInitMode 27 | DSP *v1alpha1.DSP 28 | Percentile *v1alpha1.Percentile 29 | } 30 | -------------------------------------------------------------------------------- /pkg/prediction/dsp/aggregate_signal.go: -------------------------------------------------------------------------------- 1 | package dsp 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gocrane/crane/pkg/common" 7 | ) 8 | 9 | type aggregateSignal struct { 10 | predictedTimeSeries *common.TimeSeries 11 | startTime time.Time 12 | endTime time.Time 13 | lastUpdateTime time.Time 14 | } 15 | 16 | func newAggregateSignal() *aggregateSignal { 17 | return &aggregateSignal{} 18 | } 19 | 20 | func (a *aggregateSignal) setPredictedTimeSeries(ts *common.TimeSeries) { 21 | n := len(ts.Samples) 22 | if n > 0 { 23 | a.startTime = time.Unix(ts.Samples[0].Timestamp, 0) 24 | a.endTime = time.Unix(ts.Samples[n-1].Timestamp, 0) 25 | a.predictedTimeSeries = ts 26 | a.lastUpdateTime = time.Now() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pkg/prediction/dsp/auto_correlation.go: -------------------------------------------------------------------------------- 1 | package dsp 2 | 3 | import ( 4 | "math" 5 | "math/cmplx" 6 | 7 | "github.com/mjibson/go-dsp/fft" 8 | "github.com/montanaflynn/stats" 9 | ) 10 | 11 | func AutoCorrelation(samples []float64) []float64 { 12 | N := len(samples) 13 | 14 | if N == 0 { 15 | return []float64{} 16 | } 17 | 18 | x := make([]float64, N) 19 | mean, _ := stats.Mean(samples) 20 | std, _ := stats.StdDevP(samples) 21 | for i := range x { 22 | x[i] = (samples[i] - mean) / std 23 | } 24 | 25 | f := fft.FFTReal(x) 26 | var p []float64 27 | for i := range f { 28 | p = append(p, math.Pow(cmplx.Abs(f[i]), 2)) 29 | } 30 | pi := fft.IFFTReal(p) 31 | 32 | var ac []float64 33 | for i := range pi { 34 | ac = append(ac, real(pi[i])/float64(N)) 35 | } 36 | return ac 37 | } 38 | -------------------------------------------------------------------------------- /pkg/providers/interfaces.go: -------------------------------------------------------------------------------- 1 | package providers 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gocrane/crane/pkg/common" 7 | "github.com/gocrane/crane/pkg/metricnaming" 8 | ) 9 | 10 | // Interface is a source of monitoring metric that provides metrics that can be used for 11 | // prediction, such as 'cpu usage', 'memory footprint', 'request per second (qps)', etc. 12 | type Interface interface { 13 | RealTime 14 | History 15 | } 16 | 17 | // RealTime is a source of monitoring metric that provides data that is streamed data in current time. 18 | type RealTime interface { 19 | // QueryLatestTimeSeries returns the latest metric values that meet the given metricNamer. 20 | QueryLatestTimeSeries(metricNamer metricnaming.MetricNamer) ([]*common.TimeSeries, error) 21 | } 22 | 23 | // History is a data source can provides history time series data at any time periods. 24 | type History interface { 25 | // QueryTimeSeries returns the time series that meet thw given metricNamer. 26 | QueryTimeSeries(metricNamer metricnaming.MetricNamer, startTime time.Time, endTime time.Time, step time.Duration) ([]*common.TimeSeries, error) 27 | } 28 | -------------------------------------------------------------------------------- /pkg/providers/prom/prom_test.go: -------------------------------------------------------------------------------- 1 | package prom 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/gocrane/crane/pkg/providers" 7 | ) 8 | 9 | func TestNewPrometheusClient(t *testing.T) { 10 | config := &providers.PromConfig{ 11 | Address: "", 12 | QueryConcurrency: 10, 13 | BRateLimit: true, 14 | MaxPointsLimitPerTimeSeries: 11000, 15 | } 16 | _, err := NewPrometheusClient(config) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /pkg/querybuilder-providers/grpc/builder.go: -------------------------------------------------------------------------------- 1 | package grpc 2 | 3 | import ( 4 | "github.com/gocrane/crane/pkg/metricquery" 5 | "github.com/gocrane/crane/pkg/querybuilder" 6 | ) 7 | 8 | var _ querybuilder.Builder = &builder{} 9 | 10 | type builder struct { 11 | metric *metricquery.Metric 12 | } 13 | 14 | func NewQueryBuilder(metric *metricquery.Metric) querybuilder.Builder { 15 | return &builder{ 16 | metric: metric, 17 | } 18 | } 19 | 20 | func (b builder) BuildQuery() (*metricquery.Query, error) { 21 | return gRPCQuery(&metricquery.GenericQuery{Metric: b.metric}), nil 22 | } 23 | 24 | func gRPCQuery(query *metricquery.GenericQuery) *metricquery.Query { 25 | return &metricquery.Query{ 26 | Type: metricquery.GrpcMetricSource, 27 | GenericQuery: query, 28 | } 29 | } 30 | 31 | func init() { 32 | querybuilder.RegisterBuilderFactory(metricquery.GrpcMetricSource, NewQueryBuilder) 33 | } 34 | -------------------------------------------------------------------------------- /pkg/querybuilder-providers/metricserver/builder.go: -------------------------------------------------------------------------------- 1 | package metricserver 2 | 3 | import ( 4 | "github.com/gocrane/crane/pkg/metricquery" 5 | "github.com/gocrane/crane/pkg/querybuilder" 6 | ) 7 | 8 | var _ querybuilder.Builder = &builder{} 9 | 10 | type builder struct { 11 | metric *metricquery.Metric 12 | } 13 | 14 | func NewMetricServerQueryBuilder(metric *metricquery.Metric) querybuilder.Builder { 15 | return &builder{ 16 | metric: metric, 17 | } 18 | } 19 | 20 | func (b builder) BuildQuery() (*metricquery.Query, error) { 21 | return metricServerQuery(&metricquery.GenericQuery{Metric: b.metric}), nil 22 | } 23 | 24 | func metricServerQuery(query *metricquery.GenericQuery) *metricquery.Query { 25 | return &metricquery.Query{ 26 | Type: metricquery.MetricServerMetricSource, 27 | GenericQuery: query, 28 | } 29 | } 30 | 31 | func init() { 32 | querybuilder.RegisterBuilderFactory(metricquery.MetricServerMetricSource, NewMetricServerQueryBuilder) 33 | } 34 | -------------------------------------------------------------------------------- /pkg/querybuilder/query.go: -------------------------------------------------------------------------------- 1 | package querybuilder 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/gocrane/crane/pkg/metricquery" 7 | ) 8 | 9 | // Builder is an interface which is used to build query for different data sources according a context info about the query. 10 | type Builder interface { 11 | BuildQuery() (*metricquery.Query, error) 12 | } 13 | 14 | // QueryBuilder is an Builder factory to make Builders 15 | type QueryBuilder interface { 16 | Builder(source metricquery.MetricSource) Builder 17 | } 18 | 19 | var ( 20 | factoryLock sync.Mutex 21 | builderFactory = make(map[metricquery.MetricSource]BuilderFactoryFunc) 22 | ) 23 | 24 | type BuilderFactoryFunc func(metric *metricquery.Metric) Builder 25 | 26 | func RegisterBuilderFactory(metricSource metricquery.MetricSource, initFunc BuilderFactoryFunc) { 27 | factoryLock.Lock() 28 | defer factoryLock.Unlock() 29 | builderFactory[metricSource] = initFunc 30 | } 31 | 32 | func GetBuilderFactory(metricSource metricquery.MetricSource) BuilderFactoryFunc { 33 | factoryLock.Lock() 34 | defer factoryLock.Unlock() 35 | return builderFactory[metricSource] 36 | } 37 | -------------------------------------------------------------------------------- /pkg/recommend/advisor/advisor.go: -------------------------------------------------------------------------------- 1 | package advisor 2 | 3 | import ( 4 | analysisapi "github.com/gocrane/api/analysis/v1alpha1" 5 | 6 | "github.com/gocrane/crane/pkg/recommend/types" 7 | ) 8 | 9 | type Advisor interface { 10 | // Name return name for current Interface 11 | Name() string 12 | 13 | // Advise analysis and give advice in ProposedRecommendation 14 | Advise(proposed *types.ProposedRecommendation) error 15 | } 16 | 17 | func NewAdvisors(ctx *types.Context) (advisors []Advisor) { 18 | switch ctx.Recommendation.Spec.Type { 19 | case analysisapi.AnalysisTypeResource: 20 | advisors = []Advisor{ 21 | &ResourceRequestAdvisor{ 22 | Context: ctx, 23 | }, 24 | } 25 | case analysisapi.AnalysisTypeReplicas: 26 | advisors = []Advisor{ 27 | &ReplicasAdvisor{ 28 | Context: ctx, 29 | }, 30 | } 31 | } 32 | 33 | return 34 | } 35 | -------------------------------------------------------------------------------- /pkg/recommend/inspector/inspector.go: -------------------------------------------------------------------------------- 1 | package inspector 2 | 3 | import ( 4 | analysisapi "github.com/gocrane/api/analysis/v1alpha1" 5 | 6 | "github.com/gocrane/crane/pkg/recommend/types" 7 | ) 8 | 9 | type Inspector interface { 10 | // Name return name for current Interface 11 | Name() string 12 | 13 | // Inspect valid for Context to ensure the target is available for recommendation 14 | Inspect() error 15 | } 16 | 17 | func NewInspectors(ctx *types.Context) []Inspector { 18 | var inspectors []Inspector 19 | 20 | switch ctx.Recommendation.Spec.Type { 21 | case analysisapi.AnalysisTypeResource: 22 | if ctx.Pods != nil { 23 | inspectors = append(inspectors, &ResourceRequestInspector{Context: ctx}) 24 | } 25 | case analysisapi.AnalysisTypeReplicas: 26 | if ctx.Scale != nil { 27 | inspector := &WorkloadInspector{ 28 | Context: ctx, 29 | } 30 | inspectors = append(inspectors, inspector) 31 | } 32 | 33 | if ctx.Pods != nil { 34 | inspector := &WorkloadPodsInspector{ 35 | Pods: ctx.Pods, 36 | Context: ctx, 37 | } 38 | inspectors = append(inspectors, inspector) 39 | } 40 | } 41 | 42 | return inspectors 43 | } 44 | -------------------------------------------------------------------------------- /pkg/recommend/inspector/resource_request.go: -------------------------------------------------------------------------------- 1 | package inspector 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gocrane/crane/pkg/recommend/types" 7 | ) 8 | 9 | type ResourceRequestInspector struct { 10 | *types.Context 11 | } 12 | 13 | func (i *ResourceRequestInspector) Inspect() error { 14 | if len(i.Pods) == 0 { 15 | return fmt.Errorf("pod not found") 16 | } 17 | 18 | pod := i.Pods[0] 19 | if len(pod.OwnerReferences) == 0 { 20 | return fmt.Errorf("owner reference not found") 21 | } 22 | 23 | return nil 24 | } 25 | 26 | func (i *ResourceRequestInspector) Name() string { 27 | return "ResourceRequestInspector" 28 | } 29 | -------------------------------------------------------------------------------- /pkg/recommend/inspector/workload.go: -------------------------------------------------------------------------------- 1 | package inspector 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/gocrane/crane/pkg/recommend/types" 8 | ) 9 | 10 | type WorkloadInspector struct { 11 | Context *types.Context 12 | } 13 | 14 | func (i *WorkloadInspector) Inspect() error { 15 | workloadMinReplicas, err := strconv.ParseInt(i.Context.ConfigProperties["replicas.workload-min-replicas"], 10, 32) 16 | if err != nil { 17 | return err 18 | } 19 | 20 | if i.Context.Scale != nil && i.Context.Scale.Spec.Replicas < int32(workloadMinReplicas) { 21 | return fmt.Errorf("workload replicas %d should be larger than %d ", i.Context.Scale.Spec.Replicas, int32(workloadMinReplicas)) 22 | } 23 | 24 | for _, container := range i.Context.PodTemplate.Spec.Containers { 25 | if container.Resources.Requests.Cpu() == nil { 26 | return fmt.Errorf("container %s resource cpu request is empty ", container.Name) 27 | } 28 | 29 | if container.Resources.Limits.Cpu() == nil { 30 | return fmt.Errorf("container %s resource cpu limit is empty ", container.Name) 31 | } 32 | } 33 | 34 | return nil 35 | } 36 | 37 | func (i *WorkloadInspector) Name() string { 38 | return "WorkloadInspector" 39 | } 40 | -------------------------------------------------------------------------------- /pkg/recommendation/framework/filter.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | // Filter interface 4 | type Filter interface { 5 | // The Filter will filter resource can`t be recommended via target recommender. 6 | Filter(ctx *RecommendationContext) error 7 | } 8 | -------------------------------------------------------------------------------- /pkg/recommendation/framework/observe.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | // Observe interface 4 | type Observe interface { 5 | Observe(ctx *RecommendationContext) error 6 | } 7 | -------------------------------------------------------------------------------- /pkg/recommendation/framework/prepare.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | // PrePrepare interface 4 | type PrePrepare interface { 5 | CheckDataProviders(ctx *RecommendationContext) error 6 | } 7 | 8 | // Prepare interface 9 | type Prepare interface { 10 | CollectData(ctx *RecommendationContext) error 11 | } 12 | 13 | type PostPrepare interface { 14 | PostProcessing(ctx *RecommendationContext) error 15 | } 16 | -------------------------------------------------------------------------------- /pkg/recommendation/framework/recommend.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | // PreRecommend interface 4 | type PreRecommend interface { 5 | PreRecommend(ctx *RecommendationContext) error 6 | } 7 | 8 | // Recommend interface 9 | type Recommend interface { 10 | Recommend(ctx *RecommendationContext) error 11 | } 12 | 13 | // PostRecommend interface 14 | type PostRecommend interface { 15 | Policy(ctx *RecommendationContext) error 16 | } 17 | -------------------------------------------------------------------------------- /pkg/recommendation/recommender/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/pkg/recommendation/recommender/.DS_Store -------------------------------------------------------------------------------- /pkg/recommendation/recommender/apis/recommender.go: -------------------------------------------------------------------------------- 1 | package apis 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | ) 7 | 8 | func (r Recommender) GetConfigFloat(key string, def float64) (float64, error) { 9 | if value, exists := r.Config[key]; exists { 10 | return strconv.ParseFloat(value, 64) 11 | } 12 | return def, nil 13 | } 14 | 15 | func (r Recommender) GetConfigInt(key string, def int64) (int64, error) { 16 | if value, exists := r.Config[key]; exists { 17 | return strconv.ParseInt(value, 10, 64) 18 | } 19 | return def, nil 20 | } 21 | 22 | func (r Recommender) GetConfigBool(key string, def bool) (bool, error) { 23 | if value, exists := r.Config[key]; exists { 24 | return strconv.ParseBool(value) 25 | } 26 | return def, nil 27 | } 28 | 29 | func (r Recommender) GetConfigString(key string, def string) string { 30 | if value, exists := r.Config[key]; exists { 31 | return value 32 | } 33 | return def 34 | } 35 | 36 | func (r Recommender) GetConfigDuration(key string, def time.Duration) (time.Duration, error) { 37 | if value, exists := r.Config[key]; exists { 38 | return time.ParseDuration(value) 39 | } 40 | return def, nil 41 | } 42 | -------------------------------------------------------------------------------- /pkg/recommendation/recommender/base/observe.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "github.com/gocrane/crane/pkg/recommendation/framework" 5 | ) 6 | 7 | // Observe enhance the observability. 8 | func (br *BaseRecommender) Observe(ctx *framework.RecommendationContext) error { 9 | return nil 10 | } 11 | -------------------------------------------------------------------------------- /pkg/recommendation/recommender/base/recommend.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "github.com/montanaflynn/stats" 5 | 6 | "github.com/gocrane/crane/pkg/common" 7 | "github.com/gocrane/crane/pkg/recommendation/framework" 8 | ) 9 | 10 | func (br *BaseRecommender) PreRecommend(ctx *framework.RecommendationContext) error { 11 | return nil 12 | } 13 | 14 | func (br *BaseRecommender) Recommend(ctx *framework.RecommendationContext) error { 15 | return nil 16 | } 17 | 18 | // Policy add some logic for result of recommend phase. 19 | func (br *BaseRecommender) Policy(ctx *framework.RecommendationContext) error { 20 | return nil 21 | } 22 | 23 | func (br *BaseRecommender) GetPercentile(percentile float64, ts []*common.TimeSeries) (float64, error) { 24 | var values stats.Float64Data 25 | for _, ss := range ts[0].Samples { 26 | values = append(values, ss.Value) 27 | } 28 | return stats.Percentile(values, percentile) 29 | } 30 | -------------------------------------------------------------------------------- /pkg/recommendation/recommender/base/registry.go: -------------------------------------------------------------------------------- 1 | package base 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gocrane/crane/pkg/recommendation/recommender" 7 | "github.com/gocrane/crane/pkg/recommendation/recommender/apis" 8 | ) 9 | 10 | var _ recommender.Recommender = &BaseRecommender{} 11 | 12 | const DefaultCreationCoolDown = time.Minute * 3 13 | 14 | type BaseRecommender struct { 15 | apis.Recommender 16 | CreationCoolDown time.Duration 17 | } 18 | 19 | func (br *BaseRecommender) Name() string { 20 | return "" 21 | } 22 | 23 | // NewBaseRecommender create a new base recommender. 24 | func NewBaseRecommender(recommender apis.Recommender) *BaseRecommender { 25 | creationCoolDown, exists := recommender.Config["creation-cooldown"] 26 | creationCoolDownDuration, err := time.ParseDuration(creationCoolDown) 27 | if err != nil || !exists { 28 | creationCoolDownDuration = DefaultCreationCoolDown 29 | } 30 | 31 | return &BaseRecommender{ 32 | recommender, 33 | creationCoolDownDuration, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /pkg/recommendation/recommender/const.go: -------------------------------------------------------------------------------- 1 | package recommender 2 | 3 | const ( 4 | // ReplicasRecommender name 5 | ReplicasRecommender string = "Replicas" 6 | 7 | // ResourceRecommender name 8 | ResourceRecommender string = "Resource" 9 | 10 | // HPARecommender name 11 | HPARecommender string = "HPA" 12 | 13 | // IdleNodeRecommender name 14 | IdleNodeRecommender string = "IdleNode" 15 | 16 | // VolumeRecommender name 17 | VolumeRecommender string = "Volume" 18 | 19 | // ServiceRecommender name 20 | ServiceRecommender string = "Service" 21 | ) 22 | -------------------------------------------------------------------------------- /pkg/recommendation/recommender/hpa/observe.go: -------------------------------------------------------------------------------- 1 | package hpa 2 | 3 | import ( 4 | "github.com/gocrane/crane/pkg/recommendation/framework" 5 | ) 6 | 7 | // Observe enhance the observability. 8 | func (rr *HPARecommender) Observe(ctx *framework.RecommendationContext) error { 9 | return nil 10 | } 11 | -------------------------------------------------------------------------------- /pkg/recommendation/recommender/hpa/prepare.go: -------------------------------------------------------------------------------- 1 | package hpa 2 | 3 | import ( 4 | "github.com/gocrane/crane/pkg/recommendation/framework" 5 | ) 6 | 7 | // CheckDataProviders in PrePrepare phase, will create data source provider via your recommendation config. 8 | func (rr *HPARecommender) CheckDataProviders(ctx *framework.RecommendationContext) error { 9 | return rr.ReplicasRecommender.CheckDataProviders(ctx) 10 | } 11 | 12 | func (rr *HPARecommender) CollectData(ctx *framework.RecommendationContext) error { 13 | return rr.ReplicasRecommender.CollectData(ctx) 14 | } 15 | 16 | func (rr *HPARecommender) PostProcessing(ctx *framework.RecommendationContext) error { 17 | return rr.ReplicasRecommender.PostProcessing(ctx) 18 | } 19 | -------------------------------------------------------------------------------- /pkg/recommendation/recommender/idlenode/filter.go: -------------------------------------------------------------------------------- 1 | package idlenode 2 | 3 | import ( 4 | "github.com/gocrane/crane/pkg/recommendation/framework" 5 | ) 6 | 7 | // Filter out k8s resources that are not supported by the recommender. 8 | func (inr *IdleNodeRecommender) Filter(ctx *framework.RecommendationContext) error { 9 | var err error 10 | 11 | // filter resource that not match objectIdentity 12 | if err = inr.BaseRecommender.Filter(ctx); err != nil { 13 | return err 14 | } 15 | 16 | if err = framework.RetrievePods(ctx); err != nil { 17 | return err 18 | } 19 | 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /pkg/recommendation/recommender/idlenode/observe.go: -------------------------------------------------------------------------------- 1 | package idlenode 2 | 3 | import ( 4 | "github.com/gocrane/crane/pkg/recommendation/framework" 5 | ) 6 | 7 | // Observe enhance the observability. 8 | func (inr *IdleNodeRecommender) Observe(ctx *framework.RecommendationContext) error { 9 | return nil 10 | } 11 | -------------------------------------------------------------------------------- /pkg/recommendation/recommender/interfaces.go: -------------------------------------------------------------------------------- 1 | package recommender 2 | 3 | import ( 4 | "github.com/gocrane/crane/pkg/recommendation/framework" 5 | ) 6 | 7 | type Recommender interface { 8 | Name() string 9 | framework.Filter 10 | framework.PrePrepare 11 | framework.Prepare 12 | framework.PostPrepare 13 | framework.PreRecommend 14 | framework.Recommend 15 | framework.PostRecommend 16 | framework.Observe 17 | } 18 | -------------------------------------------------------------------------------- /pkg/recommendation/recommender/resource/filter.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/gocrane/crane/pkg/recommendation/framework" 7 | ) 8 | 9 | // Filter out k8s resources that are not supported by the recommender. 10 | func (rr *ResourceRecommender) Filter(ctx *framework.RecommendationContext) error { 11 | var err error 12 | 13 | // filter resource that not match objectIdentity 14 | if err = rr.BaseRecommender.Filter(ctx); err != nil { 15 | return err 16 | } 17 | 18 | if err = framework.RetrievePodTemplate(ctx); err != nil { 19 | return err 20 | } 21 | 22 | if err = framework.RetrieveScale(ctx); err != nil { 23 | return err 24 | } 25 | 26 | if err = framework.RetrievePods(ctx); err != nil { 27 | return err 28 | } 29 | 30 | // filter workloads that are downing 31 | if len(ctx.Pods) == 0 { 32 | return fmt.Errorf("pod not found") 33 | } 34 | 35 | pod := ctx.Pods[0] 36 | if len(pod.OwnerReferences) == 0 { 37 | return fmt.Errorf("owner reference not found") 38 | } 39 | 40 | return nil 41 | } 42 | -------------------------------------------------------------------------------- /pkg/recommendation/recommender/resource/prepare.go: -------------------------------------------------------------------------------- 1 | package resource 2 | 3 | import ( 4 | "github.com/gocrane/crane/pkg/recommendation/framework" 5 | ) 6 | 7 | // CheckDataProviders in PrePrepare phase, will create data source provider via your recommendation config. 8 | func (rr *ResourceRecommender) CheckDataProviders(ctx *framework.RecommendationContext) error { 9 | if err := rr.BaseRecommender.CheckDataProviders(ctx); err != nil { 10 | return err 11 | } 12 | 13 | return nil 14 | } 15 | 16 | func (rr *ResourceRecommender) CollectData(ctx *framework.RecommendationContext) error { 17 | return nil 18 | } 19 | 20 | func (rr *ResourceRecommender) PostProcessing(ctx *framework.RecommendationContext) error { 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /pkg/recommendation/recommender/service/observe.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "github.com/gocrane/crane/pkg/recommendation/framework" 5 | ) 6 | 7 | // Observe enhance the observability. 8 | func (s *ServiceRecommender) Observe(ctx *framework.RecommendationContext) error { 9 | return nil 10 | } 11 | -------------------------------------------------------------------------------- /pkg/recommendation/recommender/volume/filter.go: -------------------------------------------------------------------------------- 1 | package volume 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | 6 | "github.com/gocrane/crane/pkg/recommendation/framework" 7 | "github.com/gocrane/crane/pkg/utils" 8 | ) 9 | 10 | // Filter out k8s resources that are not supported by the recommender. 11 | func (vr *VolumeRecommender) Filter(ctx *framework.RecommendationContext) error { 12 | var err error 13 | 14 | // filter resource that not match objectIdentity 15 | if err = vr.BaseRecommender.Filter(ctx); err != nil { 16 | return err 17 | } 18 | 19 | var pv corev1.PersistentVolume 20 | if err = framework.ObjectConversion(ctx.Object, &pv); err != nil { 21 | return err 22 | } 23 | 24 | if pv.Spec.ClaimRef == nil { 25 | return nil 26 | } 27 | 28 | if ctx.Pods, err = utils.GetNamespacePods(ctx.Client, pv.Spec.ClaimRef.Namespace); err != nil { 29 | return err 30 | } 31 | 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /pkg/recommendation/recommender/volume/observe.go: -------------------------------------------------------------------------------- 1 | package volume 2 | 3 | import ( 4 | "github.com/gocrane/crane/pkg/recommendation/framework" 5 | ) 6 | 7 | // Observe enhance the observability. 8 | func (vr *VolumeRecommender) Observe(ctx *framework.RecommendationContext) error { 9 | return nil 10 | } 11 | -------------------------------------------------------------------------------- /pkg/recommendation/recommender/volume/prepare.go: -------------------------------------------------------------------------------- 1 | package volume 2 | 3 | import ( 4 | "github.com/gocrane/crane/pkg/recommendation/framework" 5 | ) 6 | 7 | // CheckDataProviders in PrePrepare phase, will create data source provider via your recommendation config. 8 | func (vr *VolumeRecommender) CheckDataProviders(ctx *framework.RecommendationContext) error { 9 | if err := vr.BaseRecommender.CheckDataProviders(ctx); err != nil { 10 | return err 11 | } 12 | 13 | return nil 14 | } 15 | 16 | func (vr *VolumeRecommender) CollectData(ctx *framework.RecommendationContext) error { 17 | return nil 18 | } 19 | 20 | func (vr *VolumeRecommender) PostProcessing(ctx *framework.RecommendationContext) error { 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /pkg/recommendation/recommender/volume/registry.go: -------------------------------------------------------------------------------- 1 | package volume 2 | 3 | import ( 4 | analysisv1alph1 "github.com/gocrane/api/analysis/v1alpha1" 5 | 6 | "github.com/gocrane/crane/pkg/recommendation/config" 7 | "github.com/gocrane/crane/pkg/recommendation/recommender" 8 | "github.com/gocrane/crane/pkg/recommendation/recommender/apis" 9 | "github.com/gocrane/crane/pkg/recommendation/recommender/base" 10 | ) 11 | 12 | var _ recommender.Recommender = &VolumeRecommender{} 13 | 14 | type VolumeRecommender struct { 15 | base.BaseRecommender 16 | } 17 | 18 | func init() { 19 | recommender.RegisterRecommenderProvider(recommender.VolumeRecommender, NewVolumeRecommender) 20 | } 21 | 22 | func (vr *VolumeRecommender) Name() string { 23 | return recommender.VolumeRecommender 24 | } 25 | 26 | // NewVolumeRecommender create a new Volumes recommender. 27 | func NewVolumeRecommender(recommender apis.Recommender, recommendationRule analysisv1alph1.RecommendationRule) (recommender.Recommender, error) { 28 | recommender = config.MergeRecommenderConfigFromRule(recommender, recommendationRule) 29 | return &VolumeRecommender{ 30 | *base.NewBaseRecommender(recommender), 31 | }, nil 32 | } 33 | -------------------------------------------------------------------------------- /pkg/server/ginwrapper/core.go: -------------------------------------------------------------------------------- 1 | package ginwrapper 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | type Response struct { 10 | // Message is the detail message of the error 11 | Error string `json:"error"` 12 | // Data is the response data 13 | Data interface{} `json:"data"` 14 | } 15 | 16 | // WriteResponse write an error or the response data into http response body. 17 | // The Response.Error is empty if the err is null, or the Response.Error is the error message. 18 | func WriteResponse(c *gin.Context, err error, data interface{}) { 19 | if err != nil { 20 | c.JSON(http.StatusOK, Response{ 21 | Error: err.Error(), 22 | Data: data, 23 | }) 24 | return 25 | } 26 | 27 | c.JSON(http.StatusOK, Response{ 28 | Data: data, 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/server/middleware/cors.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gin-contrib/cors" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | const ( 11 | maxAge = 24 12 | ) 13 | 14 | // Cors add cors headers. 15 | func Cors() gin.HandlerFunc { 16 | return cors.New(cors.Config{ 17 | AllowOrigins: []string{"*"}, 18 | AllowMethods: []string{"POST", "DELETE", "PUT", "PATCH", "GET", "OPTIONS"}, 19 | AllowHeaders: []string{"Origin", "Authorization", "Content-Type", "Accept"}, 20 | ExposeHeaders: []string{"Content-Length"}, 21 | AllowCredentials: true, 22 | MaxAge: maxAge * time.Hour, 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /pkg/server/middleware/log.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gin-gonic/gin" 7 | "k8s.io/klog/v2" 8 | ) 9 | 10 | func Logger() gin.HandlerFunc { 11 | return func(c *gin.Context) { 12 | start := time.Now() 13 | c.Next() 14 | end := time.Now() 15 | klog.V(6).Infof("| %3d | %13v | %15s | %s %s |", c.Writer.Status(), end.Sub(start), c.ClientIP(), c.Request.Method, c.Request.URL.Path) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /pkg/server/middleware/middleware.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | // Middlewares store registered middlewares. 8 | var Middlewares = defaultMiddlewares() 9 | 10 | func defaultMiddlewares() map[string]gin.HandlerFunc { 11 | return map[string]gin.HandlerFunc{ 12 | "log": Logger(), 13 | "cors": Cors(), 14 | "recovery": gin.Recovery(), 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /pkg/server/service/dashboard/dashboard_test.go: -------------------------------------------------------------------------------- 1 | package dashboard 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestBuildEmbeddingLink(t *testing.T) { 8 | testCases := []struct { 9 | desc string 10 | scheme string 11 | host string 12 | boardurl string 13 | useRange bool 14 | from, to int64 15 | panel uint 16 | wanted string 17 | }{ 18 | { 19 | desc: "1", 20 | scheme: "http", 21 | host: "127.0.0.1:3000", 22 | boardurl: "/d/xxx", 23 | useRange: true, 24 | from: 123, 25 | to: 111, 26 | panel: 1, 27 | wanted: "http://127.0.0.1:3000/d/xxx?from=123&to=111&viewPanel=1", 28 | }, 29 | { 30 | desc: "2", 31 | scheme: "http", 32 | host: "127.0.0.1:3000", 33 | boardurl: "/d/xxx", 34 | useRange: false, 35 | from: 123, 36 | to: 111, 37 | panel: 1, 38 | wanted: "http://127.0.0.1:3000/d/xxx?viewPanel=1", 39 | }, 40 | } 41 | for _, tc := range testCases { 42 | gotLink := BuildEmbeddingLink(tc.scheme, tc.host, tc.boardurl, tc.useRange, tc.from, tc.to, tc.panel) 43 | if gotLink != tc.wanted { 44 | t.Fatalf("tc %v, got %v, wanted: %v", tc.desc, gotLink, tc.wanted) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /pkg/utils/cpuset.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | topologyapi "github.com/gocrane/api/topology/v1alpha1" 5 | corev1 "k8s.io/api/core/v1" 6 | "k8s.io/kubernetes/pkg/kubelet/cm/cpuset" 7 | ) 8 | 9 | // GetReservedCPUs ... 10 | func GetReservedCPUs(cpus string) (cpuset.CPUSet, error) { 11 | emptyCPUSet := cpuset.NewCPUSet() 12 | if cpus == "" { 13 | return emptyCPUSet, nil 14 | } 15 | return cpuset.Parse(cpus) 16 | } 17 | 18 | // PodExcludeReservedCPUs ... 19 | func PodExcludeReservedCPUs(pod *corev1.Pod) bool { 20 | if pod == nil { 21 | return false 22 | } 23 | return pod.Annotations[topologyapi.AnnotationPodExcludeReservedCPUs] == "true" 24 | } 25 | -------------------------------------------------------------------------------- /pkg/utils/labels.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | "k8s.io/apimachinery/pkg/labels" 6 | ) 7 | 8 | func LabelSelectorMatched(maps map[string]string, selector *metav1.LabelSelector) (bool, error) { 9 | if selector == nil { 10 | return true, nil 11 | } 12 | 13 | ls, err := metav1.LabelSelectorAsSelector(selector) 14 | if err != nil { 15 | return false, err 16 | } 17 | 18 | return ls.Matches(labels.Set(maps)), nil 19 | } 20 | 21 | // ContainMaps to judge the maps b is contained by maps a 22 | func ContainMaps(a map[string]string, b map[string]string) bool { 23 | for k, v := range b { 24 | if vv, ok := a[k]; !ok { 25 | return false 26 | } else { 27 | if vv != v { 28 | return false 29 | } 30 | } 31 | } 32 | return true 33 | } 34 | -------------------------------------------------------------------------------- /pkg/utils/ref.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "strings" 5 | 6 | v1 "k8s.io/api/core/v1" 7 | "k8s.io/apimachinery/pkg/types" 8 | ) 9 | 10 | const ( 11 | CgroupKubePods = "kubepods" 12 | CgroupPodPrefix = "pod" 13 | ) 14 | 15 | func GetNodeRef(nodeName string) *v1.ObjectReference { 16 | return &v1.ObjectReference{ 17 | Kind: "Node", 18 | Name: nodeName, 19 | UID: types.UID(nodeName), 20 | Namespace: "", 21 | } 22 | } 23 | 24 | func GetContainerIdFromKey(key string) string { 25 | subPaths := strings.Split(key, "/") 26 | 27 | if len(subPaths) > 0 { 28 | // if the latest sub path is pod-xxx-xxx, we regard as it pod path 29 | // if not we used the latest sub path as the containerId 30 | if strings.HasPrefix(subPaths[len(subPaths)-1], CgroupPodPrefix) { 31 | return "" 32 | } else { 33 | return subPaths[len(subPaths)-1] 34 | } 35 | } 36 | 37 | return "" 38 | } 39 | -------------------------------------------------------------------------------- /pkg/utils/ref_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "testing" 4 | 5 | type KeyTest struct { 6 | input string 7 | output string 8 | } 9 | 10 | func TestGetContainerIdFromKey(t *testing.T) { 11 | var cases = []KeyTest{ 12 | { 13 | input: "/kubepods/besteffort/pod04e5e9e7-8d95-44dd-9af7-ab944405fff8/18b514fc91ecb19b7ee79ebeaa6f2df86c6c939e420520b97ad4f7532582d35a", 14 | output: "18b514fc91ecb19b7ee79ebeaa6f2df86c6c939e420520b97ad4f7532582d35a", 15 | }, 16 | { 17 | input: "/kubepods/besteffort/pod04e5e9e7-8d95-44dd-9af7-ab944405fff8", 18 | output: "", 19 | }, 20 | { 21 | input: "/kubepods/besteffort/pod04e5e9e7-8d95-44dd-9af7-ab944405fff8/2cc2c4badac0618edda11bdd06826e7385b885ca88323b6f5d90270395e039d9", 22 | output: "2cc2c4badac0618edda11bdd06826e7385b885ca88323b6f5d90270395e039d9", 23 | }, 24 | } 25 | 26 | for _, c := range cases { 27 | if r := GetContainerIdFromKey(c.input); r != c.output { 28 | t.Fatalf("TestGetContainerIdFromKey failed {%s,%s}, r: %s", c.input, c.output, r) 29 | } 30 | } 31 | 32 | return 33 | } 34 | -------------------------------------------------------------------------------- /pkg/utils/schema.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "k8s.io/apimachinery/pkg/api/meta" 5 | "k8s.io/apimachinery/pkg/runtime/schema" 6 | ) 7 | 8 | func KindForResource(resource string, restMapper meta.RESTMapper) (string, error) { 9 | singular, err := restMapper.ResourceSingularizer(resource) 10 | if err != nil { 11 | return "", err 12 | } 13 | fullySpecifiedGVR, groupResource := schema.ParseResourceArg(singular) 14 | gvk := schema.GroupVersionKind{} 15 | if fullySpecifiedGVR != nil { 16 | gvk, err = restMapper.KindFor(*fullySpecifiedGVR) 17 | } 18 | if gvk.Empty() { 19 | gvk, err = restMapper.KindFor(groupResource.WithVersion("")) 20 | } 21 | 22 | return gvk.Kind, err 23 | } 24 | -------------------------------------------------------------------------------- /pkg/utils/slice.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | func ContainsString(slice []string, str string) bool { 4 | for _, s := range slice { 5 | if s == str { 6 | return true 7 | } 8 | } 9 | return false 10 | } 11 | 12 | func RemoveString(slice []string, str string) []string { 13 | if len(slice) == 0 { 14 | return slice 15 | } 16 | var newSlice []string 17 | for _, item := range slice { 18 | if item != str { 19 | newSlice = append(newSlice, item) 20 | } 21 | } 22 | return newSlice 23 | } 24 | -------------------------------------------------------------------------------- /pkg/utils/string.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | ) 7 | 8 | func ParseFloat(str string, defaultValue float64) (float64, error) { 9 | if len(str) == 0 { 10 | return defaultValue, nil 11 | } 12 | return strconv.ParseFloat(str, 64) 13 | } 14 | 15 | // parsePercentage parse the percent string value 16 | func ParsePercentage(input string) (float64, error) { 17 | if len(input) == 0 { 18 | return 0, nil 19 | } 20 | value, err := strconv.ParseFloat(strings.TrimRight(input, "%"), 64) 21 | if err != nil { 22 | return 0, err 23 | } 24 | return value / 100, nil 25 | } 26 | -------------------------------------------------------------------------------- /pkg/utils/time.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "regexp" 5 | "strconv" 6 | "time" 7 | ) 8 | 9 | // ParseDuration parse a string to time.Duration 10 | func ParseDuration(s string) (time.Duration, error) { 11 | if matched, _ := regexp.MatchString("(\\d+)d", s); matched { 12 | if nDays, err := strconv.ParseInt(s[:len(s)-1], 10, 64); err == nil { 13 | return time.Hour * 24 * time.Duration(nDays), nil 14 | } 15 | } 16 | return time.ParseDuration(s) 17 | } 18 | 19 | // ParseTimestamp parse a string to time.Time 20 | func ParseTimestamp(ts string) (time.Time, error) { 21 | i, err := strconv.ParseInt(ts, 10, 64) 22 | if err != nil { 23 | return time.Now(), err 24 | } 25 | return time.Unix(i, 0), nil 26 | } 27 | 28 | func NowUTC() time.Time { 29 | now := time.Now() 30 | loc, _ := time.LoadLocation("UTC") 31 | return now.In(loc) 32 | } 33 | -------------------------------------------------------------------------------- /pkg/utils/timestamp.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gocrane/crane/pkg/common" 7 | ) 8 | 9 | func DetectTimestampCompletion(tsList []*common.TimeSeries, historyLength string, timeNow time.Time) (bool, int, error) { 10 | historyLengthDuration, err := ParseDuration(historyLength) 11 | if err != nil { 12 | return false, 0, err 13 | } 14 | end := timeNow.Truncate(time.Minute) 15 | days := int(historyLengthDuration.Hours() / 24) 16 | if days < 1 { 17 | days = 1 18 | } 19 | daysExistence := make(map[int]bool, days) 20 | 21 | for _, sample := range tsList[0].Samples { 22 | dayDifference := int((end.Unix() - sample.Timestamp) / (24 * 60 * 60)) 23 | if 0 <= dayDifference && dayDifference <= days { 24 | daysExistence[dayDifference] = true 25 | } 26 | } 27 | 28 | existDays := 0 29 | for _, exists := range daysExistence { 30 | if exists { 31 | existDays++ 32 | } 33 | } 34 | 35 | return existDays >= (days - 1), existDays, nil // stretch the checking to tolerate some missing data. 36 | } 37 | -------------------------------------------------------------------------------- /pkg/utils/vpa.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "context" 5 | 6 | corev1 "k8s.io/api/core/v1" 7 | "sigs.k8s.io/controller-runtime/pkg/client" 8 | 9 | autoscalingapi "github.com/gocrane/api/autoscaling/v1alpha1" 10 | ) 11 | 12 | func GetEVPAFromScaleTarget(context context.Context, kubeClient client.Client, namespace string, objRef corev1.ObjectReference) (*autoscalingapi.EffectiveVerticalPodAutoscaler, error) { 13 | evpaList := &autoscalingapi.EffectiveVerticalPodAutoscalerList{} 14 | opts := []client.ListOption{ 15 | client.InNamespace(namespace), 16 | } 17 | err := kubeClient.List(context, evpaList, opts...) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | for _, evpa := range evpaList.Items { 23 | if evpa.Spec.TargetRef.Name == objRef.Name && 24 | evpa.Spec.TargetRef.Kind == objRef.Kind && 25 | evpa.Spec.TargetRef.APIVersion == objRef.APIVersion { 26 | return &evpa, nil 27 | } 28 | } 29 | 30 | return nil, nil 31 | } 32 | -------------------------------------------------------------------------------- /pkg/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "runtime" 7 | ) 8 | 9 | var ( 10 | gitVersion = "crane-%s" 11 | gitCommit = "$Format:%H$" // sha1 from git, output of $(git rev-parse HEAD) 12 | gitTreeState = "" // state of git tree, either "clean" or "dirty" 13 | gitTag = "" 14 | buildDate = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ') 15 | ) 16 | 17 | type Version struct { 18 | GitVersion string `json:"gitVersion"` 19 | GitCommit string `json:"gitCommit"` 20 | BuildDate string `json:"buildDate"` 21 | GoVersion string `json:"goVersion"` 22 | Compiler string `json:"compiler"` 23 | Platform string `json:"platform"` 24 | GitTreeState string `json:"gitTreeState"` 25 | } 26 | 27 | func GetVersionInfo() string { 28 | ver := Version{ 29 | GitVersion: fmt.Sprintf(gitVersion, gitTag), 30 | GitCommit: gitCommit, 31 | BuildDate: buildDate, 32 | GoVersion: runtime.Version(), 33 | Compiler: runtime.Compiler, 34 | Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), 35 | GitTreeState: gitTreeState, 36 | } 37 | res, _ := json.Marshal(ver) 38 | return string(res) 39 | } 40 | -------------------------------------------------------------------------------- /pkg/web/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | .git -------------------------------------------------------------------------------- /pkg/web/.editorconfig: -------------------------------------------------------------------------------- 1 | # indicate this is the root of the project 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /pkg/web/.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | mock 3 | config 4 | public 5 | scripts 6 | -------------------------------------------------------------------------------- /pkg/web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /coverage 6 | 7 | # production 8 | /dist 9 | 10 | # misc 11 | .DS_Store 12 | .idea 13 | .VSCodeCounter 14 | 15 | # log 16 | npm-debug.log* 17 | yarn-debug.log* 18 | yarn-error.log* 19 | 20 | 21 | 22 | # code editor setting 23 | /.vscode 24 | -------------------------------------------------------------------------------- /pkg/web/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 120, 3 | tabWidth: 2, 4 | useTabs: false, 5 | semi: true, 6 | singleQuote: true, 7 | quoteProps: 'as-needed', 8 | jsxSingleQuote: true, 9 | trailingComma: 'all', 10 | bracketSpacing: true, 11 | jsxBracketSameLine: false, 12 | arrowParens: 'always', 13 | rangeStart: 0, 14 | rangeEnd: null, 15 | requirePragma: false, 16 | insertPragma: false, 17 | proseWrap: 'preserve', 18 | htmlWhitespaceSensitivity: 'css', 19 | vueIndentScriptAndStyle: false, 20 | endOfLine: 'lf', 21 | embeddedLanguageFormatting: 'auto', 22 | }; 23 | -------------------------------------------------------------------------------- /pkg/web/Dockerfile: -------------------------------------------------------------------------------- 1 | # -- BUILD -- 2 | FROM node:14.17-alpine as build 3 | 4 | WORKDIR /usr/src/app 5 | 6 | COPY package* ./ 7 | COPY . . 8 | 9 | RUN npm i 10 | RUN npm run build 11 | 12 | # -- RELEASE -- 13 | FROM nginx:stable-alpine as release 14 | 15 | COPY --from=build /usr/src/app/dist /usr/share/nginx/html 16 | 17 | WORKDIR /usr/share/nginx/html 18 | EXPOSE 9090 19 | 20 | CMD ["/bin/sh", "-c", "nginx -g \"daemon off;\""] 21 | -------------------------------------------------------------------------------- /pkg/web/i18next-scanner.config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | module.exports = { 4 | input: ['src/**/*.{js,jsx,ts,tsx}', '!**/node_modules/**', '!src/**/*.test.{ts,tsx}'], 5 | output: './', 6 | options: { 7 | debug: true, 8 | func: { 9 | list: ['t', 'i18n.t'], 10 | extensions: ['.js', '.jsx', '.ts', '.tsx'], 11 | }, 12 | lngs: ['zh'], 13 | ns: ['translation'], 14 | defaultLng: 'zh', 15 | defaultNs: 'translation', 16 | resource: { 17 | loadPath: 'src/i18n/resources/{{lng}}/{{ns}}.json', 18 | savePath: 'src/i18n/resources/{{lng}}/{{ns}}.json', 19 | jsonIndent: 2, 20 | lineEnding: '\n', 21 | }, 22 | nsSeparator: false, // namespace separator 23 | keySeparator: false, // key separator 24 | interpolation: { 25 | prefix: '{{', 26 | suffix: '}}', 27 | }, 28 | }, 29 | transform: function customTransform(file, enc, done) { 30 | const { parser } = this; 31 | const content = fs.readFileSync(file.path, enc); 32 | 33 | parser.parseFuncFromString(content, (key, options) => { 34 | options.defaultValue = key; 35 | parser.set(key, options); 36 | }); 37 | 38 | done(); 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /pkg/web/mock/index.js: -------------------------------------------------------------------------------- 1 | export default []; 2 | -------------------------------------------------------------------------------- /pkg/web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/pkg/web/public/favicon.ico -------------------------------------------------------------------------------- /pkg/web/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /pkg/web/src/assets/svg/assets-setting-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /pkg/web/src/components/DatePicker/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { DateRangePicker, DateRangeValue } from 'tdesign-react'; 3 | import dayjs from 'dayjs'; 4 | import { useTranslation } from 'react-i18next'; 5 | 6 | const RECENT_7_DAYS: DateRangeValue = [ 7 | dayjs().subtract(7, 'day').format('YYYY-MM-DD'), 8 | dayjs().subtract(1, 'day').format('YYYY-MM-DD'), 9 | ]; 10 | 11 | const LastWeekDatePicker = (onChange: (value: DateRangeValue) => void) => { 12 | const { t } = useTranslation(); 13 | return ( 14 | onChange(value)} 20 | /> 21 | ); 22 | }; 23 | 24 | export default LastWeekDatePicker; 25 | -------------------------------------------------------------------------------- /pkg/web/src/components/ErrorPage/index.module.less: -------------------------------------------------------------------------------- 1 | .errorBox { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | height: 75vh; 7 | padding: 24px; 8 | min-height: 400px; 9 | color: var(--td-brand-color); 10 | img { 11 | width: 200px; 12 | height: 140px; 13 | color: var(--td-brand-color); 14 | } 15 | 16 | .title { 17 | font-weight: 500; 18 | font-size: 20px; 19 | line-height: 28px; 20 | margin-top: 8px; 21 | color: var(--td-text-color-primary); 22 | } 23 | .description { 24 | margin: 8px 0 32px; 25 | font-size: 14px; 26 | line-height: 22px; 27 | color: var(--td-text-color-secondary); 28 | } 29 | 30 | .rightButton { 31 | margin-left: 8px; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pkg/web/src/components/common/Card.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const Card = ({ 4 | children, 5 | title = null, 6 | operations = null, 7 | style = {}, 8 | className, 9 | }: { 10 | children: React.ReactNode; 11 | title?: string | null; 12 | operations?: React.ReactNode; 13 | style?: React.CSSProperties; 14 | className?: string; 15 | }) => ( 16 |
17 |
18 | {title} 19 | {operations} 20 |
21 | {children} 22 |
23 | ); 24 | -------------------------------------------------------------------------------- /pkg/web/src/components/common/Editor.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react'; 2 | 3 | import 'monaco-editor/esm/vs/basic-languages/dockerfile/dockerfile.contribution'; 4 | import 'monaco-editor/esm/vs/basic-languages/yaml/yaml.contribution'; 5 | import MonacoEditor, { MonacoEditorProps } from 'react-monaco-editor'; 6 | 7 | export interface EditorProps { 8 | value: string; 9 | } 10 | 11 | const Editor = (props: EditorProps) => { 12 | const { value } = props; 13 | const options: Partial = { 14 | readOnly: true, 15 | theme: 'vs-dark', 16 | fontSize: 14, 17 | formatOnType: true, 18 | wordWrap: 'on', 19 | }; 20 | 21 | const ref = useRef(null); 22 | 23 | return ; 24 | }; 25 | 26 | export default Editor; 27 | -------------------------------------------------------------------------------- /pkg/web/src/components/common/HeadBubble.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Tooltip } from 'tdesign-react'; 3 | 4 | export interface HeadBubbleProps { 5 | /** 显示标题 */ 6 | title?: string | React.ReactElement; 7 | 8 | /** 显示的文本 */ 9 | text?: string | React.ReactElement; 10 | 11 | /** 气泡显示方式 */ 12 | position?: 'top' | 'bottom' | 'left' | 'right'; 13 | 14 | /** 对齐方式 */ 15 | align?: 'start' | 'end'; 16 | 17 | /** 用于title隐藏 */ 18 | autoflow?: boolean; 19 | } 20 | 21 | export const HeadBubble = React.memo((props: HeadBubbleProps) => { 22 | const { title = '', text = '', position, autoflow } = props; 23 | return ( 24 |
25 | {autoflow ? {title} : {title}} 26 | {text}

} placement={position || 'top'}> 27 | 28 | 29 | 30 |
31 |
32 | ); 33 | }); 34 | -------------------------------------------------------------------------------- /pkg/web/src/configs/color.ts: -------------------------------------------------------------------------------- 1 | import { ETheme } from 'types/index.d'; 2 | 3 | export const defaultColor = ['#0052d9', '#0594fa', '#00a870', '#ebb105', '#ed7b2f', '#e34d59', '#ed49b4', '#834ec2']; 4 | 5 | export const darkColor = ['#4582e6', '#29a4fb', '#03a56f', '#ca8d03', '#ed7b2f', '#ea7b84', '#f172c5', '#ab87d5']; 6 | 7 | // todo 写法可优化 8 | export const colorMap: { 9 | [key: string]: string; 10 | } = { 11 | '#0052d9': '', 12 | '#0594fa': 'cyan', 13 | '#00a870': 'green', 14 | '#ebb105': 'yellow', 15 | '#ed7b2f': 'orange', 16 | '#e34d59': 'red', 17 | '#ed49b4': 'pink', 18 | '#834ec2': 'purple', 19 | 20 | '#4582e6': '', 21 | '#29a4fb': 'cyan', 22 | '#03a56f': 'green', 23 | '#ca8d03': 'yellow', 24 | '#ea7b84': 'red', 25 | '#f172c5': 'pink', 26 | '#ab87d5': 'purple', 27 | }; 28 | 29 | export const CHART_COLORS = { 30 | [ETheme.light]: { 31 | textColor: 'rgba(0, 0, 0, 0.9)', 32 | placeholderColor: 'rgba(0, 0, 0, 0.35)', 33 | borderColor: '#dcdcdc', 34 | containerColor: '#fff', 35 | }, 36 | [ETheme.dark]: { 37 | textColor: 'rgba(255, 255, 255, 0.9)', 38 | placeholderColor: 'rgba(255, 255, 255, 0.35)', 39 | borderColor: '#5e5e5e', 40 | containerColor: '#242424', 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /pkg/web/src/configs/host.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | mock: { 3 | // 本地mock数据 4 | API: '', 5 | }, 6 | development: { 7 | // 开发环境接口请求 8 | API: '', 9 | }, 10 | test: { 11 | // 测试环境接口地址 12 | API: '', 13 | }, 14 | production: { 15 | // 正式环境接口地址 16 | API: '', 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /pkg/web/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.avif' { 2 | export default src as string; 3 | } 4 | 5 | declare module '*.bmp' { 6 | export default src as string; 7 | } 8 | 9 | declare module '*.gif' { 10 | export default src as string; 11 | } 12 | 13 | declare module '*.jpg' { 14 | export default src as string; 15 | } 16 | 17 | declare module '*.jpeg' { 18 | export default src as string; 19 | } 20 | 21 | declare module '*.png' { 22 | export default src as string; 23 | } 24 | 25 | declare module '*.webp' { 26 | export default src as string; 27 | } 28 | 29 | declare module '*.svg' { 30 | export default src as string; 31 | } 32 | declare module '*.svg?component' { 33 | export default src as string; 34 | } 35 | declare module '*.module.css' { 36 | export default classes as { readonly [key: string]: string }; 37 | } 38 | 39 | declare module '*.module.less' { 40 | export default classes as { readonly [key: string]: string }; 41 | } 42 | 43 | declare module '*.less' { 44 | export default classes as { readonly [key: string]: string }; 45 | } 46 | 47 | declare module 'hex-to-hsl'; 48 | 49 | declare interface ImportMeta { 50 | env: { 51 | MODE: 'development' | 'test' | 'production'; 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /pkg/web/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useSelector'; 2 | export * from './useCraneUrl'; 3 | export * from './useCraneDiscount'; 4 | export * from './useGrafanaQueyStr'; 5 | export * from './useIsIntersecting'; 6 | export * from './useIsNeedSelectNamespace'; 7 | export * from './useIsValidPanel'; 8 | export * from './useDashboardControl'; 9 | -------------------------------------------------------------------------------- /pkg/web/src/hooks/useCraneDiscount.ts: -------------------------------------------------------------------------------- 1 | import { useSelector } from './useSelector'; 2 | import { useFetchClusterListQuery } from 'services/clusterApi'; 3 | 4 | export const useCraneDiscount = () => { 5 | const selectedClusterId = useSelector((state) => state.insight.selectedClusterId); 6 | 7 | const clusterList = useFetchClusterListQuery({}); 8 | 9 | return (clusterList.data?.data?.items ?? []).find((cluster) => cluster.id === selectedClusterId)?.discount; 10 | }; 11 | -------------------------------------------------------------------------------- /pkg/web/src/hooks/useCraneUrl.ts: -------------------------------------------------------------------------------- 1 | import { useSelector } from './useSelector'; 2 | import { useFetchClusterListQuery } from 'services/clusterApi'; 3 | 4 | export const useCraneUrl = () => { 5 | const selectedClusterId = useSelector((state) => state.insight.selectedClusterId); 6 | 7 | const clusterList = useFetchClusterListQuery({}); 8 | 9 | return (clusterList.data?.data?.items ?? []).find((cluster) => cluster.id === selectedClusterId)?.craneUrl; 10 | }; 11 | -------------------------------------------------------------------------------- /pkg/web/src/hooks/useDashboardControl.ts: -------------------------------------------------------------------------------- 1 | import { useSelector } from './useSelector'; 2 | import { useFetchClusterListQuery } from 'services/clusterApi'; 3 | 4 | export const useDashboardControl = () => { 5 | const selectedClusterId = useSelector((state) => state.insight.selectedClusterId); 6 | 7 | const clusterList = useFetchClusterListQuery({}); 8 | 9 | return (clusterList.data?.data?.items ?? []).find((cluster) => cluster.id === selectedClusterId)?.dashboardControl; 10 | }; 11 | -------------------------------------------------------------------------------- /pkg/web/src/hooks/useIsIntersecting.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const useIsIntersecting = (ref: { current: Element }) => { 4 | const [isIntersecting, setIntersecting] = React.useState(false); 5 | 6 | const observer = React.useMemo( 7 | () => new IntersectionObserver(([entry]) => setIntersecting(entry.isIntersecting)), 8 | [], 9 | ); 10 | 11 | // eslint-disable-next-line consistent-return 12 | React.useEffect(() => { 13 | if (ref && ref.current) { 14 | observer.observe(ref.current); 15 | // Remove the observer as soon as the component is unmounted 16 | return () => { 17 | observer.disconnect(); 18 | }; 19 | } 20 | }, [observer, ref]); 21 | 22 | return isIntersecting; 23 | }; 24 | -------------------------------------------------------------------------------- /pkg/web/src/hooks/useIsNeedSelectNamespace.ts: -------------------------------------------------------------------------------- 1 | import { grafanaApi } from 'services/grafanaApi'; 2 | 3 | export const useIsNeedSelectNamespace = ({ selectedDashboard }: { selectedDashboard?: any } = {}) => { 4 | const dashboardDetail = grafanaApi.useFetchDashboardDetailQuery( 5 | { dashboardUid: selectedDashboard?.uid }, 6 | { skip: !selectedDashboard?.uid }, 7 | ); 8 | 9 | return (dashboardDetail?.data?.dashboard?.templating?.list ?? []).find( 10 | (item: { name: string }) => item.name === 'namespace' || item.name === 'Namespace', 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /pkg/web/src/hooks/useIsValidPanel.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-prototype-builtins 2 | export const useIsValidPanel = ({ panel }: { panel: any }) => !panel.hasOwnProperty('collapsed'); 3 | -------------------------------------------------------------------------------- /pkg/web/src/hooks/useSelector.ts: -------------------------------------------------------------------------------- 1 | import { shallowEqual, useSelector as useRawSelector } from 'react-redux'; 2 | 3 | import { RootState } from 'modules/store'; 4 | 5 | export const useSelector = (selector: (state: RootState) => T): T => 6 | useRawSelector((state) => selector(state as RootState), shallowEqual); 7 | -------------------------------------------------------------------------------- /pkg/web/src/i18n/index.ts: -------------------------------------------------------------------------------- 1 | import i18n, { t } from 'i18next'; 2 | import LanguageDetector from 'i18next-browser-languagedetector'; 3 | import ICU from 'i18next-icu'; 4 | 5 | import { initReactI18next, Trans } from 'react-i18next'; 6 | 7 | import en from './resources/en/translation.json'; 8 | import zh from './resources/zh/translation.json'; 9 | 10 | const resources = { 11 | zh: { 12 | translation: { ...zh }, 13 | }, 14 | en: { 15 | translation: { ...en }, 16 | }, 17 | }; 18 | 19 | export enum SupportLanguages { 20 | zh = 'zh', 21 | en = 'en', 22 | } 23 | 24 | i18n 25 | .use(LanguageDetector) 26 | .use(new ICU()) 27 | .use(initReactI18next) // passes i18n down to react-i18next 28 | .init({ 29 | fallbackLng: 'zh', 30 | resources, 31 | debug: process.env.NODE_ENV === 'development', 32 | saveMissing: true, 33 | 34 | interpolation: { 35 | escapeValue: false, // react already safes from xss 36 | }, 37 | }); 38 | 39 | const changeLanguage = (language: SupportLanguages) => { 40 | i18n.changeLanguage(language); 41 | }; 42 | 43 | export { t, Trans, changeLanguage }; 44 | 45 | export default i18n; 46 | -------------------------------------------------------------------------------- /pkg/web/src/layouts/components/AppLayout.module.less: -------------------------------------------------------------------------------- 1 | .sidePanel { 2 | height: 100%; 3 | display: flex; 4 | flex-direction: row!important; 5 | } 6 | 7 | .sideContainer { 8 | flex: 1; 9 | min-width: 760px; 10 | overflow: auto; 11 | } 12 | 13 | .topPanel { 14 | min-width: 1150px; 15 | } 16 | 17 | .mixPanel { 18 | height: 100%; 19 | } 20 | 21 | .mixMain { 22 | flex-direction: row!important; 23 | flex: 1; 24 | overflow: auto; 25 | } 26 | 27 | .mixContent { 28 | overflow: auto; 29 | } 30 | -------------------------------------------------------------------------------- /pkg/web/src/layouts/components/AppRouter.module.less: -------------------------------------------------------------------------------- 1 | .loading { 2 | height: 100%; 3 | width: 100%; 4 | display: flex; 5 | justify-content: center; 6 | align-content: center; 7 | } 8 | -------------------------------------------------------------------------------- /pkg/web/src/layouts/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { selectGlobal } from 'modules/global'; 2 | import { useAppSelector } from 'modules/store'; 3 | import React from 'react'; 4 | import { useTranslation } from 'react-i18next'; 5 | import { Layout, Row } from 'tdesign-react'; 6 | 7 | const { Footer: TFooter } = Layout; 8 | 9 | const Footer = () => { 10 | const { t } = useTranslation(); 11 | const globalState = useAppSelector(selectGlobal); 12 | if (!globalState.showFooter) { 13 | return null; 14 | } 15 | 16 | return ( 17 | 18 | {t('Thanks for all the crane contributors.')} 19 | 20 | ); 21 | }; 22 | 23 | export default React.memo(Footer); 24 | -------------------------------------------------------------------------------- /pkg/web/src/layouts/components/Header/HeaderIcon.module.less: -------------------------------------------------------------------------------- 1 | .dropdown { 2 | :global { 3 | .t-dropdown__item { 4 | max-width: none !important; 5 | width: 117px; 6 | &-text { 7 | display: flex; 8 | align-items: center; 9 | } 10 | .t-icon { 11 | margin-right: 8px; 12 | } 13 | } 14 | } 15 | } 16 | 17 | .badgeBtn { 18 | overflow: visible; 19 | } 20 | -------------------------------------------------------------------------------- /pkg/web/src/layouts/components/Header/Search.module.less: -------------------------------------------------------------------------------- 1 | .panel { 2 | :global { 3 | .t-input { 4 | border: none; 5 | } 6 | .t-input:hover { 7 | background: var(--td-bg-color-secondarycontainer); 8 | transition: background var(--td-anim-duration-base) linear 9 | } 10 | .t-input--focused { 11 | box-shadow: none; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /pkg/web/src/layouts/components/Header/Search.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Input } from 'tdesign-react'; 3 | import { SearchIcon } from 'tdesign-icons-react'; 4 | import Style from './Search.module.less'; 5 | import { useTranslation } from 'react-i18next'; 6 | 7 | const Search = () => { 8 | const { t } = useTranslation(); 9 | return } placeholder={t('请输入搜索内容')} />; 10 | }; 11 | 12 | export default React.memo(Search); 13 | -------------------------------------------------------------------------------- /pkg/web/src/layouts/components/Header/index.module.less: -------------------------------------------------------------------------------- 1 | .panel { 2 | flex-shrink: 0; 3 | padding-left: 20px; 4 | padding-right: 20px; 5 | position: sticky; 6 | top: 0; 7 | z-index: 101; 8 | display: flex; 9 | justify-content: space-between; 10 | border-bottom: 1px solid var(--td-component-stroke); 11 | } 12 | -------------------------------------------------------------------------------- /pkg/web/src/layouts/components/Menu.module.less: -------------------------------------------------------------------------------- 1 | .menuPanel { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100%; 5 | } 6 | 7 | .menuTip { 8 | color: var(--td-text-color-primary); 9 | opacity: 0.4; 10 | height: 38px; 11 | line-height: 38px; 12 | text-align: center; 13 | } 14 | 15 | .menuLogo { 16 | width: 100%; 17 | height: 100%; 18 | display: flex; 19 | justify-content: center; 20 | align-items: center; 21 | cursor: pointer; 22 | color: var(--td-text-color-primary); 23 | } 24 | 25 | .menuMiniLogo { 26 | width: 28px; 27 | height: 28px; 28 | } 29 | 30 | .logoFull { 31 | color: var(--td-text-color-primary); 32 | width: 184px; 33 | height: 32px; 34 | } 35 | -------------------------------------------------------------------------------- /pkg/web/src/layouts/components/MenuLogo.tsx: -------------------------------------------------------------------------------- 1 | import Style from './Menu.module.less'; 2 | import FullLogo from 'assets/svg/crane-logo-full.svg?component'; 3 | import MiniLogo from 'assets/svg/crane-logo-mini.svg?component'; 4 | import React, { memo } from 'react'; 5 | import { useNavigate } from 'react-router-dom'; 6 | 7 | interface IProps { 8 | collapsed?: boolean; 9 | } 10 | 11 | export default memo((props: IProps) => { 12 | const navigate = useNavigate(); 13 | 14 | const handleClick = () => { 15 | navigate('/'); 16 | }; 17 | 18 | return ( 19 |
20 | {props.collapsed ? : } 21 |
22 | ); 23 | }); 24 | -------------------------------------------------------------------------------- /pkg/web/src/layouts/components/Page.module.less: -------------------------------------------------------------------------------- 1 | .panel { 2 | margin: 24px; 3 | padding: 0; 4 | overflow: auto; 5 | } 6 | 7 | .breadcrumb { 8 | margin-bottom: 8px; 9 | } 10 | -------------------------------------------------------------------------------- /pkg/web/src/layouts/components/Page.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useAppDispatch, useAppSelector } from '../../modules/store'; 3 | import { selectGlobal, switchFullPage } from '../../modules/global'; 4 | import { Breadcrumb, Layout } from 'tdesign-react'; 5 | import Style from './Page.module.less'; 6 | 7 | const { Content } = Layout; 8 | const { BreadcrumbItem } = Breadcrumb; 9 | 10 | const Page = ({ 11 | children, 12 | isFullPage, 13 | breadcrumbs, 14 | }: React.PropsWithChildren<{ isFullPage?: boolean; breadcrumbs?: string[] }>) => { 15 | const globalState = useAppSelector(selectGlobal); 16 | const dispatch = useAppDispatch(); 17 | useEffect(() => { 18 | dispatch(switchFullPage(isFullPage)); 19 | }, [isFullPage]); 20 | 21 | if (isFullPage) { 22 | return <>{children}; 23 | } 24 | 25 | return ( 26 | 27 | {globalState.showBreadcrumbs && ( 28 | 29 | {breadcrumbs?.map((item, index) => ( 30 | {item} 31 | ))} 32 | 33 | )} 34 | {children} 35 | 36 | ); 37 | }; 38 | 39 | export default React.memo(Page); 40 | -------------------------------------------------------------------------------- /pkg/web/src/layouts/components/Setting/RadioColor.module.less: -------------------------------------------------------------------------------- 1 | .panel { 2 | display: flex; 3 | justify-content: space-between; 4 | } 5 | 6 | .box { 7 | cursor: pointer; 8 | border: 2px solid; 9 | padding: 4px; 10 | border-radius: 50%; 11 | } 12 | 13 | .item { 14 | width: 24px; 15 | height: 24px; 16 | border-radius: 50%; 17 | } 18 | -------------------------------------------------------------------------------- /pkg/web/src/layouts/components/Setting/RadioColor.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { defaultColor } from 'configs/color'; 3 | import Style from './RadioColor.module.less'; 4 | 5 | interface IProps { 6 | defaultValue?: number | string; 7 | onChange: (color: string) => void; 8 | } 9 | 10 | const RadioColor = (props: IProps) => ( 11 |
12 | {defaultColor.map((color, index) => ( 13 |
props?.onChange(color)} 16 | className={Style.box} 17 | style={{ borderColor: props.defaultValue === color ? color : 'transparent' }} 18 | > 19 |
20 |
21 | ))} 22 |
23 | ); 24 | 25 | export default React.memo(RadioColor); 26 | -------------------------------------------------------------------------------- /pkg/web/src/layouts/components/Setting/RadioRect.module.less: -------------------------------------------------------------------------------- 1 | .radioRectPanel { 2 | display: flex; 3 | justify-content: space-between; 4 | } 5 | 6 | .rectItem { 7 | border: 2px solid #e3e6eb; 8 | padding: 8px; 9 | border-radius: 3px; 10 | cursor: pointer; 11 | } 12 | 13 | .rectItemSelected { 14 | border-color: var(--td-brand-color); 15 | } 16 | 17 | .rectText { 18 | text-align: center; 19 | font-size: 14px; 20 | margin-top: 8px; 21 | line-height: 21px; 22 | } 23 | 24 | .rectImg { 25 | width: 88px; 26 | height: 48px; 27 | background-size: 100% 100%; 28 | } 29 | -------------------------------------------------------------------------------- /pkg/web/src/layouts/components/Setting/index.module.less: -------------------------------------------------------------------------------- 1 | .settingTitle { 2 | margin: 32px 0 24px 0; 3 | color: var(--td-text-color-primary); 4 | font-size: 14px; 5 | line-height: 22px; 6 | font-weight: 500; 7 | } 8 | 9 | .settingSubTitle { 10 | font-size: 14px; 11 | line-height: 21px; 12 | margin-bottom: 24px; 13 | } 14 | -------------------------------------------------------------------------------- /pkg/web/src/layouts/index.module.less: -------------------------------------------------------------------------------- 1 | .panel { 2 | height: 100%; 3 | :global { 4 | .t-menu--scroll::-webkit-scrollbar { 5 | width: 8px; 6 | background: transparent; 7 | } 8 | .t-menu--scroll::-webkit-scrollbar-thumb { 9 | border-radius: 6px; 10 | border: 2px solid transparent; 11 | background-clip: content-box; 12 | background-color: #EEEEEE; 13 | } 14 | .t-card__title { 15 | font-weight: 700; 16 | font-size: 20px; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /pkg/web/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | import { HashRouter } from 'react-router-dom'; 5 | import store from 'modules/store'; 6 | import App from 'layouts/index'; 7 | 8 | import 'tdesign-react/es/style/index.css'; 9 | import './styles/theme.less'; 10 | import './styles/index.less'; 11 | 12 | const renderApp = () => { 13 | ReactDOM.render( 14 | 15 | 16 | 17 | 18 | , 19 | document.getElementById('app'), 20 | ); 21 | }; 22 | 23 | renderApp(); 24 | -------------------------------------------------------------------------------- /pkg/web/src/modules/configSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from '@reduxjs/toolkit'; 2 | 3 | export interface ConfigState { 4 | chartBaselineHeight: number; 5 | chartDefaultHeight: number; 6 | } 7 | 8 | export const initialConfigState: ConfigState = { 9 | chartBaselineHeight: 50, 10 | chartDefaultHeight: 300, 11 | }; 12 | 13 | const slice = createSlice({ 14 | name: 'config', 15 | initialState: initialConfigState, 16 | reducers: { 17 | chartBaselineHeight: (state, action: PayloadAction) => { 18 | state.chartBaselineHeight = action.payload; 19 | }, 20 | chartDefaultHeight: (state, action: PayloadAction) => { 21 | state.chartDefaultHeight = action.payload; 22 | }, 23 | }, 24 | }); 25 | 26 | export const configActions = slice.actions; 27 | export const configReducer = slice.reducer; 28 | -------------------------------------------------------------------------------- /pkg/web/src/modules/overviewSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from '@reduxjs/toolkit'; 2 | 3 | export interface OverviewState { 4 | searchText: string; 5 | } 6 | 7 | export const initialOverviewState: OverviewState = { 8 | searchText: '', 9 | }; 10 | 11 | const slice = createSlice({ 12 | name: 'overview', 13 | initialState: initialOverviewState, 14 | reducers: { 15 | searchText: (state, action: PayloadAction) => { 16 | state.searchText = action.payload; 17 | }, 18 | }, 19 | }); 20 | 21 | export const overviewActions = slice.actions; 22 | export const overviewReducer = slice.reducer; 23 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Cost/CarbonInsight/Index.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react'; 2 | import CarbonChart from './components/CarbonChart'; 3 | 4 | const CarbonDashBoard = () => ( 5 |
6 | 7 |
8 | ); 9 | 10 | export default memo(CarbonDashBoard); 11 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Cost/CarbonInsight/components/CarbonChart.module.less: -------------------------------------------------------------------------------- 1 | .carbonChartPanel { 2 | margin-top: 16px; 3 | } 4 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Dashboard/Base/components/CpuChart.module.less: -------------------------------------------------------------------------------- 1 | .cpuChartPanel { 2 | margin-top: 16px; 3 | } 4 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Dashboard/Base/components/MemoryChart.module.less: -------------------------------------------------------------------------------- 1 | .memoryChartPanel { 2 | margin-top: 16px; 3 | } 4 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Dashboard/Base/components/MiddleChart.module.less: -------------------------------------------------------------------------------- 1 | .middleChartPanel { 2 | margin-top: 16px; 3 | } 4 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Dashboard/Base/components/TopPanel.module.less: -------------------------------------------------------------------------------- 1 | .iconWrap { 2 | display: inline-flex; 3 | align-items: center; 4 | justify-content: center; 5 | width: 56px; 6 | height: 56px; 7 | background: var(--td-brand-color-1); 8 | border-radius: 50%; 9 | position: absolute; 10 | top: 32px; 11 | right: 32px; 12 | } 13 | 14 | .svgIcon { 15 | font-size: 24px; 16 | color: var(--td-brand-color); 17 | } 18 | 19 | .paneLineChart { 20 | width: 103px; 21 | height: 56px; 22 | overflow: hidden; 23 | position: absolute; 24 | top: 55px; 25 | right: 32px; 26 | } 27 | .paneBarChart { 28 | width: 103px; 29 | height: 36px; 30 | -webkit-tap-highlight-color: transparent; 31 | user-select: none; 32 | position: absolute; 33 | top: 55px; 34 | right: 32px; 35 | } 36 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Dashboard/Base/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react'; 2 | import TopPanel from './components/TopPanel'; 3 | import MiddleChart from './components/MiddleChart'; 4 | import CpuChart from './components/CpuChart'; 5 | import MemoryChart from './components/MemoryChart'; 6 | 7 | const DashBoard = () => ( 8 |
9 | 10 | 11 | 12 | 13 |
14 | ); 15 | 16 | export default memo(DashBoard); 17 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Recommend/IdleNode/index.module.less: -------------------------------------------------------------------------------- 1 | .list-common-table-query { 2 | margin-bottom: 30px; 3 | } 4 | 5 | .table-container { 6 | margin-top: 30px; 7 | } 8 | &-btn { 9 | margin-right: 10px; 10 | color: #29a4fb; 11 | cursor: pointer; 12 | } 13 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Recommend/RecommendationRule/consts.ts: -------------------------------------------------------------------------------- 1 | interface IOption { 2 | value: number | string; 3 | label: string; 4 | } 5 | 6 | // 推荐类型 7 | 8 | export const RECOMMENDATION_RULE_TYPE_OPTIONS: Array = [ 9 | { value: 'Resource', label: 'Resource' }, 10 | { value: 'Replicas', label: 'Replicas' }, 11 | ]; 12 | 13 | // 合同状态枚举 14 | export const CONTRACT_STATUS = { 15 | FAIL: 0, 16 | AUDIT_PENDING: 1, 17 | EXEC_PENDING: 2, 18 | EXECUTING: 3, 19 | FINISH: 4, 20 | }; 21 | 22 | export const CONTRACT_STATUS_OPTIONS: Array = [ 23 | { value: CONTRACT_STATUS.FAIL, label: '审核失败' }, 24 | { value: CONTRACT_STATUS.AUDIT_PENDING, label: '待审核' }, 25 | { value: CONTRACT_STATUS.EXEC_PENDING, label: '待履行' }, 26 | { value: CONTRACT_STATUS.EXECUTING, label: '审核成功' }, 27 | { value: CONTRACT_STATUS.FINISH, label: '已完成' }, 28 | ]; 29 | 30 | // 合同类型枚举 31 | export const CONTRACT_TYPES = { 32 | MAIN: 0, 33 | SUB: 1, 34 | SUPPLEMENT: 2, 35 | }; 36 | 37 | export const CONTRACT_TYPE_OPTIONS: Array = [ 38 | { value: CONTRACT_TYPES.MAIN, label: '主合同' }, 39 | { value: CONTRACT_TYPES.SUB, label: '子合同' }, 40 | { value: CONTRACT_TYPES.SUPPLEMENT, label: '补充合同' }, 41 | ]; 42 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Recommend/RecommendationRule/index.module.less: -------------------------------------------------------------------------------- 1 | .list-common-table-query { 2 | margin-bottom: 30px; 3 | } 4 | 5 | .table-container { 6 | margin-top: 30px; 7 | } 8 | &-btn { 9 | margin-right: 10px; 10 | color: #29a4fb; 11 | cursor: pointer; 12 | } 13 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Recommend/ReplicaRecommend/consts.ts: -------------------------------------------------------------------------------- 1 | interface IOption { 2 | value: number | string; 3 | label: string; 4 | } 5 | 6 | // 推荐类型 7 | 8 | export const RECOMMENDATION_RULE_TYPE_OPTIONS: Array = [ 9 | { value: 'Resource', label: 'Resource' }, 10 | { value: 'Replicas', label: 'Replicas' }, 11 | ]; 12 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Recommend/ReplicaRecommend/index.module.less: -------------------------------------------------------------------------------- 1 | .list-common-table-query { 2 | margin-bottom: 30px; 3 | } 4 | 5 | .table-container { 6 | margin-top: 30px; 7 | } 8 | &-btn { 9 | margin-right: 10px; 10 | color: #29a4fb; 11 | cursor: pointer; 12 | } 13 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Recommend/ResourcesRecommend/consts.ts: -------------------------------------------------------------------------------- 1 | interface IOption { 2 | value: number | string; 3 | label: string; 4 | } 5 | 6 | // 推荐类型 7 | 8 | export const RECOMMENDATION_RULE_TYPE_OPTIONS: Array = [ 9 | { value: 'Resource', label: 'Resource' }, 10 | { value: 'Replicas', label: 'Replicas' }, 11 | ]; 12 | 13 | // 合同状态枚举 14 | export const CONTRACT_STATUS = { 15 | FAIL: 0, 16 | AUDIT_PENDING: 1, 17 | EXEC_PENDING: 2, 18 | EXECUTING: 3, 19 | FINISH: 4, 20 | }; 21 | 22 | export const CONTRACT_STATUS_OPTIONS: Array = [ 23 | { value: CONTRACT_STATUS.FAIL, label: '审核失败' }, 24 | { value: CONTRACT_STATUS.AUDIT_PENDING, label: '待审核' }, 25 | { value: CONTRACT_STATUS.EXEC_PENDING, label: '待履行' }, 26 | { value: CONTRACT_STATUS.EXECUTING, label: '审核成功' }, 27 | { value: CONTRACT_STATUS.FINISH, label: '已完成' }, 28 | ]; 29 | 30 | // 合同类型枚举 31 | export const CONTRACT_TYPES = { 32 | MAIN: 0, 33 | SUB: 1, 34 | SUPPLEMENT: 2, 35 | }; 36 | 37 | export const CONTRACT_TYPE_OPTIONS: Array = [ 38 | { value: CONTRACT_TYPES.MAIN, label: '主合同' }, 39 | { value: CONTRACT_TYPES.SUB, label: '子合同' }, 40 | { value: CONTRACT_TYPES.SUPPLEMENT, label: '补充合同' }, 41 | ]; 42 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Recommend/ResourcesRecommend/index.module.less: -------------------------------------------------------------------------------- 1 | .list-common-table-query { 2 | margin-bottom: 30px; 3 | } 4 | 5 | .table-container { 6 | margin-top: 30px; 7 | } 8 | &-btn { 9 | margin-right: 10px; 10 | color: #29a4fb; 11 | cursor: pointer; 12 | } 13 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Result/403/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ErrorPage from 'components/ErrorPage'; 3 | 4 | const UnAuthorized = () => ; 5 | 6 | export default React.memo(UnAuthorized); 7 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Result/404/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ErrorPage from 'components/ErrorPage'; 3 | 4 | const NotFound = () => ; 5 | 6 | export default React.memo(NotFound); 7 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Result/500/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ErrorPage from 'components/ErrorPage'; 3 | 4 | const ServerError = () => ; 5 | 6 | export default React.memo(ServerError); 7 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Result/Fail/index.module.less: -------------------------------------------------------------------------------- 1 | .Content { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | height: 75vh; 7 | 8 | .icon { 9 | font-size: 72px; 10 | color: var(--td-text-color-secondary); 11 | } 12 | 13 | .title { 14 | font-weight: 500; 15 | font-size: 20px; 16 | line-height: 28px; 17 | margin-top: 28px; 18 | color: var(--td-text-color-primary); 19 | } 20 | .description { 21 | margin: 8px 0 32px; 22 | font-size: 14px; 23 | line-height: 22px; 24 | color: var(--td-text-color-secondary); 25 | } 26 | 27 | .rightButton { 28 | margin-left: 8px; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Result/Fail/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react'; 2 | import { Button } from 'tdesign-react'; 3 | import { ErrorCircleIcon } from 'tdesign-icons-react'; 4 | 5 | import style from './index.module.less'; 6 | 7 | const Fail = () => ( 8 |
9 | 10 |
创建失败
11 |
抱歉,您的项目创建失败,企业微信联系检查创建者权限,或返回修改。
12 |
13 | 14 | 17 |
18 |
19 | ); 20 | 21 | export default memo(Fail); 22 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Result/Maintenance/index.module.less: -------------------------------------------------------------------------------- 1 | .Content { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | height: 75vh; 7 | padding: 24px; 8 | min-height: 400px; 9 | color: var(--td-brand-color); 10 | 11 | .title { 12 | font-weight: 500; 13 | font-size: 20px; 14 | line-height: 28px; 15 | margin-top: 8px; 16 | color: var(--td-text-color-primary); 17 | } 18 | .description { 19 | margin: 8px 0 32px; 20 | font-size: 14px; 21 | line-height: 22px; 22 | color: var(--td-text-color-secondary); 23 | } 24 | 25 | .rightButton { 26 | margin-left: 8px; 27 | } 28 | 29 | .resultSlotContainer { 30 | position: relative; 31 | display: flex; 32 | flex-direction: column; 33 | align-items: center; 34 | justify-content: space-between; 35 | color: var(--td-text-color-secondary); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Result/Maintenance/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react'; 2 | import { Button } from 'tdesign-react'; 3 | 4 | import MaintenanceIcon from 'assets/svg/assets-result-maintenance.svg?component'; 5 | import style from './index.module.less'; 6 | 7 | const BrowserIncompatible = () => ( 8 |
9 | 10 |
系统维护中
11 |
系统维护中,请稍后再试。
12 | 13 |
14 | 15 |
16 |
17 | ); 18 | 19 | export default memo(BrowserIncompatible); 20 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Result/NetworkError/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react'; 2 | import { Button } from 'tdesign-react'; 3 | 4 | import NetworkErrorIcon from 'assets/svg/assets-result-network-error.svg?component'; 5 | import style from '../index.module.less'; 6 | 7 | const NetworkError = () => ( 8 |
9 | 10 |
网络异常
11 |
网络异常,请稍后再试
12 |
13 | 14 | 17 |
18 |
19 | ); 20 | 21 | export default memo(NetworkError); 22 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Result/Success/index.module.less: -------------------------------------------------------------------------------- 1 | .Content { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | height: 75vh; 7 | 8 | .icon { 9 | font-size: 72px; 10 | color: var(--td-success-color); 11 | } 12 | 13 | .title { 14 | font-weight: 500; 15 | font-size: 20px; 16 | line-height: 28px; 17 | margin-top: 28px; 18 | color: var(--td-text-color-primary); 19 | } 20 | .description { 21 | margin: 8px 0 32px; 22 | font-size: 14px; 23 | line-height: 22px; 24 | color: var(--td-text-color-secondary); 25 | } 26 | 27 | .rightButton { 28 | margin-left: 8px; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Result/Success/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react'; 2 | import { Button } from 'tdesign-react'; 3 | import { CheckCircleIcon } from 'tdesign-icons-react'; 4 | 5 | import style from './index.module.less'; 6 | 7 | const Success = () => ( 8 |
9 | 10 |
项目已创建成功
11 |
可以联系负责人分发应用
12 |
13 | 14 | 17 |
18 |
19 | ); 20 | 21 | export default memo(Success); 22 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Result/index.module.less: -------------------------------------------------------------------------------- 1 | .Content { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | align-items: center; 6 | height: 75vh; 7 | padding: 24px; 8 | min-height: 400px; 9 | color: var(--td-brand-color); 10 | 11 | .title { 12 | font-weight: 500; 13 | font-size: 20px; 14 | line-height: 28px; 15 | margin-top: 8px; 16 | color: var(--td-text-color-primary); 17 | } 18 | .description { 19 | margin: 8px 0 32px; 20 | font-size: 14px; 21 | line-height: 22px; 22 | color: var(--td-text-color-secondary); 23 | } 24 | 25 | .rightButton { 26 | margin-left: 8px; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pkg/web/src/pages/Settings/cluster/OverviewPanel.tsx: -------------------------------------------------------------------------------- 1 | import { EditClusterModal } from './EditClusterModal'; 2 | import { OverviewActionPanel } from './OverviewActionPanel'; 3 | import { OverviewTablePanel } from './OverviewTablePanel'; 4 | import classnames from 'classnames'; 5 | import React, { memo } from 'react'; 6 | import CommonStyle from 'styles/common.module.less'; 7 | 8 | export default memo(() => ( 9 |
10 | <> 11 | 12 | 13 | 14 | 15 |
16 | )); 17 | -------------------------------------------------------------------------------- /pkg/web/src/router/modules/dashboard.ts: -------------------------------------------------------------------------------- 1 | import { lazy } from 'react'; 2 | import { useTranslation } from 'react-i18next'; 3 | import { DashboardIcon } from 'tdesign-icons-react'; 4 | import { IRouter } from '../index'; 5 | 6 | export const useDashboardRouteConfig = (): IRouter[] => { 7 | const { t } = useTranslation(); 8 | 9 | return [ 10 | { 11 | path: '/dashboard', 12 | meta: { 13 | title: t('集群总览'), 14 | Icon: DashboardIcon, 15 | }, 16 | Component: lazy(() => import('pages/Dashboard/Base')), 17 | }, 18 | ]; 19 | }; 20 | -------------------------------------------------------------------------------- /pkg/web/src/router/modules/others.ts: -------------------------------------------------------------------------------- 1 | import { lazy } from 'react'; 2 | import { IRouter } from '../index'; 3 | 4 | const otherRoutes: IRouter[] = [ 5 | { 6 | path: '/403', 7 | Component: lazy(() => import('pages/Result/403')), 8 | }, 9 | { 10 | path: '/500', 11 | Component: lazy(() => import('pages/Result/500')), 12 | }, 13 | { 14 | path: '*', 15 | Component: lazy(() => import('pages/Result/404')), 16 | }, 17 | ]; 18 | 19 | export default otherRoutes; 20 | -------------------------------------------------------------------------------- /pkg/web/src/router/modules/settings.ts: -------------------------------------------------------------------------------- 1 | import { IRouter } from '../index'; 2 | import { lazy } from 'react'; 3 | import { SettingIcon } from 'tdesign-icons-react'; 4 | import { useTranslation } from 'react-i18next'; 5 | 6 | export const useSettingRouteConfig = (): IRouter[] => { 7 | const { t } = useTranslation(); 8 | return [ 9 | { 10 | path: '/settings', 11 | meta: { 12 | title: t('设置'), 13 | Icon: SettingIcon, 14 | }, 15 | children: [ 16 | { 17 | path: 'cluster', 18 | Component: lazy(() => import('pages/Settings/cluster/OverviewPanel')), 19 | meta: { 20 | title: t('集群管理'), 21 | }, 22 | }, 23 | ], 24 | }, 25 | ]; 26 | }; 27 | -------------------------------------------------------------------------------- /pkg/web/src/services/namespaceApi.ts: -------------------------------------------------------------------------------- 1 | import { buildRetryFetchBaseQuery } from './retryFetchBaseQuery'; 2 | import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; 3 | 4 | export interface FetchNamespaceListArgs { 5 | clusterId?: string; 6 | } 7 | 8 | export interface FetchNamespaceListResult { 9 | error?: any; 10 | data: { 11 | totalCount: number; 12 | items: Array; 13 | }; 14 | } 15 | 16 | export const namespaceApi = createApi({ 17 | reducerPath: 'namespaceApi', 18 | tagTypes: ['namespaceList'], 19 | baseQuery: buildRetryFetchBaseQuery( 20 | fetchBaseQuery({ 21 | baseUrl: '/api/v1/namespaces', 22 | timeout: 15000, 23 | prepareHeaders: (headers, _api) => { 24 | headers.set('Content-Type', 'application/json'); 25 | headers.set('Accept', 'application/json'); 26 | return headers; 27 | }, 28 | }), 29 | ), 30 | endpoints: (builder) => ({ 31 | fetchNamespaceList: builder.query({ 32 | query: (args) => ({ 33 | url: `/${args.clusterId}`, 34 | }), 35 | }), 36 | }), 37 | }); 38 | 39 | export const { useLazyFetchNamespaceListQuery, useFetchNamespaceListQuery } = namespaceApi; 40 | -------------------------------------------------------------------------------- /pkg/web/src/services/retryFetchBaseQuery.ts: -------------------------------------------------------------------------------- 1 | import { BaseQueryFn, retry } from '@reduxjs/toolkit/query/react'; 2 | 3 | /** 4 | * BuildRetryFetchBaseQuery 5 | * https://redux-toolkit.js.org/rtk-query/usage/customizing-queries#automatic-retries 6 | * 7 | * RTK Query exports a utility called retry that you can wrap the baseQuery in your API definition with. It defaults to 5 attempts with a basic exponential backoff. 8 | * The default behavior would retry at these intervals: 9 | * 10 | * 600ms * random(0.4, 1.4) 11 | * 1200ms * random(0.4, 1.4) 12 | * 2400ms * random(0.4, 1.4) 13 | * 4800ms * random(0.4, 1.4) 14 | * 9600ms * random(0.4, 1.4) 15 | * @param fn fetchBaseQuery 16 | * @returns 17 | */ 18 | export const buildRetryFetchBaseQuery = (fn: any): BaseQueryFn => { 19 | const staggeredBaseQuery = retry(fn, { 20 | maxRetries: 10, 21 | }); 22 | return staggeredBaseQuery; 23 | }; 24 | -------------------------------------------------------------------------------- /pkg/web/src/styles/common.module.less: -------------------------------------------------------------------------------- 1 | .pageWithPadding { 2 | padding: 30px 32px; 3 | } 4 | 5 | .pageWithColor { 6 | background: var(--td-bg-color-container); 7 | box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1); 8 | border-radius: 3px; 9 | } 10 | -------------------------------------------------------------------------------- /pkg/web/src/styles/index.less: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 4 | 'Droid Sans', 'Helvetica Neue', sans-serif; 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | } 8 | 9 | html, 10 | body, 11 | #app { 12 | height: 100%; 13 | } 14 | 15 | code { 16 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; 17 | } 18 | -------------------------------------------------------------------------------- /pkg/web/src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | export enum ETheme { 2 | light = 'light', 3 | dark = 'dark', 4 | } 5 | -------------------------------------------------------------------------------- /pkg/web/src/utils/copyToClipboard.ts: -------------------------------------------------------------------------------- 1 | export const copyToClipboard = (textToCopy: string) => { 2 | // navigator clipboard api needs a secure context (https) 3 | if (navigator.clipboard && window.isSecureContext) { 4 | // navigator clipboard api method' 5 | return navigator.clipboard.writeText(textToCopy); 6 | } 7 | // text area method 8 | const textArea = document.createElement('textarea'); 9 | textArea.value = textToCopy; 10 | // make the textarea out of viewport 11 | textArea.style.position = 'fixed'; 12 | textArea.style.left = '-999999px'; 13 | textArea.style.top = '-999999px'; 14 | document.body.appendChild(textArea); 15 | textArea.focus(); 16 | textArea.select(); 17 | return new Promise((res, rej) => { 18 | // here the magic happens 19 | // eslint-disable-next-line no-unused-expressions 20 | document.execCommand('copy') ? res() : rej(); 21 | textArea.remove(); 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /pkg/web/src/utils/getErrorMsg.ts: -------------------------------------------------------------------------------- 1 | import { SerializedError } from '@reduxjs/toolkit'; 2 | import { FetchBaseQueryError } from '@reduxjs/toolkit/dist/query'; 3 | 4 | import { t } from '../i18n'; 5 | 6 | export const getErrorMsg = (error: FetchBaseQueryError | SerializedError | undefined): string => { 7 | let msg = t('发生未知错误,请稍候再试'); 8 | 9 | const serializedError = error as SerializedError; 10 | const fetchBaseQueryError = error as FetchBaseQueryError; 11 | const anyErrror = error as any; 12 | 13 | if (serializedError.message) { 14 | msg = serializedError.message; 15 | } else if ((fetchBaseQueryError.data as any)?.message) { 16 | msg = (fetchBaseQueryError.data as any).message; 17 | } else if (anyErrror?.data?.ErrStatus) { 18 | msg = (error as any).data.ErrStatus.message; 19 | } 20 | 21 | return msg; 22 | }; 23 | -------------------------------------------------------------------------------- /pkg/web/src/utils/normalizeNumber.ts: -------------------------------------------------------------------------------- 1 | export const normalizeNumber = (num: number) => { 2 | if (num < 0.01) { 3 | return num.toPrecision(1); 4 | } 5 | return num.toFixed(2); 6 | }; 7 | -------------------------------------------------------------------------------- /pkg/web/src/utils/path.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 解析当前菜单对应的路由 3 | * @param path1 4 | * @param path2 5 | */ 6 | export const resolve = (path1 = '', path2 = '') => { 7 | let separator = '/'; 8 | if (path1.endsWith('/') || path2.startsWith('/')) { 9 | separator = ''; 10 | } 11 | return `${path1}${separator}${path2}`; 12 | }; 13 | -------------------------------------------------------------------------------- /pkg/web/src/utils/rangeMap.ts: -------------------------------------------------------------------------------- 1 | import dayjs from 'dayjs'; 2 | 3 | import { QueryWindow } from '../models'; 4 | 5 | export const rangeMap = { 6 | [QueryWindow.LAST_1_DAY]: [dayjs(+dayjs() - 3600 * 24 * 1000), dayjs()], 7 | [QueryWindow.LAST_7_DAY]: [dayjs().subtract(7, 'd').startOf('day'), dayjs()], 8 | [QueryWindow.LAST_30_DAY]: [dayjs().subtract(30, 'd').startOf('day'), dayjs()], 9 | }; 10 | -------------------------------------------------------------------------------- /pkg/web/src/utils/request.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import proxy from '../configs/host'; 3 | 4 | const env = import.meta.env.MODE || 'development'; 5 | const API_HOST = proxy[env].API; 6 | 7 | const SUCCESS_CODE = 0; 8 | const TIMEOUT = 5000; 9 | 10 | export const instance = axios.create({ 11 | baseURL: API_HOST, 12 | timeout: TIMEOUT, 13 | withCredentials: true, 14 | }); 15 | 16 | instance.interceptors.response.use( 17 | // eslint-disable-next-line consistent-return 18 | (response) => { 19 | if (response.status === 200) { 20 | const { data } = response; 21 | if (data.code === SUCCESS_CODE) { 22 | return data; 23 | } 24 | return Promise.reject(data); 25 | } 26 | return Promise.reject(response?.data); 27 | }, 28 | (e) => Promise.reject(e), 29 | ); 30 | 31 | export default instance; 32 | -------------------------------------------------------------------------------- /pkg/web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src", 4 | "target": "es5", 5 | "lib": [ 6 | "dom", 7 | "dom.iterable", 8 | "esnext" 9 | ], 10 | "allowJs": true, 11 | "skipLibCheck": true, 12 | "esModuleInterop": true, 13 | "allowSyntheticDefaultImports": true, 14 | "strict": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "noFallthroughCasesInSwitch": true, 17 | "module": "esnext", 18 | "moduleResolution": "node", 19 | "resolveJsonModule": true, 20 | "isolatedModules": true, 21 | "noEmit": true, 22 | "jsx": "react-jsx" 23 | }, 24 | "include": [ 25 | "src" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /pkg/webhooks/config.go: -------------------------------------------------------------------------------- 1 | package webhooks 2 | 3 | // WebhookConfig represents the config of prometheus 4 | type WebhookConfig struct { 5 | Enabled bool 6 | } 7 | -------------------------------------------------------------------------------- /site/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/.DS_Store -------------------------------------------------------------------------------- /site/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM klakegg/hugo:ext-alpine 2 | 3 | RUN apk add git && \ 4 | git config --global --add safe.directory /src 5 | -------------------------------------------------------------------------------- /site/README.md: -------------------------------------------------------------------------------- 1 | # Crane website 2 | 3 | This is the website for Crane! 4 | 5 | We use [Hugo](https://gohugo.io/) and [google/docsy](https://github.com/google/docsy) to be the website template for development. 6 | 7 | ## Install DevDependencies 8 | 9 | ```bash 10 | npm i 11 | ``` 12 | 13 | ## Running the website locally 14 | 15 | Building and running the site locally requires a recent `extended` version of [Hugo](https://gohugo.io). 16 | You can find out more about how to install Hugo for your environment in our 17 | [Getting started](https://www.docsy.dev/docs/getting-started/#prerequisites-and-installation) guide. 18 | 19 | ``` 20 | hugo server 21 | ``` 22 | 23 | ## Build the static website 24 | 25 | ```bash 26 | hugo --minify 27 | ``` 28 | -------------------------------------------------------------------------------- /site/content/en/blog/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Crane Blog" 3 | menu: 4 | main: 5 | weight: 30 6 | --- 7 | -------------------------------------------------------------------------------- /site/content/en/community/_index.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /site/content/en/docs/Best Practices/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Best Practices" 3 | weight: 14 4 | description: > 5 | Best Practices for adopting crane. 6 | --- 7 | -------------------------------------------------------------------------------- /site/content/en/docs/Best Practices/how-kujiale-adopt-ehpa.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "How Kujiale achieve autoscaling with Crane EHPA" 3 | weight: 20 4 | description: > 5 | How Kujiale achieve autoscaling with Crane EHPA. 6 | --- 7 | 8 | The original article is:[How Kujiale achieve autoscaling with Crane EHPA](https://mp.weixin.qq.com/s/3X_hHbisynxDwWx9Lnbp-w) -------------------------------------------------------------------------------- /site/content/en/docs/Contributing/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Contributing" 3 | linkTitle: "Contributing Guidelines" 4 | weight: 16 5 | description: > 6 | How to contribute to the community 7 | --- 8 | -------------------------------------------------------------------------------- /site/content/en/docs/Core Concept/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Core Concept" 3 | weight: 11 4 | description: > 5 | Core concepts in Crane. 6 | --- 7 | -------------------------------------------------------------------------------- /site/content/en/docs/Getting started/Installation/_index.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | title: "Installation" 4 | linkTitle: "Installation" 5 | description: "Installation for crane" 6 | weight: 11 7 | 8 | --- -------------------------------------------------------------------------------- /site/content/en/docs/Getting started/_index.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | title: "Getting Started" 4 | linkTitle: "Getting Started" 5 | description: "Getting Started with Crane" 6 | weight: 10 7 | 8 | --- -------------------------------------------------------------------------------- /site/content/en/docs/Proposals/_index.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | title: "Proposals" 4 | linkTitle: "Proposals" 5 | description: "Proposals for new feature or enhancement" 6 | weight: 15 7 | 8 | --- -------------------------------------------------------------------------------- /site/content/en/docs/Roadmap/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Roadmap" 3 | weight: 17 4 | description: > 5 | The roadmap for future 6 | --- 7 | -------------------------------------------------------------------------------- /site/content/en/docs/Roadmap/roadmap-2023.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Roadmap for 2023" 3 | description: "The designed roadmap items for 2023" 4 | weight: 10 5 | 6 | --- 7 | 8 | Please refer the following sections for Crane release plan of 2023. 9 | 10 | Please let us know if you have urgent needs which are not presented in the plan. 11 | 12 | ### 0.10.0 13 | - Optimization of Grafana & Crane Dashboard Phase 2, adding resource optimization model charts 14 | - Carbon Footprint, carbon emissions display Phase 2 15 | - Support for immediately executable recommendations 16 | 17 | ### 0.11.0 18 | - Utilization analysis and resource recommendation for GPU resources (no implementation of GPU sharing technology, only integration) 19 | - Integration of recommendation framework with CICD platforms such as ArgoCD, Jenkins 20 | 21 | ### 0.12.0 22 | - More idle resource recognition, nodes, PV, LB 23 | - Cost allocation based on Project and Department application dimensions 24 | - Node resource amplification 25 | -------------------------------------------------------------------------------- /site/content/en/docs/Tutorials/Colocation with Enhanced QOS/_index.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | title: "Colocation with Enhanced QOS" 4 | weight: 9 5 | description: > 6 | Introduction to QOS and colocation related capabilities. 7 | --- -------------------------------------------------------------------------------- /site/content/en/docs/Tutorials/Recommendation/_index.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | title: "Recommendation" 4 | weight: 10 5 | description: > 6 | Docs for Recommendation. 7 | --- -------------------------------------------------------------------------------- /site/content/en/docs/Tutorials/_index.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | title: "Tutorials" 4 | linkTitle: "Tutorials" 5 | weight: 12 6 | description: > 7 | Crane Tutorials. 8 | --- 9 | -------------------------------------------------------------------------------- /site/content/en/docs/_index.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "Documentation" 3 | description = "All the documentation for Crane" 4 | +++ -------------------------------------------------------------------------------- /site/content/en/featured-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/content/en/featured-background.jpg -------------------------------------------------------------------------------- /site/content/en/search.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Search Results 3 | layout: search 4 | 5 | --- 6 | 7 | -------------------------------------------------------------------------------- /site/content/zh/blog/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "博客" 3 | menu: 4 | main: 5 | weight: 30 6 | --- 7 | -------------------------------------------------------------------------------- /site/content/zh/community/_index.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /site/content/zh/docs/Best Practices/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "最佳实践" 3 | weight: 14 4 | description: > 5 | 使用 Crane 的最佳实践. 6 | --- 7 | -------------------------------------------------------------------------------- /site/content/zh/docs/Best Practices/how-kujiale-adopt-ehpa.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "酷家乐基于 Crane EHPA 的弹性落地实践" 3 | weight: 20 4 | description: > 5 | 酷家乐基于 Crane EHPA 的弹性落地实践. 6 | --- 7 | 8 | 原文请见:[酷家乐基于 Crane EHPA 的弹性落地实践](https://mp.weixin.qq.com/s/3X_hHbisynxDwWx9Lnbp-w) -------------------------------------------------------------------------------- /site/content/zh/docs/Contributing/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "贡献" 3 | linkTitle: "贡献指南" 4 | weight: 16 5 | description: > 6 | 如何向社区贡献代码 7 | --- 8 | -------------------------------------------------------------------------------- /site/content/zh/docs/Core Concept/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "核心概念" 3 | weight: 11 4 | description: > 5 | Crane 中的核心概念 6 | --- 7 | -------------------------------------------------------------------------------- /site/content/zh/docs/Getting started/Installation/_index.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | title: "安装指南" 4 | linkTitle: "安装指南" 5 | description: "如何安装 crane 及 kubectl-crane 命令行工具" 6 | weight: 33 7 | 8 | --- -------------------------------------------------------------------------------- /site/content/zh/docs/Getting started/_index.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | title: "开始使用" 4 | description: "开始使用 Crane" 5 | weight: 10 6 | 7 | --- -------------------------------------------------------------------------------- /site/content/zh/docs/Proposals/_index.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | title: "提案" 4 | description: "新功能和能力增强的提案" 5 | weight: 15 6 | 7 | --- -------------------------------------------------------------------------------- /site/content/zh/docs/Roadmap/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "路线图" 3 | weight: 17 4 | description: > 5 | 未来的路线图 6 | --- 7 | -------------------------------------------------------------------------------- /site/content/zh/docs/Roadmap/roadmap-2023.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Roadmap for 2023" 3 | description: "The designed roadmap items for 2023" 4 | weight: 10 5 | 6 | --- 7 | 8 | Please refer the following sections for Crane release plan of 2023. 9 | 10 | Please let us know if you have urgent needs which are not presented in the plan. 11 | 12 | ### 0.10.0 13 | - Optimization of Grafana & Crane Dashboard Phase 2, adding resource optimization model charts 14 | - Carbon Footprint, carbon emissions display Phase 2 15 | - Support for immediately executable recommendations 16 | 17 | ### 0.11.0 18 | - Utilization analysis and resource recommendation for GPU resources (no implementation of GPU sharing technology, only integration) 19 | - Integration of recommendation framework with CICD platforms such as ArgoCD, Jenkins 20 | 21 | ### 0.12.0 22 | - More idle resource recognition, nodes, PV, LB 23 | - Cost allocation based on Project and Department application dimensions 24 | - Node resource amplification 25 | -------------------------------------------------------------------------------- /site/content/zh/docs/Tutorials/Colocation with Enhanced QOS/_index.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | title: "QOS增强与混布" 4 | weight: 9 5 | description: > 6 | QOS与混布相关能力介绍. 7 | --- -------------------------------------------------------------------------------- /site/content/zh/docs/Tutorials/Recommendation/_index.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | title: "智能推荐" 4 | weight: 10 5 | description: > 6 | 智能推荐能力介绍. 7 | --- -------------------------------------------------------------------------------- /site/content/zh/docs/Tutorials/_index.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | title: "教程" 4 | weight: 12 5 | description: > 6 | Crane 教程. 7 | --- -------------------------------------------------------------------------------- /site/content/zh/docs/Tutorials/dynamic-scheduler-plugin.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "动态调度器:一个基于负载感知的调度插件" 3 | description: "动态调度器插件功能介绍" 4 | weight: 12 5 | --- 6 | 7 | kubernetes 的原生调度器只能通过资源请求来调度 pod,这很容易造成一系列负载不均的问题: 8 | 9 | - 对于某些节点,实际负载与资源请求相差不大,这会导致很大概率出现稳定性问题。 10 | - 对于其他节点来说,实际负载远小于资源请求,这将导致资源的巨大浪费。 11 | 12 | 为了解决这些问题,动态调度器根据实际的节点利用率构建了一个简单但高效的模型,并过滤掉那些负载高的节点来平衡集群。 13 | 14 | ## 设计细节 15 | 16 | ### 架构 17 | ![](/images/dynamic-scheduler-plugin.png) 18 | 19 | 20 | 如上图,动态调度器依赖于`Prometheus`和`Node-exporter`收集和汇总指标数据,它由两个组件组成: 21 | 22 | !!! note "Note" 23 | `Node-annotator` 目前是 `Crane-scheduler-controller`的一个模块. 24 | 25 | - `Node-annotator`定期从 Prometheus 拉取数据,并以注释的形式在节点上用时间戳标记它们。 26 | - `Dynamic plugin`直接从节点的注释中读取负载数据,过滤并基于简单的算法对候选节点进行评分。 27 | 28 | ### 调度策略 29 | 动态调度器提供了一个默认值[调度策略](../deploy/manifests/policy.yaml)并支持用户自定义策略。默认策略依赖于以下指标: 30 | 31 | - `cpu_usage_avg_5m` 32 | - `cpu_usage_max_avg_1h` 33 | - `cpu_usage_max_avg_1d` 34 | - `mem_usage_avg_5m` 35 | - `mem_usage_max_avg_1h` 36 | - `mem_usage_max_avg_1d` 37 | 38 | 在调度的`Filter`阶段,如果该节点的实际使用率大于上述任一指标的阈值,则该节点将被过滤。而在`Score`阶段,最终得分是这些指标值的加权和。 39 | 40 | ### Hot Value 41 | 42 | 在生产集群中,可能会频繁出现调度热点,因为创建 Pod 后节点的负载不能立即增加。因此,我们定义了一个额外的指标,名为`Hot Value`,表示节点最近几次的调度频率。并且节点的最终优先级是最终得分减去`Hot Value`。 43 | -------------------------------------------------------------------------------- /site/content/zh/docs/_index.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "文档" 3 | description = "Crane 的所有文档" 4 | +++ -------------------------------------------------------------------------------- /site/content/zh/featured-background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/content/zh/featured-background.jpg -------------------------------------------------------------------------------- /site/content/zh/search.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Search Results 3 | layout: search 4 | 5 | --- 6 | 7 | -------------------------------------------------------------------------------- /site/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.3" 2 | 3 | services: 4 | 5 | site: 6 | image: docsy/docsy-example 7 | build: 8 | context: . 9 | command: server 10 | ports: 11 | - "1313:1313" 12 | volumes: 13 | - .:/src 14 | -------------------------------------------------------------------------------- /site/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/google/docsy-example 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/FortAwesome/Font-Awesome v0.0.0-20210804190922-7d3d774145ac // indirect 7 | github.com/google/docsy v0.2.0 // indirect 8 | github.com/twbs/bootstrap v4.6.1+incompatible // indirect 9 | ) 10 | -------------------------------------------------------------------------------- /site/go.sum: -------------------------------------------------------------------------------- 1 | github.com/FortAwesome/Font-Awesome v0.0.0-20210804190922-7d3d774145ac h1:AjwgwoaDsNEA1Wtc8pgw/BqG7SEk9bKxXPjEPQQ42vY= 2 | github.com/FortAwesome/Font-Awesome v0.0.0-20210804190922-7d3d774145ac/go.mod h1:IUgezN/MFpCDIlFezw3L8j83oeiIuYoj28Miwr/KUYo= 3 | github.com/google/docsy v0.2.0-pre.0.20220404161753-f7b37a0aca2a h1:bnufXLbTD8QCLbqygy/kmYxUK1JINSlHU5rLQYTcFMQ= 4 | github.com/google/docsy v0.2.0-pre.0.20220404161753-f7b37a0aca2a/go.mod h1:yuKLZHMX5CKiLUH55+ePFJaYnoSwUVVffNareaOGQYo= 5 | github.com/google/docsy v0.2.0 h1:DN6wfyyp2rXsjdV1K3wioxOBTRvG6Gg48wLPDso2lc4= 6 | github.com/google/docsy v0.2.0/go.mod h1:shlabwAQakGX6qpXU6Iv/b/SilpHRd7d+xqtZQd3v+8= 7 | github.com/google/docsy/dependencies v0.2.0-pre.0.20220404161753-f7b37a0aca2a h1:fy6IqUmWGMdQngRa7+CP1cRkTseQK7OEsqx6r7dNuSA= 8 | github.com/google/docsy/dependencies v0.2.0-pre.0.20220404161753-f7b37a0aca2a/go.mod h1:oPdn05sNt61uT6K+LqNRhYq1jeqrsbbQMDXkPdPscmA= 9 | github.com/google/docsy/dependencies v0.2.0/go.mod h1:2zZxHF+2qvkyXhLZtsbnqMotxMukJXLaf8fAZER48oo= 10 | github.com/twbs/bootstrap v4.6.1+incompatible h1:75PsBfPU1SS65ag0Z3Cq6JNXVAfUNfB0oCLHh9k9Fu8= 11 | github.com/twbs/bootstrap v4.6.1+incompatible/go.mod h1:fZTSrkpSf0/HkL0IIJzvVspTt1r9zuf7XlZau8kpcY0= 12 | -------------------------------------------------------------------------------- /site/layouts/404.html: -------------------------------------------------------------------------------- 1 | {{ define "main"}} 2 |
3 |
4 |

Not found

5 |

Oops! This page doesn't exist. Try going back to our home page.

6 | 7 |

You can learn how to make a 404 page like this in Custom 404 Pages.

8 |
9 |
10 | {{ end }} 11 | -------------------------------------------------------------------------------- /site/layouts/partials/footer.html: -------------------------------------------------------------------------------- 1 | {{ if .Params.math }}{{ partial "helpers/katex.html" . }}{{ end }} -------------------------------------------------------------------------------- /site/layouts/partials/helpers/katex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /site/netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | [build.environment] 3 | HUGO_VERSION = "0.100.1" 4 | GO_VERSION = "1.18.3" 5 | -------------------------------------------------------------------------------- /site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tech-doc-hugo", 3 | "version": "0.0.1", 4 | "description": "Hugo theme for technical documentation.", 5 | "main": "none.js", 6 | "scripts": { 7 | "build": "hugo --minify" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/google/docsy-example.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/google/docsy-example/issues" 17 | }, 18 | "homepage": "https://github.com/google/docsy-example#readme", 19 | "devDependencies": { 20 | "autoprefixer": "^10.4.11", 21 | "postcss": "^8.3.7", 22 | "postcss-cli": "^9.1.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /site/static/CNAME: -------------------------------------------------------------------------------- 1 | gocrane.io 2 | -------------------------------------------------------------------------------- /site/static/images/Crane-FinOps-Certified-Solution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/Crane-FinOps-Certified-Solution.png -------------------------------------------------------------------------------- /site/static/images/add_cluster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/add_cluster.png -------------------------------------------------------------------------------- /site/static/images/advanced_cpuset_manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/advanced_cpuset_manager.png -------------------------------------------------------------------------------- /site/static/images/algorithm/dsp/acf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/algorithm/dsp/acf.png -------------------------------------------------------------------------------- /site/static/images/algorithm/dsp/dsp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/algorithm/dsp/dsp.png -------------------------------------------------------------------------------- /site/static/images/algorithm/dsp/dsp_debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/algorithm/dsp/dsp_debug.png -------------------------------------------------------------------------------- /site/static/images/algorithm/dsp/input0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/algorithm/dsp/input0.png -------------------------------------------------------------------------------- /site/static/images/algorithm/dsp/lft_0_001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/algorithm/dsp/lft_0_001.png -------------------------------------------------------------------------------- /site/static/images/algorithm/dsp/lft_0_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/algorithm/dsp/lft_0_01.png -------------------------------------------------------------------------------- /site/static/images/algorithm/dsp/linear_regression.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/algorithm/dsp/linear_regression.png -------------------------------------------------------------------------------- /site/static/images/algorithm/dsp/max_value.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/algorithm/dsp/max_value.png -------------------------------------------------------------------------------- /site/static/images/algorithm/dsp/missing_data_fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/algorithm/dsp/missing_data_fill.png -------------------------------------------------------------------------------- /site/static/images/algorithm/dsp/remove_outliers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/algorithm/dsp/remove_outliers.png -------------------------------------------------------------------------------- /site/static/images/algorithm/dsp/spectrum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/algorithm/dsp/spectrum.png -------------------------------------------------------------------------------- /site/static/images/analytics-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/analytics-arch.png -------------------------------------------------------------------------------- /site/static/images/cpu-usage-water-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/cpu-usage-water-line.png -------------------------------------------------------------------------------- /site/static/images/crane-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/crane-arch.png -------------------------------------------------------------------------------- /site/static/images/crane-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/crane-architecture.png -------------------------------------------------------------------------------- /site/static/images/crane-dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/crane-dashboard.png -------------------------------------------------------------------------------- /site/static/images/crane-ehpa-metrics-chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/crane-ehpa-metrics-chart.png -------------------------------------------------------------------------------- /site/static/images/crane-ehpa-replicas-chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/crane-ehpa-replicas-chart.png -------------------------------------------------------------------------------- /site/static/images/crane-ehpa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/crane-ehpa.png -------------------------------------------------------------------------------- /site/static/images/crane-keda-ali-compare-cron.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/crane-keda-ali-compare-cron.png -------------------------------------------------------------------------------- /site/static/images/crane-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/crane-overview.png -------------------------------------------------------------------------------- /site/static/images/crane-qos-ensurance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/crane-qos-ensurance.png -------------------------------------------------------------------------------- /site/static/images/crane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/crane.png -------------------------------------------------------------------------------- /site/static/images/crane_recommendation_framework.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/crane_recommendation_framework.jpg -------------------------------------------------------------------------------- /site/static/images/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/dashboard.png -------------------------------------------------------------------------------- /site/static/images/dashboard_nodeport.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/dashboard_nodeport.png -------------------------------------------------------------------------------- /site/static/images/developer-guide/make_all_binaries_result.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/developer-guide/make_all_binaries_result.jpg -------------------------------------------------------------------------------- /site/static/images/developer-guide/make_all_finish.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/developer-guide/make_all_finish.jpg -------------------------------------------------------------------------------- /site/static/images/developer-guide/make_image_docker_images.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/developer-guide/make_image_docker_images.jpg -------------------------------------------------------------------------------- /site/static/images/developer-guide/make_image_finish.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/developer-guide/make_image_finish.jpg -------------------------------------------------------------------------------- /site/static/images/developer-guide/make_image_start.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/developer-guide/make_image_start.jpg -------------------------------------------------------------------------------- /site/static/images/disablescheduling-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/disablescheduling-example.png -------------------------------------------------------------------------------- /site/static/images/dynamic-scheduler-plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/dynamic-scheduler-plugin.png -------------------------------------------------------------------------------- /site/static/images/first_cluster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/first_cluster.png -------------------------------------------------------------------------------- /site/static/images/opa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/opa.png -------------------------------------------------------------------------------- /site/static/images/recommendation-framework.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/recommendation-framework.png -------------------------------------------------------------------------------- /site/static/images/remote-adapter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/remote-adapter.png -------------------------------------------------------------------------------- /site/static/images/resource-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/resource-flow.png -------------------------------------------------------------------------------- /site/static/images/resource-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/resource-model.png -------------------------------------------------------------------------------- /site/static/images/resource-waste.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/resource-waste.jpg -------------------------------------------------------------------------------- /site/static/images/topology-awareness-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/topology-awareness-architecture.png -------------------------------------------------------------------------------- /site/static/images/topology-awareness-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/topology-awareness-details.png -------------------------------------------------------------------------------- /site/static/images/tsdb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/tsdb.png -------------------------------------------------------------------------------- /site/static/images/users/netease.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/users/netease.png -------------------------------------------------------------------------------- /site/static/images/users/shushu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/users/shushu.png -------------------------------------------------------------------------------- /site/static/images/users/tencent-cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/users/tencent-cloud.png -------------------------------------------------------------------------------- /site/static/images/users/xiaohongshu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/users/xiaohongshu.png -------------------------------------------------------------------------------- /site/static/images/waterline-construct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/waterline-construct.png -------------------------------------------------------------------------------- /site/static/images/wechat.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/static/images/wechat.jpeg -------------------------------------------------------------------------------- /site/training.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocrane/crane/2b0ddae8ebbca49788d77ece632d73b252533ef9/site/training.zip -------------------------------------------------------------------------------- /tools/initializer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.13.5 2 | 3 | WORKDIR / 4 | COPY qos-checking.sh / 5 | RUN chmod 777 /qos-checking.sh -------------------------------------------------------------------------------- /tools/initializer/podinfo: -------------------------------------------------------------------------------- 1 | cluster="test-cluster1" 2 | rack="rack-22" 3 | zone="us-est-coast" 4 | gocrane.io/cpu-qos="123" 5 | -------------------------------------------------------------------------------- /tools/initializer/qos-checking.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ready=0 4 | while [ $ready -eq 0 ]; do 5 | while read line; 6 | do 7 | if echo "$line" | grep -q "gocrane.io/cpu-qos"; then 8 | echo "found annotations" $line 9 | ready=1 10 | break 11 | fi 12 | 13 | done < /etc/podinfo/annotations 14 | 15 | if [[ $ready -eq 0 ]] 16 | then 17 | sleep 5 18 | fi 19 | done 20 | 21 | echo "QOS Initialized" --------------------------------------------------------------------------------