# llama.cpp-Server je Profil (Task 6.1). Idempotent: Modelle per Prüfsumme, Image nur bauen, wenn es fehlt # (Image-Tag enthält Stand und CUDA-Version), compose ändert nur, was sich geändert hat. - name: Profile prüfen ansible.builtin.assert: that: - llamacpp_profiles | length > 0 - llamacpp_profiles | difference(llamacpp_services.keys() | list) | length == 0 fail_msg: "llamacpp_profiles muss aus {{ llamacpp_services.keys() | list }} bestehen (Inventar, Gruppe gpu_llamacpp)" # Compose baut nur mit buildx (BuildKit, auch für den Git-Kontext mit Commit-Pin) - name: docker-buildx installieren ansible.builtin.apt: name: docker-buildx state: present # --- Kartenwahl: UUID statt Index, geprüft über den Kartennamen --- - name: Karten in der VM lesen ansible.builtin.command: nvidia-smi --query-gpu=index,uuid,name --format=csv,noheader register: llamacpp_smi changed_when: false - name: UUID je Dienst aus dem Kartennamen bestimmen ansible.builtin.set_fact: llamacpp_gpu_env: >- {{ llamacpp_gpu_env | default({}) | combine({item.key: (llamacpp_smi.stdout_lines | map('split', ', ') | selectattr(2, 'equalto', item.value) | map(attribute=1) | first | default(''))}) }} loop: "{{ llamacpp_cards | dict2items }}" - name: Jede gewünschte Karte ist vorhanden ansible.builtin.assert: that: llamacpp_gpu_env[item.key] is match('GPU-') fail_msg: "{{ item.value }} für {{ item.key }} nicht gefunden: {{ llamacpp_smi.stdout_lines }}" loop: "{{ llamacpp_cards | dict2items }}" # --- Modelle --- - name: Modelle laden (Prüfsumme gegen Hugging Face) ansible.builtin.get_url: url: "{{ item.url }}" dest: "{{ llamacpp_models_dir }}/{{ item.dest }}" checksum: "{{ item.checksum }}" mode: "0644" timeout: 120 loop: "{{ llamacpp_profiles | map('extract', llamacpp_models) | flatten }}" loop_control: label: "{{ item.dest }}" # --- Compose-Projekt --- - name: Projektverzeichnis ansible.builtin.file: path: "{{ llamacpp_dir }}" state: directory mode: "0755" - name: Compose-Datei kopieren ansible.builtin.copy: src: "{{ playbook_dir }}/../../../../deploy/compose/gpu-llamacpp/docker-compose.yml" dest: "{{ llamacpp_dir }}/docker-compose.yml" mode: "0644" - name: .env schreiben (keine Geheimnisse) ansible.builtin.copy: dest: "{{ llamacpp_dir }}/.env" mode: "0644" content: | # Verwaltet von Ansible (Rolle gpu-llamacpp) COMPOSE_PROFILES={{ llamacpp_profiles | join(',') }} {% for k, v in (llamacpp_env | combine(llamacpp_gpu_env)).items() %} {{ k }}={{ v }} {% endfor %} # ufw-Regeln wirken auch bei inaktivem ufw (werden vorgemerkt). Fremde Regeln bleiben unberührt. - name: Ist ufw vorhanden? ansible.builtin.stat: path: /usr/sbin/ufw register: llamacpp_ufw - name: ufw – llama.cpp-Ports aus dem LAN vormerken community.general.ufw: rule: allow src: "{{ llamacpp_allow_from }}" port: "{{ item.port | string }}" proto: tcp comment: knaster-llamacpp loop: "{{ llamacpp_profiles | map('extract', llamacpp_services) | flatten }}" loop_control: label: "{{ item.name }}:{{ item.port }}" when: llamacpp_ufw.stat.exists # Der erste Build dauert lange (llama.cpp mit CUDA, sm_52/61, alle CPU-Varianten auf 4 vCPU) - name: Image bauen (falls es fehlt) und Dienste starten community.docker.docker_compose_v2: project_src: "{{ llamacpp_dir }}" profiles: "{{ llamacpp_profiles }}" build: policy state: present remove_orphans: true wait: false async: 7200 poll: 30 - name: Warten, bis alle Dienste gesund sind ansible.builtin.uri: url: "http://127.0.0.1:{{ item.port }}/health" status_code: 200 register: llamacpp_health until: llamacpp_health.status == 200 retries: 60 delay: 10 loop: "{{ llamacpp_profiles | map('extract', llamacpp_services) | flatten }}" loop_control: label: "{{ item.name }}:{{ item.port }}"